自定义控件神器--PathMeasure

我们平时自定义View的时候,经常会绘制path,想要求出path路径上的某个点的位置,很难通过一般的数学函数来计算,幸好系统给我们提供了一个PathMeasure可以用来测量一个path的路径,我们可以根据测量得到的值来绘制一些别的效果。

PathMeasure有两个构造方法

  public PathMeasure() {
        mPath = null;
        native_instance = native_create(0, false);
    }
  public PathMeasure(Path path, boolean forceClosed) {
        mPath = path;
        native_instance = native_create(path != null ? path.readOnlyNI() : 0,forceClosed);
    }

一个是有参数的,一个是没参数的

  • 有参数的构造方法中第一个很好理解就是需要测量的path,第二个参数的意思是是否计算path路径闭合的路径。如果是true,那么不管我们path是不是闭合了,它都会去计算闭合的路径。如果是false就不会计算。
  • 没有参数创建出来之后,可以通过 pathMeasure.setPath(mPath,false);方法把path和forceClosed这两个参数传进去

下面来看PathMeasure中的几个比较常用的方法

getSegment

给定一个起始点,一个结束点和一个空path对象,把给定的起点到终点的路径赋值给这个空的path。public boolean getSegment(float startD, float stopD, Path dst, boolean startWithMoveTo)。最后一个参数startWithMoveTo表示起点是否使用moveTo,用来保证截取的path第一个点的位置不变。从而保证截取的片段不会变形

它返回的是一个boolean值,如果截取到的路径长度是0就返回false,不是0就返回true。

下面来看一下使用getSegment的简单案例,在onDraw方法中

        mPath.reset();
        mFloat += 0.01;
        if (mFloat >= 1){
            mFloat = 0;
        }
        mPath.addCircle(getWidth()/2,getHeight()/2,200,Path.Direction.CW);
        mDst.reset();
        pathMeasure.setPath(mPath,false);
        float distance = pathMeasure.getLength() * mFloat;
        pathMeasure.getSegment(0, distance , mDst, true);
        canvas.drawPath(mDst, mPaint);
        invalidate();

使用一个0-1的成员变量mFloat来控制当前截取比例,每次调用invalidate的时候mFloat的值都会改变,这样就可以弄个简单的动画效果 效果如下
在这里插入图片描述

上面一直是从0来时截取如果我们改一下截取的位置

pathMeasure.getSegment(2*distance/3, distance, mDst, true);

第一个参数改成了2*distance/3,效果如下,类似一个加载动画的效果,这个参数可以随便改,更改的不同截取效果就不同

在这里插入图片描述

前面两个我们只绘制了我们截取的path,下面把原path也绘制上,截取的部分换一种颜色绘制,效果如下

在这里插入图片描述

getPosTan

getPosTan方法,可以获得path路径上某个点的位置和这个点的切线的值。public boolean getPosTan(float distance, float pos[], float tan[]) distance是当前需要截取的长度,pos是一个数组,如果不为空,可以给这个数组赋值,pos[0]是x坐标pos[1]是y坐标,tan跟也是个数组,tan[0],tan[1]代表切线的值,通过Math.atan2()方法传入tan[0],tan[1]可以获取到当前切线的角度。

下面看一个简单的例子

      mPath.reset();

        mFloat += 0.01;
        if (mFloat >= 1){
            mFloat = 0;
        }
       mPath.addCircle(getWidth()/2,getHeight()/2,200,Path.Direction.CW);
        pathMeasure.setPath(mPath,false);
       //用来记录位置
        float []pos = new float[2];
        //用来记录切点的位置
        float []tan = new float[2];
        float distance = pathMeasure.getLength() * mFloat;
        pathMeasure.getPosTan(distance,pos,tan);
        //计算出当前图片要旋转的角度
        float degree = (float) (Math.atan2(tan[1], tan[0]) * 180 / Math.PI);
        mMatrix.reset();
        //设置旋转角度和旋转中心
        mMatrix.postRotate(degree,mBitmap.getWidth() / 2,mBitmap.getHeight() / 2);
        //设置绘制的中心点与当前图片中心点重合
        mMatrix.postTranslate(pos[0]-mBitmap.getWidth() / 2,pos[1]-mBitmap.getHeight()/2);
        canvas.drawPath(mPath, mPaint);
        canvas.drawBitmap(mBitmap,mMatrix, mPaint);

效果如下:

在这里插入图片描述

通过getPosTan方法,获取到当前的角度,通过Matrix方法把一个图片旋转这个角度,然后绘制到圆圈上

除了使用getPosTan之外,还有一个稍微简单的方法可以获取到pos和tan的值,通过pathMeasure.getMatrix方法

public boolean getMatrix(float distance, Matrix matrix, int flags)

前两个值好理解,最后一个值flag,用来指定矩阵中需要返回那些参数,有两个值

public static final int POSITION_MATRIX_FLAG = 0x01;
public static final int TANGENT_MATRIX_FLAG = 0x02;

这两个值分别代表前面的pos和tan,例如下面的代码

       mPath.reset();
        mFloat += 0.01;
        if (mFloat >= 1){
            mFloat = 0;
        }
        //路径
        mPath.lineTo(0, 200);
        mPath.lineTo(300, 200);
        mPath.quadTo(450,100,600,200);
        mPath.lineTo(900, 200);
        pathMeasure.setPath(mPath,false);
        //将pos信息和tan信息保存在mMatrix中
        pathMeasure.getMatrix(pathMeasure.getLength() * mFloat, mMatrix,
                PathMeasure.POSITION_MATRIX_FLAG | PathMeasure.TANGENT_MATRIX_FLAG);
        //将图片的旋转坐标调整到图片中心位置
        mMatrix.preTranslate(-mBitmap.getWidth() / 2, -mBitmap.getHeight() / 2);
        canvas.drawPath(mPath, mPaint);
        canvas.drawBitmap(mBitmap,mMatrix, mPaint);

