自定义控件神器–PathMeasure
我们平时自定义View的时候,经常会绘制path,想要求出path路径上的某个点的位置,很难通过一般的数学函数来计算,幸好系统给我们提供了一个PathMeasure可以用来测量一个path的路径,我们可以根据测量得到的值来绘制一些别的效果。
PathMeasure有两个构造方法
1 2 3 4 5 6 7 8
| 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方法中
1 2 3 4 5 6 7 8 9 10 11 12
| 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来时截取如果我们改一下截取的位置
1
| 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]可以获取到当前切线的角度。
下面看一个简单的例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| 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,例如下面的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| 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); 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这个方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181
| 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;
private int mType;
private float mCircleValue;
private float mLineValue;
private float mLineValue2;
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