本文属于Android技术论述文章,阅读完大致需要五分钟
原创文章,转载请注明出处。
没时间的小伙伴可以直接跳过文章,点击项目地址,如果喜欢的话,顺手给个star那是极好的【娇羞……】
二话不说,效果奉上!
知识点序列:
- Canvas绘制
- TextPaint
- Path
- PathMeasure【测量Path】
代码分析
获取文字的轮廓Path
在TextPaint
中有这样一个方法,getTextPath
,如下:
1 2 3 4 5 6 7 8
| public void getTextPath(String text, int start, int end, float x, float y, Path path) { if ((start | end | (end - start) | (text.length() - end)) < 0) { throw new IndexOutOfBoundsException(); } nGetTextPath(mNativePaint, mNativeTypeface, mBidiFlags, text, start, end, x, y, path.mutateNI()); }
|
👀其中nGetTextPath
里面是调用了一个原生函数,具体实现不必理会。只需要了解函数的各个参数就可以了。
- 参数含义:
- text:文本内容
- start:需要测量的文本中的第一个字符的下标
- end:需要测量的文本最后一个字符的下标加1
- x:Path起点的坐标X
- y:Path起点的坐标Y
- path:最终测量得到的Path
Path分段绘制
PathMeasure这个类可以说是Path相关的工具解析类,里面大多是原生函数,实现细节不必深究。只需要会使用方法即可。
第一步我们得到文字的轮廓Path之后,这一步将此path按段绘制出来即可。在代码中,由CanvasView
这个类来具体实现绘制的详尽流程。看代码,将文本轮廓Path设置进来:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| // 保留一份原始Path,用作绘制最终填色的文字 mOrignalPath = orignalPath; if (null == mPathMeasure) { mPathMeasure = new PathMeasure(); } //先重置一下需要显示动画的path mAnimPath.reset(); mAnimPath.moveTo(0, 0); mPathMeasure.setPath(orignalPath, false); // getLength()方法获得的是当前path的长度;而nextContour()方法是将Path切换到下一段Path,多应用在复杂path中 mTextCount = 0; // 计算文字总共有多少段Path while (mPathMeasure.nextContour()) { mTextCount++; } // PathMeasure重新设置一次 mPathMeasure.setPath(orignalPath, false); mPaint.setStyle(Paint.Style.STROKE);
|
- 其中成员变量mAnimPath,即是用在
onDraw()
方法内用于绘制的Path。我们会不停地通过PathMeasure
刷新这个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
| if (null == mValueAnimator) { // 如果一个文本Path包含n个小Path,那么属性动画会Repeat运行n次,每一段小path默认动画时间为900ms mValueAnimator = ValueAnimator.ofFloat(0.0f, 1.0f); mValueAnimator.setDuration(900); mValueAnimator.setInterpolator(new LinearInterpolator()); } // 引擎无限次重复发动 mValueAnimator.setRepeatCount(ValueAnimator.INFINITE); mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { float value = (float) animation.getAnimatedValue(); // 将一段小Path从0%到100%赋值到mAnimPath中,调用重绘 mPathMeasure.getSegment(0, mPathMeasure.getLength() * value, mAnimPath, true); invalidate(); } });
mValueAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationRepeat(Animator animation) { super.onAnimationRepeat(animation); //绘制完一条Path之后,再绘制下一条,直到完成为止。 if (!mPathMeasure.nextContour()) { animation.end(); } invalidate(); } });
|
- 注释里具体原因亦已写明。这种引擎的设置,就是每一小段Path不管长短,其绘制时间都是相等的,会造成动画看起来时慢时快。
- 其实要让动画一直匀速跑起来也很容易,就是提前将原始Path测量一遍,设置一个总时间,然后根据小Path的长短来按照比例分配时间。这样即可使动画匀速进行。具体代码不再多言😘,有兴趣的小伙伴可以试试。
再看看CanvasView
内的onDraw()
方法实现:
1 2 3 4 5 6 7 8 9 10 11
| @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (null != mPathMeasure && mPathMeasure.getLength() == 0) { mPaint.setStyle(Paint.Style.FILL); canvas.drawPath(mOrignalPath, mPaint); return; } canvas.drawPath(mAnimPath, mPaint); }
|
- 这里面的代码更加简单,在Path绘制完之前,一直绘制mAnimPath即可。Path绘制完之后,设置画笔的绘制风格,将文字空白处填上色即可。
以上。就是本次项目的主要思路解析。大家走过路过不要错过,给添个Star呗😄。