Android利用 PathMeasure 实现路径动画

做者:放码过来,地址:http://blog.huangyuanlove.com前端

先上图

咱们能够利用路径动画实现不少好玩的东西,好比上面图中的相似支付宝支付完成的动画。主要用到了PathMeasur,ValueAnumator 这两个类
程序员

PathMeasure

相似于一个计算器,能够计算一些和路径相关的东西。两种初始化方式:web

PathMeasure pathMeasure = new PathMeasure();
pathMeasure.setPath(path,false);

或者编程

PathMeasure pathMeasure = new PathMeasure(path,false);
getLength()

用来获取路径长度,而且获取到的是当前曲线的长度,而不是整个 Path 的长度canvas

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    canvas.translate(150150);
    path.addRect(-50,-50,50,50,Path.Direction.CW);
    path.addRect(-100,-100,100,100,Path.Direction.CW);
    path.addRect(-120,-120,120,120,Path.Direction.CW);

    canvas.drawPath(path, paint);
    pathMeasure.setPath(path,false);
    do{
     Log.e("huangyuan",pathMeasure.getLength()+"");
    }while (pathMeasure.nextContour());

}

log以下:api

E/huangyuan: 400.0
E/huangyuan: 800.0
E/huangyuan: 960.0

pathMeasure.nextContour ()获得的曲线的顺序与添加到 Path 中的顺序相同数组

getSegment()

函数定义缓存

public boolean getSegment(float startD, float stopD, Path dst, boolean startWithMoveTo)微信

懒得翻译,本身看吧app

/**
    * Given a start and stop distance, return in dst the intervening
    * segment(s). If the segment is zero-length, return false, else return
    * true. startD and stopD are pinned to legal values (0..getLength()).
    * If startD >= stopD then return false (and leave dst untouched).
    * Begin the segment with a moveTo if startWithMoveTo is true.
*/

咱们能够这么用

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    canvas.translate(150150);
    path.addRect(-50,-50,50,50,Path.Direction.CW);
    Path dst = new Path();
    pathMeasure.setPath(path,false);
    pathMeasure.getSegment(0,150,dst,true);
    canvas.drawPath(dst,paint);
}

获得:

若是 dst 路径不为空

Path dst = new Path();
dst.lineTo(10,100);

获得

若是 dst 路径不空,startWithMoveTofalse,获得

路径加载动画

public TestPathMeasure(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
       super(context, attrs, defStyleAttr);
       init();
   }

   private void init() {
       //禁用硬件加速
       setLayerType(LAYER_TYPE_SOFTWARE,null);
       paint = new Paint(Paint.ANTI_ALIAS_FLAG);
       paint.setColor(Color.BLACK);
       paint.setStrokeWidth(4);
       paint.setStyle(Paint.Style.STROKE);
       dstPath = new Path();
       circlePath = new Path();
       circlePath.addCircle(100,100,50,Path.Direction.CW);
       pathMeasure = new PathMeasure(circlePath,true);


       ValueAnimator valueAnimator = ValueAnimator.ofFloat(0,1);
       valueAnimator.setRepeatCount(ValueAnimator.INFINITE);


       valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
           @Override
           public void onAnimationUpdate(ValueAnimator animation) {
               currentAnimValue = (float) animation.getAnimatedValue();
               invalidate();
           }
       });
       valueAnimator.setDuration(2000);
       valueAnimator.start();

   }


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


       float length = pathMeasure.getLength();

       float stop = pathMeasure.getLength() * currentAnimValue;

       float start = (float) (stop- ((0.5- Math.abs(currentAnimValue-0.5))*length));

       dstPath.reset();
       pathMeasure.getSegment(start,stop,dstPath,true);
       canvas.drawPath(dstPath,paint);
   }

咱们能够在路径动画上加个箭头,

public class TestPathMeasure extends View {

    private Paint paint;
    private Path path;
    private Path dstPath;
    private Path circlePath;
    private PathMeasure pathMeasure;
    private float currentAnimValue;

    private Bitmap icChevronRight;

    private float[] pos = new float[2];
    private float[] tan = new float[2];


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

    public TestPathMeasure(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public TestPathMeasure(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    private void init(Context context) {
        //禁用硬件加速
        setLayerType(LAYER_TYPE_SOFTWARE, null);
        paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setColor(Color.BLACK);
        paint.setStrokeWidth(4);
        paint.setStyle(Paint.Style.STROKE);
        dstPath = new Path();
        circlePath = new Path();
        circlePath.addCircle(300300150, Path.Direction.CW);
        pathMeasure = new PathMeasure(circlePath, true);
        BitmapFactory.Options options = new BitmapFactory.Options();

        icChevronRight = BitmapFactory.decodeResource(context.getResources(), R.drawable.right);


        ValueAnimator valueAnimator = ValueAnimator.ofFloat(01);
        valueAnimator.setRepeatCount(ValueAnimator.INFINITE);


        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                currentAnimValue = (float) animation.getAnimatedValue();
                invalidate();
            }
        });
        valueAnimator.setDuration(2000);
        valueAnimator.start();

    }


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


        float length = pathMeasure.getLength();

        float stop = pathMeasure.getLength() * currentAnimValue;

        float start = (float) (stop - ((0.5 - Math.abs(currentAnimValue - 0.5)) * length));

        dstPath.reset();
        pathMeasure.getSegment(start, stop, dstPath, true);
        canvas.drawPath(dstPath, paint);


        pathMeasure.getPosTan(stop, pos, tan);
        float degrees = (float) (Math.atan2(tan[1], tan[0]) * 180 / Math.PI);

        Matrix matrix = new Matrix();
