Android-玩转PathMeasure

20171109151015906869351.gif

PathMeasure

Public constructors

  • PathMeasure 创建一个空的 pathmeasure 对象
  • PathMeasure(Path path,boolean forceClosed)创建一个带 path 参数的 PathMeasure,forceClosed控制 path 是否自动闭合

Public methods

  • getLength() 返回当前 Path 的总长度。
  • getMatrix(float distance, Matrix matrix, int flags)
  • getPosTan(float distance, float[] pos, float[] tan)获取distance长度的 point 值给 pos,point 点的正切值给 tan。
  • getSegment(float startD, float stopD, Path dst, boolean startWithMoveTo) 获取 path 的一个片段,即startD到 stopD 的线段,辅助给 dst。
  • isClosed() 是否自动闭合
  • nextContour() 移动到下一条曲线。如果 path 中含有不连续的线条,getLength、getPosTan等方法之会在第一条线上运行,需要使用这个方法跳到第二条线
  • setPath(Path path, boolean forceClosed)

是不是很简单,就这么几个方法,现在去画光能使者阵有思路了么~~
接下来为了便于大家理解,我们再来简单回顾一下 path 的 api,因为静态的光能使者阵是需要 path 去绘制的。

Path

方法名 作用
moveTo 移动到指定点
setLastPoint 重新设置当前Path的最后一个点,如果当前Path为空,则等同上个方法
lineTo 添加当前点到一个指定点的直线
close 连接当前点到起点,形成闭合路径
addRect、addRoundRect、addOval、addCircle、addPath、addArc、arcTo 添加各种图形
isEmpty 是否为空
isRect 是否为矩形
set 用一个新的path替换
offset 对当前的路径进行偏移,不会影响后续操作
quadTo、cubicTo 贝塞尔曲线
rMoveTo、rLineTo、rQuaTo、rCubicTo 带r的是基于当前点的偏移量,不带r基于坐标原点
setFillType、getFillType 设置填充模式
transform 矩阵变换

动画拆解

好了,准备工作完成,我们开始撸代码

第一步,绘制静态的光能使者阵
首先绘制两个圆,然后就是中间的六角星(其实仔细看就是两个三角形)。
都是很简单的方法,同学们动手去画的时候可能会遇到一个这样的问题,就是三角形的三个点不好取。其实很简单,直接在圆上取0,1/3,2/3长度的点即可,刚刚我们不是说了 PathMeasure 的方法么,用getPosTan就可以实现。

第二步,让光能使者阵动起来
这里我们把这个动画效果分成三个阶段吧。

第一阶段,绘制两个圆

20171109151015971189141.gif

如上图所示,这里两个圆是慢慢绘制出来的, 圆的 path 很容易绘制出来,这里我就不讲了,然后PathMeasure的getLength可以获取 path 总的长度,getSegment可以获取某个点到某个点的 Path。因此一个 ValueAnimator 就可以解决从0到100%长度的过程,具体实现看后面的代码。

然后问题来了,path画出来的圆的起点在哪里?怎么控制两个圆开始绘制的角度不一样。有同学可能想到了旋转90°再画第二个圆,当然这种方式是可以实现的,但是由于后面的三角形也需要旋转,这里我们就不用 path 画圆了,用 path 添加一个正方形 Rect 的圆弧也是一个圆,然后我们的圆弧可以控制开始的角度,弧度。
然后变成这样了

20171109151015974099352.gif

WTF?角度怎么不对了,我明明设置的开始角度的呀

1
2
innerCircle.addArc(innerRect, 150, -360);
outerCircle.addArc(outerRect, 60, -360);

最后有个大牛说你的圆变成闭环了,PathMeasure 找不到开始点,用了默认的。你把360度改成359.9让他不是一个闭环的圆就行了。

第二阶段,两个点在圆里面弹射

20171109151015979584476.gif

看起来好像还要干什么碰撞反弹之类的事,一副高科技的样子,其实不是的。

轨迹就是两个三角形,怎么让两条线跟着三角形走呢,而且走的时候还要不段变化长度。

刚刚第一步我们用 ValueAnimator 来控制一个圆从0到100%的过程,

pathMeasure.getSegment(0, distance * pathMeasure.getLength(), drawPath, true);

不断截取起点到*%长度的 path 赋值给drawPath。

从里是从起点开始截取,那么我们不从起点开始截取,从当前点附近开始截取不就行了吗,哈哈哈哈~so easy

1
2
3
float stopD = distance * pathMeasure.getLength();
float startD = stopD - (0.5f - Math.abs(0.5f - distance)) * 200;
pathMeasure.getSegment(startD, stopD, drawPath, true);

第三阶段绘制两个三角形

20171109151015984291399.gif

其实两个三角形就是第二步的运动轨迹,也是就是说直接用第阶段的 Path 即可,然后再用第一阶段一样的办法就可以实现效果。

