Today, a relatively simple thing to define a Marker is actually a custom view, not much associated with the track, and the track source code to share at the end of the article, if it is helpful to you to give a star.

LocationMarker in the trace in the GIF below

Customize View LocationMarker

It mainly includes drawing droplet shape, drawing circle, drawing text, drawing ellipse shadow at the bottom, mainly drawing droplet shape, here using Bessel three order drawing, first directly look at the code:

public class LocationMarker extends View {
    private Path mPath;
    private Paint mFillCirclePaint;
    private Paint mTextPaint;
    private VPoint p2;
    private VPoint p4;
    private HPoint p1;
    private HPoint p3;
    private Context mContext;

    private float c;
    private float blackMagic = 0.551915024494 f;
    private int wrapperColor;
    private int circleColor;

    private int radius = DisplayUtil.dip2px(20);
    private String mMilePost;
    private int textSize = 13;

    private boolean drawBottomShader;

    public LocationMarker(Context context, int radius, String milePost, int textSize) {
        this(context, null);
        this.mContext = context;
        this.radius = radius;
        this.mMilePost = milePost;
        this.textSize = textSize;
        init();
    }

    public LocationMarker(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
        this.mContext = context;
        init();
    }

    public LocationMarker(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        this.mContext = context;
        init();
    }

    /** * Initialization operation */
    private void init(a) {
        mFillCirclePaint = new Paint();
        mFillCirclePaint.setColor(0xFFFFFFFF);
        mFillCirclePaint.setStyle(Paint.Style.FILL);
        mFillCirclePaint.setStrokeWidth(1);
        mFillCirclePaint.setAntiAlias(true);
        mPath = new Path();
        p2 = new VPoint();
        p4 = new VPoint();
        p1 = new HPoint();
        p3 = new HPoint();
        c = radius * blackMagic;
        initTextPain();
        wrapperColor = R.color.location_wrapper;
        circleColor = R.color.location_inner_circle;
    }

    public void setColors(int wrapperColorResource, int circleColorResource){
        this.wrapperColor = wrapperColorResource;
        this.circleColor = circleColorResource;
    }


    private void initTextPain(a) {
        mTextPaint = new Paint();
        mTextPaint.setColor(0xFFFFFFFF);
        mTextPaint.setStyle(Paint.Style.FILL);
        mTextPaint.setStrokeWidth(1);
        mTextPaint.setAntiAlias(true);
        mTextPaint.setTypeface(Typeface.DEFAULT_BOLD);
        mTextPaint.setTextSize(DisplayUtil.sp2px(mContext, textSize));
    }

    @Override
    protected void onDraw(Canvas canvas) {
        mPath.reset();
        canvas.translate(getWidth() / 2, getHeight() / 2);
        drawWaterDrop(canvas, radius);
    }

 
    private void drawWaterDrop(Canvas canvas, int radius) {
        canvas.save();
        Path path = getPath(radius);

      // Path of the inner circle
        Path circle = new Path();
        circle.addCircle(p3.x, p3.y + radius, radius - radius / 5, Path.Direction.CCW);

        / / to serrate
        canvas.setDrawFilter(new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG));
        drawBottomOval(canvas);
      // Draw the outer water droplets
        drawBezierPath(canvas, ColorUtil.getResourcesColor(mContext, wrapperColor), path);
      // Draw the inner circle
        drawBezierPath(canvas, ColorUtil.getResourcesColor(mContext, circleColor), circle);
        drawText(canvas, mMilePost);
        canvas.restore();
    }

  	// Draw the shadow at the bottom and drawBottomShader controls whether to display the shadow
    private void drawBottomOval(Canvas canvas){
        if (drawBottomShader){
            RectF rectF = new RectF();
            float width = DisplayUtil.dip2px(12);
            float height = DisplayUtil.dip2px(4);
            rectF.set(p1.x - width/2, p1.y - height/2, p1.x + width/2, p1.y + height/2);
            intcolor = mFillCirclePaint.getColor(); mFillCirclePaint.setColor(ColorUtil.getResourcesColor(mContext, R.color.location_bottom_shader)); canvas.drawOval(rectF, mFillCirclePaint); mFillCirclePaint.setColor(color); }}// Draw the text in the center of the Marker
    private void drawText(Canvas canvas, String mileStr) {
        RectF rectF = new RectF();
        float width = mTextPaint.measureText(mileStr);

        float rectFLeft = p3.x - width / 2 ;
        float rectFRight = p3.x + width / 2 ;

        float rectHeight = TextUtil.getTxtHeight1(mTextPaint);
        float rectTop = p2.y + DisplayUtil.dip2px(2);// Adjust the position so that it looks centered
        float rectBottom = p2.y + rectHeight;

        rectF.set(rectFLeft, rectTop, rectFRight, rectBottom);

        Paint.FontMetrics fontMetrics = mTextPaint.getFontMetrics();
        float top = fontMetrics.top;// Is the distance between the baseline and the border on the font, which is top in the figure above
        float bottom = fontMetrics.bottom;// Is the distance from the baseline to the bottom border of the font, the bottom in the figure above
        int baseLineY = (int) (rectF.centerY() + (top + bottom) / 2);// The Y-axis of the baseline is calculated
        canvas.drawText(mileStr, rectF.left, baseLineY, mTextPaint);
    }

    /**
     * 画圆
     */
    private Path getPath(int radius) {
        CircleModel(radius);
        Path path = new Path();
        p1.setY(p1.y + radius * 0.2 f * 1.05 f); // Set the y values of the left and right points at the bottom of p1
        p1.y += radius * 0.2 f * 1.05 f;// set p1's own y value
        path.moveTo(p1.x, p1.y);
        path.cubicTo(p1.right.x, p1.right.y, p2.bottom.x, p2.bottom.y, p2.x, p2.y);
        path.cubicTo(p2.top.x, p2.top.y, p3.right.x, p3.right.y, p3.x, p3.y);
        path.cubicTo(p3.left.x, p3.left.y, p4.top.x, p4.top.y, p4.x, p4.y);
        path.cubicTo(p4.bottom.x, p4.bottom.y, p1.left.x, p1.left.y, p1.x, p1.y);
        path.close();

        return path;
    }

    private void drawBezierPath(Canvas canvas, int color, Path path) {
        int colorOrigin = mFillCirclePaint.getColor();
        mFillCirclePaint.setColor(color);
        canvas.drawPath(path, mFillCirclePaint);
        mFillCirclePaint.setColor(colorOrigin);
    }

    private void CircleModel(int radius) {
        c = radius * blackMagic;
        p1.setY(radius);/ / right
        p3.setY(-radius);/ / the left
        p3.x = p1.x = 0;/ / circle

        p3.left.x = -c;
        p3.right.x = c;
        p1.left.x = -c * 0.36 f;
        p1.right.x = c * 0.36 f;
        //p1.p3 belongs to the upper and lower points of the circle
        p2.setX(radius); / / below
        p4.setX(-radius);/ / above
        p2.y = p4.y = 0;/ / circle
        p2.top.y = p4.top.y = -c;
        p2.bottom.y = p4.bottom.y = c;
    }


    public void setDrawBottomShader(boolean drawBottomShader) {
        this.drawBottomShader = drawBottomShader; }}Copy the code