//        matrix.postRotate(degrees,icChevronRight.getWidth()/2,icChevronRight.getHeight()/2);
//        matrix.postTranslate(pos[0] -icChevronRight.getWidth()/2,pos[1]-icChevronRight.getHeight()/2);

        pathMeasure.getMatrix(stop, matrix, PathMeasure.POSITION_MATRIX_FLAG | PathMeasure.TANGENT_MATRIX_FLAG);
        matrix.preTranslate(-icChevronRight.getWidth() / 2, -icChevronRight.getHeight() / 2);
        canvas.drawBitmap(icChevronRight, matrix, paint);
    }

}

这里主要用到了 MatrixgetPosTan (), 用来获得路径上某一长度的位置以及该位置的正切值。函数原型以下:


/**
 * Pins distance to 0 <= distance <= getLength(), and then computes the
 * corresponding position and tangent. Returns false if there is no path,
 * or a zero-length path was specified, in which case position and tangent
 * are unchanged.
 *
 * @param distance The distance along the current contour to sample
 * @param pos If not null, returns the sampled position (x==[0], y==[1])
 * @param tan If not null, returns the sampled tangent (x==[0], y==[1])
 * @return false if there was no path associated with this measure object
*/

public boolean getPosTan(float distance, float pos[], float tan[])

参数解释:

  • float distance: 距离 Path 起点的长度,取值范围 0<=distance<=getLength
  • float [] pos:该点的坐标值,pos [0] 表示 x 坐标,pos [y] 表示 y 坐标
  • float [] tan: 该点的正切值。

在上面的代码中须要注意的是

  • postan 数组在使用时必须先使用 new 关键词分配存储空间,而 PathMeasure.getPosTan 函数只会向数组中的元素赋值。
  • 经过 Math.atan2 (tan [1],tan [0]) 获得的是弧度值,而不是角度
  • 先利用 matrix.postRotate 将图片旋转指定角度,而后用 matix.postTranslate将图片移动到当前路径最前端 (注释掉的那两句)
  • pathMeasure 有个 getMatrix 函数,是对咱们本身实现的那种方式的封装,咱们只须要将图片移动一下就行了

山寨支付宝支付成功动画

就是先画一个圆,而后圆形里面画个对勾。。。。。很简陋的一种实现方式

public class AliPaySuccess extends View {

    private Paint paint;
    private Path dstPath;
    private Path circlePath;

    private int centerX = 500;
    private int centerY = 500;
    private int radius = 250;
    private PathMeasure pathMeasure;
    private float currentAnimValue;
    private boolean switchLine;

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

    public AliPaySuccess(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public AliPaySuccess(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init(){
        setLayerType(LAYER_TYPE_SOFTWARE, null);
        paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setColor(Color.BLACK);
        paint.setStrokeWidth(4);
        paint.setStyle(Paint.Style.STROKE);
        dstPath = new Path();
        circlePath = new Path();
        circlePath.addCircle(centerX,centerY,radius,Path.Direction.CW);
        circlePath.moveTo(centerX-radius/2,centerY);
        circlePath.lineTo(centerX,centerY+radius/2);
        circlePath.lineTo(centerX+radius/2,centerY-radius/3);
        pathMeasure = new PathMeasure(circlePath,false);
        ValueAnimator animator = ValueAnimator.ofFloat(0,2);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                currentAnimValue = (float) animation.getAnimatedValue();
                invalidate();
            }
        });
        animator.setDuration(2000);
        animator.setInterpolator(new AccelerateInterpolator());
        animator.start();
    }


    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if(currentAnimValue<1){
            float stop = pathMeasure.getLength()*currentAnimValue;
            pathMeasure.getSegment(0,stop,dstPath,true);
        }else if (currentAnimValue >1 && !switchLine){
            pathMeasure.getSegment(0,pathMeasure.getLength(),dstPath,true);
            switchLine = true;
            pathMeasure.nextContour();
        }else   {
            float stop = pathMeasure.getLength()*(currentAnimValue-1);
            pathMeasure.getSegment(0,stop,dstPath,true);
        }
        canvas.drawPath(dstPath,paint);
    }
}

首先初始化各类参数:画笔、路径和动画

  • 初始化路径时,先添加了外面的圆形,而后是里面的对勾。设置动画从 0~20-1 时画圆,1-2 时画对勾。

  • 重写 onDraw,判断当前的动画值,在 0-1时画圆,当动画值第一次大于 1 时,切换到对号那条线上。

  • 这里的 currentAnimValue 不必定会有等于 1 的时候,至少我执行了十几遍也没出现一次。因此取的是第一次大于时切换。

这里面的数值和颜色之类的属性都是直接写死的,应该经过 xml 文件读取。

以上就是本文所有内容,求个三连,奥利给!


---END---


推荐阅读:
Activity启动流程详解(基于api28)
谷歌发布Android 11 Beta 3 ,距离正式版仅咫尺之遥
TIOBE 8 月编程语言:C、Java 差距拉大,R 语言盛行
Flutter自定义View实战—仿高德三级联动Drawer效果!
如何设计一个超牛逼的本地缓存?
Flutter 1.20正式发布,新特性解读!
15年程序员经验分享:40个改变你编程技能的小技巧!
强大!滴滴在 GitHub 开源的项目盘点?
再见,Groovy! 教你如何使用Kotlin SDL 编写Gradle脚本!
Android 11 的甜点代号曝光!
强大!ASM插桩实现Android端无埋点性能监控!


更文不易,点个“在看”支持一下👇

本文分享自微信公众号 - 技术最TOP(Tech-Android)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。

相关文章
相关标签/搜索