1. 程式人生 > >Android自定義View之使用Path繪製手勢軌跡和水波效果

Android自定義View之使用Path繪製手勢軌跡和水波效果

先看下效果圖:
path.gif

ripple.gif

繪製軌跡

繪製手指的軌跡主要是攔截View的onTouchEvent()方法,並根據手指的軌跡繪製path。path中有兩種可以實現的方法

1、Path.lineTo(x,y)方法
public class MovePathView extends View {

    private Path mPath;
    private Paint mPaint;
    //手指按下的位置
    private float startX,startY;

    public MovePathView(Context context) {
        super
(context); init(); } //初始化 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(); //設定原點 mPath.moveTo(startX,startY); break; case MotionEvent.ACTION_UP: break; case MotionEvent.ACTION_MOVE: float currX = event.getX(); float currY = event.getY(); //連線 mPath.lineTo(currX,currY); //重新整理view invalidate(); break; } //返回true,消費事件 return true; } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.drawPath(mPath,mPaint); } //對外提供的方法,重新繪製 public void reset(){ mPath.reset(); invalidate(); } }

這裡面需要知道的應該就3個點:
- View的座標系
- View的事件分發
- Path的moveTo(),lineTo()方法

2、使用Path.quadTo()繪製曲線
public class MoveQuatoView extends View {

    private Paint mPaint;
    private Path mPath;
    //上個位置
    private float mPreX,mPreY;
    //結束位置
    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);
    }
    //初始化
    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;
    }
}

上面一段程式碼為了取得平滑的效果,所以endX和endY都只取了直線的中間部分。

水波紋效果

水波紋主要用到了Path.rQuadTo()方法。
rQuadTo()也是繪製曲線的一個方法。
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:控制點X座標,表示相對上一個終點X座標的位移座標,可為負值,正值表示相加,負值表示相減;
           dy1:控制點Y座標,相對上一個終點Y座標的位移座標。同樣可為負值,正值表示相加,負值表示相減;
           dx2:終點X座標,同樣是一個相對座標,相對上一個終點X座標的位移值,可為負值,正值表示相加,負值表示相減;
           dy2:終點Y座標,同樣是一個相對,相對上一個終點Y座標的位移值。可為負值,正值表示相加,負值表示相減;
         */
        path.rQuadTo(100,-100,200,0);
        path.rQuadTo(100,100,200,0);

        canvas.drawPath(path,paint);

    }

上面程式碼總共有兩個rQuadTo()方法。
第一個path.rQuadTo(100,-100,200,0);
起始點:(100,300)
控制點座標:(200,200),X:200=100+100,Y: 200 = 300-100
終點座標: (300,300), X :300=100+200,Y:300 = 300+0
效果是:![image2.png](http://upload-images.jianshu.io/upload_images/2729169-8a82e6e36cd5cf8b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
第二個
path.rQuadTo(100,100,200,0);`
此時的起始點座標也就是第一個的終點座標,所以
起始點座標:(300,300)
控制點座標:(400,400),X: 400 = 300+100,Y:400 = 300+100
終點座標: (500,300),X: 500 = 300+200,Y:300 = 300+0
同理,如果有第三個path.rQuadTo,那麼第三個的起始點也就是上一個的終點(500,300)

搞清楚了path.rQuadTo()方法的用法就可以去實現水波紋的效果了。

public class RippleView extends View {

    private Paint mPaint;
    private Path mPath;
    //波紋的寬度
    private int mItemWaveLength = 1000;
    //波紋每次移動的距離
    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);
    }
    //初始化
    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);
        //移動後,重置mPath,將之前路徑清空
        mPath.reset();
        //距離頂部的高度
        int originY = 600;
        //波紋寬度的一般
        int halfWaveLen = mItemWaveLength/2;
        //隨著重新整理,每次移動dx距離
        mPath.moveTo(-mItemWaveLength+dx,originY);
        //for迴圈當前螢幕中所有的波紋
        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);
    }

    /**
     * 動畫的目的是讓波紋移動起來
     * 利用呼叫在path.moveTo的時候,將起始點向右移動即可實現移動,
     * 而且只要我們移動一個波長的長度,波紋就會重合,就可以實現無限迴圈了
     */
    public void startAnim(){
        //動畫移動的距離 0~mItemWaveLength
        ValueAnimator animator = ValueAnimator.ofInt(0,mItemWaveLength);
        //時間
        animator.setDuration(2000);
        //重複次數,這裡是無限次
        animator.setRepeatCount(ValueAnimator.INFINITE);
        animator.setInterpolator(new LinearInterpolator());
        //動畫重新整理監聽
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                //每次移動的距離
                dx = (int)animation.getAnimatedValue();
                //重新整理View
                postInvalidate();
            }
        });
        animator.start();
    }
}

這樣就實現了一個水波紋的效果了。