小动画之支付 (PathMeasure)

来吧展示


1. PathMeasure 是什么?

  1. 它类似一个计算器,可以计算出指定路径的一些信息,比如路径总长、指定长度所对应的坐标点等;

  2. 初始化方法;

    //方法一
    PathMeasure pathMeasure = new PathMeasure() ;
    setPath (Path path , boolean forceClosed	);
    //方法二
    PathMeasure(Path path , boolean forceClosed;
    

    其中 forceClosed 表示,待计算的 path 长度,是否需要让其闭合计算。


2. PathMeasure 的常用函数

2.1 get length() 函数;

  1. 声明:public float getLength ();
  2. 其作用就是获取计算的路径长度

2.2 getSegment ()函数

  1. 声明boolean getSegrneηt ( float startD, float stopD , Path dst , boolean startWithMoveTo )
  2. 其作用是用于截取整个Path 中的某个片段,将截取后的 Path 保存( 添加 )到参数dst 中;
  3. 参数 startD 表示 开始截取位置距离 Path 起始点 的长度;
    参数 stopD 表示 结束截取位置距离 Path 起始点 的长度;
    参数 startWithMoveTo 表示 起始点是否使用 To 将路径的新起始点移到结果 Path 的起始点,通常设置为 true;

2.3 getPosTan ()函数

  1. 声明:boolean getPosTan(float distance , float[] pos , float[) tan);
  2. 其作用是 得到路径上某一长度的位置以及该位置的正切;
  3. 参数 distance 表示 距离 Path 起始点的长度;
    参数 pos 表示 该点的坐标;pos[0 ]表示 x 坐标, pos[1]表示 y 坐标。
    参数 该点的正切 表示 该点的正切值;
  4. 注意:getPosTan () 函数中获取的正切值也是一个二维数组;这个数组标识的点是,以 1 为半径的圆上的点,通过反正切获取其对应的角度;
  5. 在Math 类中,有两个求反正切值的函数。
    double atan (double d);
    double atan2 (double y, double x);
    
    注意此处的参数 d 是 弧度值

2.4 getMatrix ()函数

  1. 声明:boolean getMatrix(float d工stance , Matrix matrix , int flags);

  2. 其作用是:用于得到路径上某一长度的位置以及该位置的正切值的矩阵;

  3. 参数 distance 表示 距离Path 起始点的长度。
    参数 matrix 表示 根据 flags 封装好的 matrix 会根据 flags 的设置而存入不同的内容。
    参数 flags 表示 用于指定哪些内容会存入 matrix 中。flags 的值有两个:
    pathMeasure.POSITION_MATRIX_FLAG 表示获取位置信息;
    pathMeasure. TANGENT_MATRIX_FLAG 表示获取切边信息,使得图片按Path 旋转。可以只指定一个,也可以使用" I '’(或运算符〉
    同时指定。

  4. getMatrix() 函数是 PathMeasure.getPosTan() 函数的另一种实现;


3. 利用 PathMeasure 实现路径动画 - 小示例

在这里插入图片描述

  1. 已知:,通过 getSegment() 函数可以根据路径的长度截取对应的路径线段。所以只需不断地给 getSegment() 函数设置逐渐增长的路径长度,就会相应得到逐渐增长的路径线段,把这个路径线段实时地画出来就可以了。
  2. 当箭头围绕圆形旋转时,应该实时地旋转箭头的转向;偏转角度即所在点的切线;具体角度根据图片的位置确定,这里的箭头本来就是向下的倒三角形,起始位置在右侧水平位置,其初始角度应为 -90°;

3.1 自定义一个 View

  1. 代码如下:

    public class GetSegmentView extends View {
    
        private Paint mPaint;
        private Path mCirclePath;
        private Path mDstPath;
        private PathMeasure mPathMeasure;
        private Float mCurAnimValue;
        private float mStop;
        private Bitmap mBitmap;
        private int mRadius = 60;
        private int mCentX = 70;
        private int mCentY = 70;
    
        private float[] pos = new float[2];
        private float[] tan = new float[2];
    
        public GetSegmentView(Context context, @Nullable AttributeSet attrs) {
            super(context, attrs);
            //禁用硬件加速
            setLayerType(LAYER_TYPE_SOFTWARE, null);
            mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.down_arrow_popup_item);
    
            mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
            mPaint.setStyle(Paint.Style.STROKE);
            mPaint.setColor(Color.WHITE);
            mPaint.setStrokeWidth(4);
    
            mCirclePath = new Path();
            mDstPath = new Path();
            mCirclePath.addCircle(mCentX, mCentY, mRadius, Path.Direction.CW);
    
            mPathMeasure = new PathMeasure(mCirclePath, true);
    
            ValueAnimator valueAnimator = ValueAnimator.ofFloat(0f, 1f);
            valueAnimator.setRepeatCount(ValueAnimator.INFINITE);
            valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    mCurAnimValue = (Float) animation.getAnimatedValue();
                    invalidate();
                }
            });
            valueAnimator.setDuration(2000);
            valueAnimator.start();
        }
    
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
        }
    }
    
  2. 重写 onDraw() 方法

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        
        //画出加载圆动画
        mStop = mPathMeasure.getLength() * mCurAnimValue;
        mDstPath.reset();
        mPathMeasure.getSegment(0f, mStop, mDstPath, true);
        canvas.drawPath(mDstPath, mPaint);
        
        //用 getPosTan() 获取图像旋转角度,画出加载动画的“箭头”
        mPathMeasure.getPosTan(mStop, pos, tan);
        float degree = (float) (Math.atan2(tan[1], tan[0]) * 180 / Math.PI) - 90.0f;
        Matrix matrix = new Matrix();
        matrix.postRotate(degree, mBitmap.getWidth() / 2, mBitmap.getHeight() / 2);
        matrix.postTranslate(pos[0] - mBitmap.getWidth() / 2, pos[1] - mBitmap.getHeight() / 2);
        canvas.drawBitmap(mBitmap, matrix, mPaint);
        
        //用 getMatrix() 获取图像旋转角度,画出加载动画的“箭头”
        //Matrix matrix2 = new Matrix();
        //mPathMeasure.getMatrix(mStop, matrix2, PathMeasure.POSITION_MATRIX_FLAG |
        //        PathMeasure.TANGENT_MATRIX_FLAG);
        //matrix2.preTranslate(-mBitmap.getWidth() / 2, -mBitmap.getHeight() / 2);
        //canvas.drawBitmap(mBitmap, matrix, mPaint);
    }
    
  3. 在布局文件中引用。

