高级UI之PathMeasure学习

效果图:

PathMeasure中的方法:

返回值 方法名 释义
void setPath(Path path, boolean forceClosed) 关联一个Path
boolean isClosed() 是否闭合
float getLength() 获取Path的长度
boolean nextContour() 跳转到下一个轮廓
boolean getSegment(float startD, float stopD, Path dst, boolean startWithMoveTo) 截取片段
boolean getPosTan(float distance, float[] pos, float[] tan) 获取指定长度的位置坐标及该点切线值
boolean getMatrix(float distance, Matrix matrix, int flags) 获取指定长度的位置坐标及该点Matrix

一、实现上述效果主要通过getSegment方法不断截取片段然后重绘界面,让我们先学习getSegment的用法,getSegment方法的参数说明如下:

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

参数说明:

startD:开始的距离在(0-getLength())之间,startD必须小于stopD

stopD:结束距离在(0-getLength())之间

startWithMoveTo,是否移动的开始位置

代码如下:

package com.cool.pathanim;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
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;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;

/**
 * Created by cool on 2017/5/18.
 */

public class MagnifyingGlassView extends View {

    private Paint mPaint;
    private Path mCirclePath;
    private Path mHandPath;
    private Path mBigCirclePath;
    private Path mCirclePathDst;
    private Path mHandPathDst;
    private Path mBigCirclePathDst;

    private PathMeasure mPathMeasure;

    //view的宽高
    private int mWhidth;
    private int mHight;
    private int mRadius;

    //当前绘制进度占总长度的百分百
    private float mCirclePercent = 0;
    private float mHandPercent = 0;
    private float mBigCirclePercent = 0;
    private boolean isCircleAnimStart = false;
    private boolean isHandAnimStart = false;
    private boolean isBigCircleAnimStart = false;

    public MagnifyingGlassView(Context context) {
        this(context, null);
    }

    public MagnifyingGlassView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

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

    private void init() {
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setColor(Color.WHITE);
        mPaint.setStrokeWidth(5);
        mPaint.setStyle(Paint.Style.STROKE);

        mCirclePath = new Path();
        mCirclePathDst = new Path();
        mBigCirclePath = new Path();
        mHandPath = new Path();
        mHandPathDst = new Path();
        mBigCirclePathDst = new Path();

        mPathMeasure = new PathMeasure();

        startCircleValueAnim();
        setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                if(!isHandAnimStart && !isCircleAnimStart && !isBigCircleAnimStart)
                startCircleValueAnim();
            }
        });
    }



    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawColor(Color.parseColor("#87CEFA"));

        canvas.save();
        canvas.translate(mWhidth / 2, mHight / 2);
        canvas.rotate(45);
        mCirclePath.addCircle(0, 0, mRadius, Path.Direction.CW);
        mHandPath.moveTo(mRadius, 0);
        mHandPath.lineTo(mRadius + dp2px(getContext(),60), 0);
        mBigCirclePath.addCircle(0,0,mRadius + dp2px(getContext(),60), Path.Direction.CW);
        if(!isCircleAnimStart) {
            canvas.drawPath(mCirclePath, mPaint);
        }
        if(!isHandAnimStart){
            canvas.drawPath(mHandPath, mPaint);
        }

        mPathMeasure.setPath(mCirclePath, false);
        mCirclePathDst.reset();
        mPathMeasure.getSegment(mCirclePercent * mPathMeasure.getLength(), mPathMeasure.getLength(), mCirclePathDst, true);
        canvas.drawPath(mCirclePathDst, mPaint);
        if (mCirclePercent == 1.0) {
            mPathMeasure.nextContour();
            mPathMeasure.setPath(mHandPath, false);
            mHandPathDst.reset();
            mPathMeasure.getSegment(mHandPercent * mPathMeasure.getLength(), mPathMeasure.getLength(), mHandPathDst, true);
            canvas.drawPath(mHandPathDst, mPaint);
        }
        if(mHandPercent == 1){
            mPathMeasure.nextContour();
            mBigCirclePathDst.reset();
            mPathMeasure.setPath(mBigCirclePath,false);
            mPathMeasure.getSegment(mBigCirclePercent * mPathMeasure.getLength(),mBigCirclePercent * mPathMeasure.getLength() + 50,mBigCirclePathDst,true);
            canvas.drawPath(mBigCirclePathDst,mPaint);
        }
        canvas.restore();
    }


    private void startHandValueAnim() {
        ValueAnimator handPathValueAnimator = ValueAnimator.ofFloat(0, 1);
        handPathValueAnimator.setDuration(500);
        handPathValueAnimator.start();
        handPathValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mHandPercent = (float) animation.getAnimatedValue();
                invalidate();
            }
        });
        handPathValueAnimator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                startBigCircleValueAnim();
            }
        });
    }

    private void startBigCircleValueAnim(){
        ValueAnimator bigCircleValueAnim = ValueAnimator.ofFloat(0,1);
        bigCircleValueAnim.setDuration(1500);
        bigCircleValueAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mBigCirclePercent = (float) animation.getAnimatedValue();
                invalidate();
            }
        });
        bigCircleValueAnim.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                isBigCircleAnimStart = false;
                isHandAnimStart = false;
                isCircleAnimStart = false;
            }

            @Override
            public void onAnimationStart(Animator animation) {
                isBigCircleAnimStart = true;
            }
        });
        bigCircleValueAnim.start();
    }

    private void startCircleValueAnim() {
        ValueAnimator circleValueAnimator = ValueAnimator.ofFloat(0, 1);
        circleValueAnimator.setDuration(1000);
        circleValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mCirclePercent = (float) animation.getAnimatedValue();
                invalidate();
            }
        });
        circleValueAnimator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                startHandValueAnim();
                isHandAnimStart = true;
            }

            @Override
            public void onAnimationStart(Animator animation) {
                isCircleAnimStart = true;
            }
        });
        circleValueAnimator.start();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(measureSpec(widthMeasureSpec), measureSpec(heightMeasureSpec));
    }

    public int measureSpec(int measureSpec) {
        int expectSize;
        int mode = MeasureSpec.getMode(measureSpec);
        int size = MeasureSpec.getSize(measureSpec);

        if (mode == MeasureSpec.EXACTLY) {
            expectSize = size;
        } else {
            expectSize = dp2px(getContext(), 300f);
            if (mode == MeasureSpec.AT_MOST) {
                expectSize = Math.min(expectSize, size);
            }
        }

        return expectSize;
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        this.mWhidth = w;
        this.mHight = h;
        this.mRadius = mWhidth / 6;
    }

    public static int dp2px(Context context, float dpValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }
}

