目录

效果展示

实现原理
实现原理简单的讲就是先获取你想要实现动画的文字的路径,然后利用PathMeasure与Animator结合便可实现文字写出来的动画效果。当然这么说的话属实有些草率,接下来我就详细的讲一下控件的实现原理。
1. 文字路径的获取
文字路径的获取可以通过Paint的getTextPath方法来获取,但是这里需要注意的是Paint的Style需要设置为Paint.Style.STROKE因为如果是Paint.Style.FILL的话会使文字看起来很别扭。


2. PathMeasure+Animator实现动画
这里需要通过PathMeasure的构造方法或者通过PathMeasure的setPath方法将获取的文字路径赋给PathMeasure这样才能对路径进行截取。
PathMeasure(tempPath, false)//通过构造方法
PathMeasure().setPath(tempPath, false)//通过setPath方法
将文字路径赋给PathMeasure后我们便可利用Animator实现文字路径的动画了,这里我使用的是ValueAnimator让值从0升到1(也可以理解为百分百)。
mAnimation = ValueAnimator.ofFloat(0f, 1f)
...省略部分代码
mAnimation!!.addUpdateListener {
mCurrentProcess = it.animatedValue as Float
invalidate()
}
mAnimation!!.start()
然后根据动画的进度结合PathMeasure不断的截取出文字路径的对应长度并绘制出来。
mPathMeasure!!.getSegment(0F, mPathMeasure!!.length * mCurrentProcess, mDst, true)//获取路径片段(这里的mDst是用来存储截取出来的路径)
canvas!!.drawPath(mDst, mPaint)//绘制路径
不过这里需要注意的是PathMeasure通过getSegment方法截取路径只能取到当前所在封闭路径,而如果想要遍历所有的路径则需调用PathMeasure的nextContour方法直到nextContour方法返回的值为false即可,说到这里有些同学可能会有点迷,这里画图解释。

3. 文字的换行问题
由于使用自定义View绘制文字它不会主动的去换行因此只能通过我们自己计算来让文字换行。
这里我们使用Paint的getTextWidths来获取所有字符的宽度,然后通过循环叠加字符的宽度,每当字符的宽度总和大于控件的宽度时我们便进行换行,这里的换行则是存储下当前遍历到的字符的索引值,然后根据这个索引值对整串字符串进行截取来实现。

具体代码如下:
val textWidths = FloatArray(mText.length + 1)
mPaint.getTextWidths(mText, 0, mText.length, textWidths)
var tempTextWidthSum = 0f
var tempViewWidth = 0//控件的宽度
...省略部分代码
for (i in 0..(textWidths.size - 1)) {
tempTextWidthSum += textWidths[i]
if (tempTextWidthSum >tempViewWidth ) {
mTextLineCountList.add(i)//存储字符的索引值
tempTextWidthSum = textWidths[i]//因为当前已经比控件宽度大了所以在重新计算的时候需要将这个字符的宽度加进去(因为这已经是第二行了)
}
}
mTextLineCountList.add(mText.length)//将最后的字符索引加进去,不然会缺字
控件的使用
为了方便使用我加了几个自定义属性:
<declare-styleable name="PathAnimTextView">
<!--文字颜色-->
<attr name="animTextColor" format="color"/>
<!--文字大小-->
<attr name="animTextSize" format="float"/>
<!--文字画笔宽度-->
<attr name="animTextStrokWidth" format="float"/>
<!--动画文字-->
<attr name="animText" format="string"/>
<!--动画的速度-->
<attr name="animSpeed">
<!--快-->
<enum name="fast" value="0"/>
<!--慢-->
<enum name="slow" value="1"/>
<!--中速-->
<enum name="medium" value="2"/>
</attr>
<!--字体库的名称-->
<attr name="animTextTypeFace" format="string"/>
<!--文字行间距-->
<attr name="animTextLineMargen" format="float"/>
</declare-styleable>
布局中的使用:
<com.hehuidai.customview.one.animtextview.PathAnimTextView
android:id="@+id/patv"
app:animTextColor="#ff0000"
app:animTextStrokWidth="2"
app:animTextSize="80"
app:animSpeed="fast"
app:animText="Animation TextView"
app:animTextTypeFace="Muyao-Softbrush.ttf"//这里的字体文件需要放到assets文件中
app:animTextLineMargen="0"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
代码中开启动画:
patv.startTextAnim()
代码中设置文字:
patv.setText("Animation TextView")
网友评论