Briefly, a circle is drawn mainly through the third-order Bezier curve. On the basis of the circle, P1 in the Bottom direction (including P1 itself and two control points left and right) is pulled up. Similar to the tidal attraction of planets and satellites, a droplet shaped sharp Angle is formed in the downward direction.

The circle is left, top, right, and bottom.

VPoint represents the vertical direction of the left and right. Here are p2 and P4. There are three points in the table.

public class VPoint {
    public float x;
    public float y;
    public PointF top = new PointF();
    public PointF bottom = new PointF();

    public void setX(float x) {
        this.x = x;
        top.x = x;
        bottom.x = x;
    }

    public void adjustY(float offset) {
        top.y -= offset;
        bottom.y += offset;
    }

    public void adjustAllX(float offset) {
        this.x += offset;
        top.x += offset;
        bottom.x += offset;
    }

    public void adjustAllY(float offset) {
        this.y += offset;
        top.y += offset;
        bottom.y += offset;
    }

    public void adjustAllXY(float x, float y) { adjustAllX(x); adjustAllY(y); }}Copy the code

Similarly, HPoint represents the tangent line of the waterside circle, and P1 and P3 represent the three points on Bottom and Top.

public class HPoint {
    public float x;
    public float y;
    public PointF left = new PointF();
    public PointF right = new PointF();

    public void setY(float y) {
        this.y = y;
        left.y = y;
        right.y = y;
    }

    public void adjustAllX(float offset) {
        this.x += offset;
        left.x += offset;
        right.x += offset;
    }

    public void adjustAllY(float offset) {
        this.y += offset;
        left.y += offset;
        right.y += offset;
    }

    public void adjustAllXY(float x, floaty) { adjustAllX(x); adjustAllY(y); }}Copy the code

See code comments for the rest.

Apply the custom View to the AMapView

Here is a direct reference to Autonavi’s demo. It should be noted that when loading the custom LocationMarker, I wrapped two layers of parent View on the outer layer of LocationMarker, but the first layer could not be displayed after trying

private void addMarker(LatLng position, String displayStr,int radius, int textSize, int wrapperColor, int circleColor, boolean showBottomShader){
		View view = View.inflate(RecordCorrectShowActivity.this, R.layout.custom_location_view, null);
		RelativeLayout locationContainer = view.findViewById(R.id.locationContainer);
		LocationMarker locationMarker = new LocationMarker(mMapView.getContext(),
				DisplayUtil.dip2px(radius), displayStr, textSize);
		locationMarker.setColors(wrapperColor, circleColor);
		locationMarker.setDrawBottomShader(showBottomShader);

		locationContainer.addView(locationMarker);
		BitmapDescriptor markerIcon = BitmapDescriptorFactory.fromView(view);

		MarkerOptions optionPosition = new MarkerOptions()
				.position(position)
				.icon(markerIcon);
		Marker marker = mAMap.addMarker(optionPosition);

		Animation markerAnimation = new ScaleAnimation(0.1.0.1); // Initialize the growth effect animation
		markerAnimation.setDuration(1000);  // Set the animation time in milliseconds
		marker.setAnimation(markerAnimation);

		marker.startAnimation();
	}
Copy the code

View with two parent layers

<? xml version="1.0" encoding="utf-8"? > <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <RelativeLayout
        android:id="@+id/locationContainer"
        android:layout_width="50dp"
        android:layout_height="55dp"/>

</LinearLayout>
Copy the code

Next time, optimizing the trajectory path, from the time of acquisition to the final optimization.

Please go to the movement track for the code. The code contains a lot of charts and Chart codes are not separated. I am free to make a series of this library, because MAAndroidChart cannot meet the requirements of the library. In the code, you need to register the Key on the Autonavi platform by yourself. You can refer to the documents of Autonavi or the Amap map track