二、再上一张效果图


此动画是通过PathMeasure的getPosTan,下面说说getPosTan的使用

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

参数说明

distance:这是距离path起点的长度,取值区间为(0-getLength())

pos:对应distance在path的坐标数组,x=pos[0],y=pos[1]

tan:对应distance在path的正切值,x=tan[0],y=[tan[1],计算该点的弧度(注意,这里计算的是弧度哦,角度需要我们再转一次),可以这样计算角度Math.atan2(mTan[1], mTan[0])*180/Math.PI

下面是上面动画的实现源码:

package com.cool.pathanim;

import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
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 cool on 2017/5/26.
 */

public class PlanFlyView extends View {

    //view的宽高
    private int mWhidth;
    private int mHight;
    private int mRadius;

    private Paint mPaint;
    private Path mCirclePath;

    private PathMeasure mPathMeasure;
    private Bitmap mPlanBitmap;
    private float mPercent;
    private float[] mPos;
    private float[] mTan;
    private Matrix matrix;

    public PlanFlyView(Context context) {
        this(context,null);
    }

    public PlanFlyView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs,0);
    }

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

    private void init() {
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setColor(Color.WHITE);
        mPaint.setStrokeWidth(5);
        mPaint.setStyle(Paint.Style.STROKE);

        mPathMeasure = new PathMeasure();
        mCirclePath = new Path();
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inSampleSize = 2;
        mPlanBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.plan, options);

        mPos = new float[2];
        mTan = new float[2];
        matrix = new Matrix();
        setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                startAnim();
            }
        });
        startAnim();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.drawColor(Color.parseColor("#87CEFA"));

        mCirclePath.addCircle(mWhidth/2,mHight/2,mRadius, Path.Direction.CCW);
        canvas.drawPath(mCirclePath,mPaint);
        mPathMeasure.setPath(mCirclePath,false);
        mPathMeasure.getPosTan(mPathMeasure.getLength()*mPercent,mPos,mTan);

        double atan = Math.atan2(mTan[1], mTan[0]);//弧度制
        double angle = atan*180/Math.PI;
        matrix.reset();
        matrix.postRotate((float) angle + 90,mPlanBitmap.getWidth()/2,mPlanBitmap.getHeight()/2);
        matrix.postTranslate(mPos[0]-mPlanBitmap.getWidth()/2,mPos[1]-mPlanBitmap.getHeight()/2);
        canvas.drawBitmap(mPlanBitmap,matrix,mPaint);
    }

    private void startAnim() {
        ValueAnimator valueAnimator = ValueAnimator.ofFloat(0,1);
        valueAnimator.setDuration(5000);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mPercent = (float) animation.getAnimatedValue();
                invalidate();
            }
        });
        valueAnimator.start();
    }


    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(measureSpec(widthMeasureSpec), measureSpec(heightMeasureSpec));
    }

    public int measureSpec(int measureSpec) {
        int expectSize;
        int mode = MeasureSpec.getMode(measureSpec);
        int size = MeasureSpec.getSize(measureSpec);

        if (mode == MeasureSpec.EXACTLY) {
            expectSize = size;
        } else {
            expectSize = dp2px(getContext(), 300f);
            if (mode == MeasureSpec.AT_MOST) {
                expectSize = Math.min(expectSize, size);
            }
        }

        return expectSize;
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        this.mWhidth = w;
        this.mHight = h;
        this.mRadius = mWhidth / 4;
    }

    public static int dp2px(Context context, float dpValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }
}

三、最后还剩下getMatrix方法
public boolean getMatrix(float distance, Matrix matrix, int flags)
参数说明:
distance:这是距离path起点的长度,取值区间为(0-getLength())
matrix: 直接获取对应distance某点的矩阵,里面封装了位置的旋转角度和位置信息,我们可以直接使用
flags:有2个可以选择,分别是POSITION_MATRIX_FLAG、TANGENT_MATRIX_FLAG,看名字就知道什么意思了
到这里,第二个动画可以使用getMatrix方法来更加简单方便的来实现的

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值