4. 支付动画分析

来吧展示

  1. 圆形和对钩是两条完全相连的路径;在 Path 变量中添加圆形、对勾两条路径;
  2. 在画完圆形以后, 需要利用 PathMeasure. nextContour() 函数将 Path 转到对钩路径继续。

4.1 自定义 View 构造代码

  1. 代码

    public class PayView extends View {
    
        private Paint mPaint;
        private Path mCirclePath;
        private Path mDstPath;
        private PathMeasure mPathMeasure;
        private Float mCurAnimValue;
        private float mStop;
        private int mRadius = 60;
        private int mCentX = 70;
        private int mCentY = 70;
        private static final String TAG = PayView.class.getClass().getSimpleName();
    
        public PayView(Context context, @Nullable AttributeSet attrs) {
            super(context, attrs);
            //禁用硬件加速
            setLayerType(LAYER_TYPE_SOFTWARE, null);
    
            mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
            mPaint.setStyle(Paint.Style.STROKE);
            mPaint.setColor(Color.WHITE);
            mPaint.setStrokeWidth(4);
    
            mCirclePath = new Path();
            mDstPath = new Path();
            mCirclePath.addCircle(mCentX, mCentY, mRadius, Path.Direction.CW);
    		//添加对勾的 Path
            mCirclePath.moveTo(mCentX - mRadius / 2, mCentY);
            mCirclePath.lineTo(mCentX, mCentY + mRadius / 2);
            mCirclePath.lineTo(mCentX + mRadius / 2, mCentY - mRadius / 3);
    
            mPathMeasure = new PathMeasure(mCirclePath, false);
    		//设置为 0 - 2 ;当动画在 0 - 1 时画圆;1 - 2 时画对勾
    		//注意:此处设置动画循环次数是失效的,(不要问我,我也不知道为啥。)
            ValueAnimator valueAnimator = ValueAnimator.ofFloat(0f, 2f);
            valueAnimator.setDuration(3000);
            valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    mCurAnimValue = (Float) animation.getAnimatedValue();
                    invalidate();
                }
            });
            valueAnimator.start();
        }	
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
        }
    }
    

4.2 重写 onDraw() 方法

  1. 代码
    //这里默认他已经绘制完圆形
    private boolean isCircleDrawed = true;
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (mCurAnimValue < 1) {
            //画出加载 圆 动画
            mStop = mPathMeasure.getLength() * mCurAnimValue;
            mPathMeasure.getSegment(0f, mStop, mDstPath, true);
            //Log.i(TAG, " mCurAnimValue = " + mCurAnimValue + " ; onDraw : mStop =  " + mStop);
        } else {
            //这里原本判断的是 mCurAnimValue == 1 时,和 mCurAnimValue > 1 时,但是判断中无法进入 mCurAnimValue == 1;
            if (isCircleDrawed) {
                //画完圆圈、并移动到下一个动画 Path;
                mPathMeasure.getSegment(0f, mPathMeasure.getLength(), mDstPath, true);
                mPathMeasure.nextContour();
                //Log.i(TAG, " mCurAnimValue = " + mCurAnimValue + " ; onDraw:mStop =  " + mStop);
                isCircleDrawed = false;
            }
            //画出圆内对勾
            mStop = mPathMeasure.getLength() * (mCurAnimValue - 1);
            mPathMeasure.getSegment(0f, mStop, mDstPath, true);
            //Log.i(TAG, " mCurAnimValue = " + mCurAnimValue + " ; onDraw:mStop =  " + mStop);
        }
        canvas.drawPath(mDstPath, mPaint);
    }
    

4.3 注意

  1. 动画进行中,返回 Float 类型的动画进度,此处本来比较的时进度 == 1 时,将圆画完,之后画对勾;
  2. 但在实际过程中,动画进度并没有完全 == 1 ,
  3. 当涉及到 float 类型的逐渐递增,最好不要使用 float f == int i 这种比较操作;因为基本上,f = 0.99450225 和 1.0094247 时,是无法进入的。

声明:本文整理自《《Android自定义控件开发入门与实战》_启舰》;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

liusaisaiV1

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值