效果:

在这里插入图片描述

下面使用pathmeasure实现一个支付宝的支付动画

效果如下:

在这里插入图片描述
主要用到getSegment这个方法

public class PathMeasurePay extends View {
    private Paint mPaint = new Paint();
    private Path mPath = new Path();
    private Path mCircleDst = new Path();
    private Path mLineDst1 = new Path();
    private Path mLineDst2 = new Path();
    private Path mLineDst = new Path();
    private PathMeasure pathMeasure;
    private int mCircleRadius;
    /**
     *  圆的中心点
     */
    private float circleX,circleY;
    /**
     * 绘制类型 1是对号  2是叉号
     */
    private int mType;
    /**
     * 动画控制的圆取值范围0-1
     */
    private float mCircleValue;
    /**
     * 动画控制的直线取值范围0-1
     * mType==1的时候代表对号的线
     * mType==2的时候代表叉号的第一条线
     */
    private float mLineValue;
    /**
     * 动画控制的叉号第二条线的取值范围0-1
     */
    private float mLineValue2;
    /**
     * mType==1的时候代表是否绘制对号
     * mType==2的时候代表是否绘制叉号的第一条线
     */
    private boolean isDrawLine = false;
    /**
     * 是否绘制叉号的第二条线
     */
    private boolean isDrawLine2 = false;
    private ValueAnimator animatorCircle;
    private ValueAnimator animatorLine;
    private ValueAnimator animatorLine2;
    private int strockWidth = 6;
    public PathMeasurePay(Context context) {
        this(context,null);
    }

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

    public PathMeasurePay(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context,attrs);
    }
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        circleX = w/2f;
        circleY = h/2f;
        mCircleRadius = Math.min(w/2-strockWidth,h/2-strockWidth);
    }

    private void init(Context context,AttributeSet attrs) {
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.PathMeasurePay);
        int paintColor = a.getColor(R.styleable.PathMeasurePay_color, Color.BLACK);
        mType = a.getInt(R.styleable.PathMeasurePay_type,1);
        a.recycle();
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setColor(paintColor);
        mPaint.setStrokeWidth(strockWidth);

        pathMeasure = new PathMeasure();
        animatorCircle = ValueAnimator.ofFloat(0,1);
        animatorCircle.setDuration(1000);

        animatorLine = ValueAnimator.ofFloat(0,1);
        animatorLine.setDuration(mType==1?1000:500);

        animatorLine2 = ValueAnimator.ofFloat(0,1);
        animatorLine2.setDuration(500);

        animatorCircle.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                mCircleValue = (float) valueAnimator.getAnimatedValue();
                invalidate();
            }
        });
        animatorCircle.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                isDrawLine = true;
                animatorLine.start();
            }
        });

        animatorLine.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mLineValue = (float) animation.getAnimatedValue();
                invalidate();
            }
        });
        animatorLine.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                isDrawLine2 = true;
                if(mType == 2){
                    animatorLine2.start();
                }
            }
        });

        animatorLine2.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mLineValue2 = (float) animation.getAnimatedValue();
                invalidate();
            }
        });

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

            mPath.reset();
            mPath.addCircle(getWidth()/2f,getHeight()/2f,mCircleRadius,Path.Direction.CW);
            mCircleDst.reset();
            pathMeasure.setPath(mPath,false);
            float distance = pathMeasure.getLength() * mCircleValue;
            pathMeasure.getSegment(0, distance , mCircleDst, true);
            canvas.drawPath(mCircleDst, mPaint);

            if(mType == 1){
                if(isDrawLine){
                    mPath.reset();
                    mLineDst.reset();
                    mPath.moveTo(circleX-mCircleRadius/2f,circleY);
                    mPath.lineTo(circleX-mCircleRadius/10f,circleY+mCircleRadius/2f);
                    mPath.lineTo(circleX+mCircleRadius/2f,circleY-mCircleRadius/4f);
                    pathMeasure.setPath(mPath,false);
                    float dis = pathMeasure.getLength()*mLineValue;
                    pathMeasure.getSegment(0,dis,mLineDst,true);
                    canvas.drawPath(mLineDst,mPaint);
                }
            }else {
                if(isDrawLine){
                    mPath.reset();
                    mLineDst1.reset();
                    mPath.moveTo(circleX - mCircleRadius/2f,circleY - mCircleRadius/2f);
                    mPath.lineTo(circleX+mCircleRadius/2f,circleY + mCircleRadius/2f);
                    pathMeasure.setPath(mPath,false);
                    float dis = pathMeasure.getLength()*mLineValue;
                    pathMeasure.getSegment(0,dis,mLineDst1,true);
                    canvas.drawPath(mLineDst1,mPaint);
                }
                if(isDrawLine2){
                    mPath.reset();
                    mLineDst2.reset();
                    mPath.moveTo(circleX+mCircleRadius/2f,circleY - mCircleRadius/2f);
                    mPath.lineTo(circleX - mCircleRadius/2f,circleY +mCircleRadius/2f);
                    pathMeasure.setPath(mPath,false);
                    float dis = pathMeasure.getLength()*mLineValue2;
                    pathMeasure.getSegment(0,dis,mLineDst2,true);
                    canvas.drawPath(mLineDst2,mPaint);
                }
            }
    }

    public void reset(){
        isDrawLine = false;
        isDrawLine2 = false;
        animatorCircle.start();
    }
}

源码地址

参考:https://www.jianshu.com/p/3efa5341abcc

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值