Android Custom View uses Path to draw gesture tracks and wave effects

First look at the effect map:


path.gif
ripple.gif

Draw Track

Drawing a finger's track is mainly the onTouchEvent() method of intercepting the View and drawing a path based on the finger's track.There are two ways to do this in path

1,Path.lineTo(x,y) method
public class MovePathView extends View {

    private Path mPath;
    private Paint mPaint;
    //Position of finger press
    private float startX,startY;

    public MovePathView(Context context) {
        super(context);
        init();
    }
    //Initialization
    private void init() {
        mPaint = new Paint();
        mPath = new Path();
        mPaint.setColor(Color.BLUE);
        mPaint.setAntiAlias(true);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(4);
    }

    public MovePathView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public MovePathView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }



    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                startX = event.getX();
                startY = event.getY();
                //Set Origin
                mPath.moveTo(startX,startY);
                break;
            case MotionEvent.ACTION_UP:
                break;
            case MotionEvent.ACTION_MOVE:
                float currX = event.getX();
                float currY = event.getY();
                //Connection
                mPath.lineTo(currX,currY);
                //Refresh view
                invalidate();
                break;

        }
        //Return true, consumer events
        return true;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawPath(mPath,mPaint);
    }
    //Externally available methods, redrawing
    public void reset(){
        mPath.reset();
        invalidate();
    }

}

There are three things you should know about this:

  • View's coordinate system
  • Event Distribution for View
  • Path's moveTo(), lineTo() methods
2. Use Path.quadTo() Draw a curve
public class MoveQuatoView extends View {

    private Paint mPaint;
    private Path mPath;
    //Last position
    private float mPreX,mPreY;
    //End position
    private float endY,endX;

    public MoveQuatoView(Context context) {
        super(context);
        init();
    }

    public MoveQuatoView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public MoveQuatoView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }
    //Initialization
    private void init() {
        mPath = new Path();
        mPaint = new Paint();
        mPaint.setColor(Color.RED);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(5);
        mPaint.setAntiAlias(true);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawPath(mPath,mPaint);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                mPath.moveTo(event.getX(), event.getY());
                mPreX = event.getX();
                mPreY = event.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                endX = (mPreX + event.getX()) / 2;
                endY = (mPreY + event.getY()) / 2;
                mPath.quadTo(mPreX, mPreY, endX, endY);
                mPreX = event.getX();
                mPreY = event.getY();
                invalidate();
            break;
        }
        return true;
    }
}

For the sake of smoothing, endX and endY take only the middle part of the line.

xRipple

Water ripple is mainly used for Path.rQuadTo() method.
rQuadTo() is also a method of drawing curves.

image.png

 @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        Paint paint = new Paint();
        paint.setColor(Color.RED);
        paint.setStrokeWidth(5);
        paint.setStyle(Paint.Style.STROKE);
        paint.setAntiAlias(true);

        Path path = new Path();
        path.moveTo(100,300);
        /**
           rQuadTo(float dx1, float dy1, float dx2, float dy2)  
           dx1:The control point X coordinate, which represents the displacement coordinate relative to the last end point X coordinate, can be negative, positive values can be added, and negative values can be subtracted.
           dy1:The Y coordinate of the control point, the displacement coordinate relative to the Y coordinate of the last endpoint.Negative values can also be used, positive values mean addition, negative values mean subtraction.
           dx2:The X coordinate of the end point is also a relative coordinate. The displacement value of the X coordinate of the last end point can be negative, positive values can be added, and negative values can be subtracted.
           dy2:The Y coordinate of the end point is also a relative displacement value relative to the Y coordinate of the last end point.They can be negative, positive values are additive, and negative values are subtractive.
         */
        path.rQuadTo(100,-100,200,0);
        path.rQuadTo(100,100,200,0);

        canvas.drawPath(path,paint);

    }

The code above has two total rQuadTo() methods.
FirstPath.rQuadTo(100, -100,200,0); Start point: (100,300) Control point coordinates: (200,200), X:200=100+100, Y:200=300-100 End point coordinates: (300,300), X:300=100+200, Y:300=300+0 The effect is:![image2.png] (image2.png]Http://upload-images.jianshu.io/upload_Images/2729169-8a82e6e36cd5cf8b.png? ImageMogr2/auto-orient/strip%7CimageView2/2/w/1240) SecondPath.rQuadTo(100,100,200,0);`
The coordinate of the starting point is also the coordinate of the first ending point, so
Starting point coordinates: (300,300)
Control point coordinates: (400,400), X:400 = 300+100, Y:400 = 300+100
End point coordinates: (500,300), X:500 = 300+200, Y:300 = 300+0
Similarly, if there is a thirdPath.rQuadToThen the third starting point is the last ending point (500,300)

Get it clearPath.rQuadToThe use of the () method can achieve the effect of water ripple.

public class RippleView extends View {

    private Paint mPaint;
    private Path mPath;
    //Width of ripple
    private int mItemWaveLength = 1000;
    //Distance of each movement of the ripple
    private int dx;

    public RippleView(Context context) {
        super(context);
        init();
    }

    public RippleView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public RippleView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }
    //Initialization
    private void init(){
        mPath = new Path();
        mPaint = new Paint();
        mPaint.setColor(Color.RED);
        mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
        mPaint.setStrokeWidth(5);
        mPaint.setAntiAlias(true);
    }


    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //After moving, reset mPath and empty the previous path
        mPath.reset();
        //Height from top
        int originY = 600;
        //General ripple width
        int halfWaveLen = mItemWaveLength/2;
        //With refresh, move dx distance each time
        mPath.moveTo(-mItemWaveLength+dx,originY);
        //for loops through all the ripples on the current screen
        for (int i = -mItemWaveLength;i<=getWidth()+mItemWaveLength;i+=mItemWaveLength){
            mPath.rQuadTo(halfWaveLen/2,-100,halfWaveLen,0);
            mPath.rQuadTo(halfWaveLen/2,100,halfWaveLen,0);
        }
        mPath.lineTo(getWidth(),getHeight());
        mPath.lineTo(0,getHeight());
        mPath.close();

        canvas.drawPath(mPath,mPaint);
    }

    /**
     * The purpose of the animation is to move the ripple
     * Using calls inPath.moveToWhen you move the starting point to the right, you can move it.
     * And as long as we move the length of a wavelength, the ripple will coincide and an infinite cycle will be possible
     */
    public void startAnim(){
        //Animation Move Distance 0~mItemWaveLength
        ValueAnimator animator = ValueAnimator.ofInt(0,mItemWaveLength);
        //time
        animator.setDuration(2000);
        //Repeats, here is infinite
        animator.setRepeatCount(ValueAnimator.INFINITE);
        animator.setInterpolator(new LinearInterpolator());
        //Animation Refresh Listening
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                //Distance per move
                dx = (int)animation.getAnimatedValue();
                //Refresh View
                postInvalidate();
            }
        });
        animator.start();
    }
}

This achieves a ripple effect.

Reference:
Graphics of Custom Control Trilogy (6) - Bessel Curve and Gesture Track, Water Wave Effect of Path

Posted by dhcrusoe on Tue, 02 Jun 2020 09:38:55 -0700