PathMeasure 轨迹动画神器

PathMeasure 轨迹动画神器

轨迹动画一般利用SVG来实现,或者使用属性动画,自定义估计值,根据两点之间的线性关系式计算坐标(复杂)
但是使用PathMeasure来进行绘制轨迹动画,so easy。

先看效果:
这里写图片描述
效果分析:
1、圆圈变成圆弧
2、圆弧不断的变小

实现
方式1:通过不断改变绘制圆弧的开始角度。 这个方法肯定是最先想到的方法,
因为api
drawArc(@NonNull RectF oval, float startAngle, float sweepAngle, boolean useCenter,
@NonNull Paint paint)用的最多

方式2:利用PathMeasure,不断截取路径。截取不断变小的圆弧,然后把截取到的path绘制出来就可以。

比较:两种方式相比PathMeasure并没有多大的优势,但是绘制圆弧可以使用 drawArc 来改变角度实现类似轨迹运动的效果,但是,如果path、不是一个圆形,问题就复杂了,PathMeasure的强大也就体现出来了。


学习PathMeasure的使用

查看源码,发现只有100多行,其实也就几个方法。但是确实非常流弊
1、setPath(Path path, boolean forceClosed)
要对path进行操作,首先需要设置一个path,也可以在构造函数中绑定,关键在第二个参数,是否关闭,如果是true,那么会给参数path,强制首尾闭合,因为path可能是一个非闭合的路径。

2、getLength
得到path路径的长度,很有用。

3、getPosTan(float distance, float pos[], float tan[])
得到distance,位置的点的坐标,和在个点与path进行切线的正切,分别放在pos[] 和tan[]中。
例如,distance==length/2,就是得到这条path路径的中间点,的坐标和正切

4、getMatrix(float distance, Matrix matrix, int flags)
这个就厉害了,直接返回得到矩阵,这个矩阵就包含了这个点移动的位置,和倾斜的角度,可以直接在这个点绘制
一个图片,利用这个matrix

5、getSegment(float startD, float stopD, Path dst, boolean startWithMoveTo)
从开始的距离,到结束的距离,截取一段path放在dst中,
startWithMoveTo 如果为true,这个dst会从截取点开始
如果为false,这个dst会从(0,0)作为开始点

使用PathMeasure来进行分析

这里写图片描述

接着圆弧动画完了,就是手柄的动画。把属性动画的0到1拆分,0到0.8,完成画的轨迹动画,0.8到1完成手柄的动画,手柄就是一条直线,再使用PathMeasure有点大炮打蚊子,直接不断改变手柄直线的结束点就可以了。

下面是具体的代码实现:

package com.lmj.searchview;

import android.animation.Animator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;

/**
 * Created by limengjie
 * on 2017/5/25.14:37
 */

public class SearchView extends View {
    private Matrix mMatrix;
    private Paint mPaint;
    private Path mPath;
    private int mCircleX;
    private int mCircleY;
    private int mRadius = 50;//圆的半径
    private PathMeasure mPathMeasure;
    private float circleLength;
    private float startD;
    private float endD;
    private ValueAnimator mvalueAnimator_rotate;
    private ValueAnimator mvalueAnimator_path;
    private String Tag = "SearchView";
    private Path dstCircle;
    private int lineD = 0;
    private static final int Status_Normal = 1;//正常状态
    private static final int Status_PathAni = 2;//正在进行轨迹动画
    private static final int Status_RotateAni = 3;//正在旋转动画
    private int Status = Status_Normal;
    private int rotateNum;

    public SearchView(Context context) {
        this(context, null);
        init();
    }

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


    private void init() {
        mMatrix = new Matrix();
        mPaint = new Paint();
        mPath = new Path();
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setColor(Color.GREEN);
        mPaint.setStrokeWidth(2);
        mPaint.setAntiAlias(true);
        mPathMeasure = new PathMeasure();

        dstCircle = new Path();

    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        mPath.reset();
        mCircleX = getWidth() / 2;
        mCircleY = getHeight() / 2;
        switch (Status) {
            case Status_Normal:
                drawNormal(canvas);
                break;
            case Status_PathAni:
                drawPathAni(canvas);
                break;
            case Status_RotateAni:
                drawRotateAni(canvas);
                break;
        }

    }