代码实现

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
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
public class GranzortView extends View {
private Paint paint;
private Path innerCircle;//内圆 path
private Path outerCircle;//外圆 path
private Path trangle1; //第一个三角形的path
private Path trangle2; //第二个三角形的path
private Path drawPath; //用于截取路径的path
private PathMeasure pathMeasure;
private float mViewWidth;
private float mViewHeight;
private long duration = 3000;
private ValueAnimator valueAnimator;
private Handler mHandler;
private float distance;//当前动画执行的百分比取值为0~1
private ValueAnimator.AnimatorUpdateListener animatorUpdateListener;
private Animator.AnimatorListener animatorListener;
private State mCurrentState = State.CIRCLE_STATE;
public GranzortView(Context context) {
this(context,null);
}
public GranzortView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs,0);
}
public GranzortView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
//三个阶段的枚举
private enum State {
CIRCLE_STATE,
TRANGLE_STATE,
FINISH_STATE
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mViewWidth = w;
mViewHeight = h;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(getResources().getColor(R.color.colorPrimary));
canvas.save();
canvas.translate(mViewWidth / 2, mViewHeight / 2);
switch (mCurrentState){
case CIRCLE_STATE:
drawPath.reset();
pathMeasure.setPath(innerCircle,false);
pathMeasure.getSegment(0,distance * pathMeasure.getLength(),drawPath,true);
canvas.drawPath(drawPath,paint);
pathMeasure.setPath(outerCircle,false);
drawPath.reset();
pathMeasure.getSegment(0,distance * pathMeasure.getLength(),drawPath,true);
canvas.drawPath(drawPath,paint);
break;
case TRANGLE_STATE:
canvas.drawPath(innerCircle,paint);
canvas.drawPath(outerCircle,paint);
drawPath.reset();
pathMeasure.setPath(trangle1,false);
float stopD = distance * pathMeasure.getLength();
float startD = stopD - (0.5f - Math.abs(0.5f - distance)) * 200;
pathMeasure.getSegment(startD,stopD,drawPath,true);
canvas.drawPath(drawPath,paint);
drawPath.reset();
pathMeasure.setPath(trangle2,false);
pathMeasure.getSegment(startD,stopD,drawPath,true);
canvas.drawPath(drawPath,paint);
break;
case FINISH_STATE:
canvas.drawPath(innerCircle,paint);
canvas.drawPath(outerCircle,paint);
drawPath.reset();
pathMeasure.setPath(trangle1,false);
pathMeasure.getSegment(0, distance * pathMeasure.getLength(),drawPath,true);
canvas.drawPath(drawPath,paint);
drawPath.reset();
pathMeasure.setPath(trangle2,false);
pathMeasure.getSegment(0,distance*pathMeasure.getLength(),drawPath,true);
canvas.drawPath(drawPath,paint);
break;
}
canvas.restore();
}
private void init(){
initPaint();
initPath();
initHandler();
initAnimatorListener();
initAnimator();
mCurrentState = State.CIRCLE_STATE;
valueAnimator.start();
}
private void initHandler(){
mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
switch (mCurrentState){
case CIRCLE_STATE:
mCurrentState = State.TRANGLE_STATE;
valueAnimator.start();
break;
case TRANGLE_STATE:
mCurrentState = State.FINISH_STATE;
valueAnimator.start();
break;
}
}
};
}
private void initAnimatorListener(){
animatorUpdateListener = new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
distance = animation.getAnimatedValue();
invalidate();
}
};
animatorListener = new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
Log.e("start",mCurrentState + "_");
}
@Override
public void onAnimationEnd(Animator animation) {
Log.e("end",mCurrentState+"_");
mHandler.sendEmptyMessage(0);
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
}
}
private void initAnimator(){
valueAnimator = ValueAnimator.ofFloat(0,1).setDuration(duration);
valueAnimator.addUpdateListener(animatorUpdateListener);
valueAnimator.addListener(animatorListener);
}
private void initPath(){
innerCircle = new Path();
outerCircle = new Path();
trangle1 = new Path();
trangle2 = new Path();
drawPath = new Path();
pathMeasure = new PathMeasure();
RectF innerRect = new RectF(-220,-220,220,220);
RectF outerRect = new RectF(-280,-280,280,280);
innerCircle.addArc(innerRect,150,-359.9f);//不能取360f,否则可能造成测量到的值不准确
outerCircle.addArc(outerRect,60,-359.9f);
pathMeasure.setPath(innerCircle,false);
float[] pos = new float[2];
pathMeasure.getPosTan(0,pos,null);//获取开始位置的坐标
trangle1.moveTo(pos[0],pos[1]);
pathMeasure.getPosTan((1f / 3f) * pathMeasure.getLength(),pos,null);
trangle1.lineTo(pos[0],pos[1]);
pathMeasure.getPosTan((2f / 3f)*pathMeasure.getLength(),pos,null);
trangle1.lineTo(pos[0],pos[1]);
trangle1.close();
pathMeasure.getPosTan((2f / 3f) * pathMeasure.getLength(),pos,null);
Matrix matrix = new Matrix();
matrix.postRotate(-180);
trangle1.transform(matrix,trangle2);
}
private void initPaint(){
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColor(Color.WHITE);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(10);
paint.setStrokeCap(Paint.Cap.ROUND);
paint.setStrokeJoin(Paint.Join.BEVEL);
paint.setShadowLayer(15,0,0,Color.WHITE);//白色光影效果
}
}