美文网首页Android技术知识Android开发经验谈Android开发
Android自定义View之使用Path绘制手势轨迹和水波效果

Android自定义View之使用Path绘制手势轨迹和水波效果

作者: 深情不及酒伴 | 来源:发表于2017-12-12 17:12 被阅读347次

    先看下效果图:


    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](https://img.haomeiwen.com/i2729169/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();
        }
    }
    

    这样就实现了一个水波纹的效果了。

    本文参考:
    自定义控件三部曲之绘图篇(六)——Path之贝赛尔曲线和手势轨迹、水波纹效果

    相关文章

      网友评论

        本文标题:Android自定义View之使用Path绘制手势轨迹和水波效果

        本文链接:https://www.haomeiwen.com/subject/bnlnixtx.html