    private void drawNormal(Canvas canvas) {
        mPaint.setColor(Color.GREEN);
        mPath.addCircle(mCircleX, mCircleY, mRadius, Path.Direction.CW);
        canvas.save();
        canvas.drawPath(mPath, mPaint);
        canvas.rotate(45, mCircleX, mCircleY);
        canvas.drawLine(mCircleX + mRadius, mCircleY, mCircleX + mRadius * 2, mCircleY, mPaint);
        canvas.restore();
    }

    private void drawPathAni(Canvas canvas) {
        mPath.addCircle(mCircleX, mCircleY, mRadius, Path.Direction.CW);
        canvas.save();
        canvas.rotate(45, mCircleX, mCircleY);
        mPathMeasure.setPath(mPath, false);
        circleLength = mPathMeasure.getLength();
        endD = circleLength;
        dstCircle.reset();
        Log.i(Tag, "sd:" + startD + ",ed:" + endD);
        mPathMeasure.getSegment(startD, endD, dstCircle, true);
//        mPaint.setColor(Color.RED);
        canvas.drawPath(dstCircle, mPaint);//圆的动画

        canvas.drawLine(mCircleX + mRadius, mCircleY, mCircleX + mRadius * 2 - lineD, mCircleY, mPaint);
        canvas.restore();

    }
    private void drawRotateAni(Canvas canvas) {
        mPath.addCircle(mCircleX, mCircleY, mRadius, Path.Direction.CW);
        canvas.save();
        canvas.rotate(45+rotateNum, mCircleX, mCircleY);
        mPathMeasure.setPath(mPath, false);
        circleLength = mPathMeasure.getLength();
        dstCircle.reset();
        mPathMeasure.getSegment(0, 20, dstCircle, true);
//        mPaint.setColor(Color.RED);
        canvas.drawPath(dstCircle, mPaint);//圆的动画
    }

    private void startPathAni() {
//        if(null==mvalueAnimator){
        if (null != mvalueAnimator_path) {
            mvalueAnimator_path.cancel();
        }
        lineD = 0;
        mvalueAnimator_path = ValueAnimator.ofFloat(0, 1);
        mvalueAnimator_path.setDuration(1500);
        mvalueAnimator_path.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                //0-0.8,分割圆,0.8到1 分割手柄
                float fraction = animation.getAnimatedFraction();
                if (fraction <= 0.8f) {
                    startD = circleLength * fraction / 0.7f;

                } else {
                    startD = circleLength;

                    lineD = (int) ((fraction - 0.8) * mRadius / 0.2f);
                }
                if(fraction==1f){
                    lineD = mRadius;
                    Status = Status_RotateAni;
                    postInvalidate();
                    startRotateAni();
                }
                postInvalidate();
            }


        });

        mvalueAnimator_path.start();
//        }
    }
    public void startRotateAni(){
        if (null != mvalueAnimator_rotate) {
            mvalueAnimator_rotate.cancel();
        }
        mvalueAnimator_rotate = ValueAnimator.ofInt(0, 360,0);
        mvalueAnimator_rotate.setDuration(3000);
        mvalueAnimator_rotate.setRepeatMode(ValueAnimator.RESTART);
        mvalueAnimator_rotate.setRepeatCount(-1);
        mvalueAnimator_rotate.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                rotateNum = (int) animation.getAnimatedValue();
                postInvalidate();
            }
        });

        mvalueAnimator_rotate.start();
    }

    public void startSearch() {
        Status = Status_PathAni;
        startPathAni();
        postInvalidate();
    }

    public void stopSearch() {
        Status = Status_Normal;
        if (null != mvalueAnimator_rotate) {
            mvalueAnimator_rotate.cancel();
        }
        if (null != mvalueAnimator_path) {
            mvalueAnimator_path.cancel();
        }
        postInvalidate();
    }

}       

总结,
只要我们有一个已知的path,就可以通过PathMeasure,来截取轨迹,根据距离来获取某个点的坐标和正切,所以有了path,轨迹动画就so easy了。

源码下载

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值