背景
最近在开发中遇到这样一个需求:实现一个类似于直播间双击屏幕点赞时右下角出现的动画,举个例子:
这是某直播App中一段录屏,在屏幕上双击的时候,除了按下的位置会有一个动画效果外,每次点赞时右下角都有一个小小的图片左右摇摆向上飘动最后消失。本文就是来仿照他实现一个轨迹动画。
分析
首先分析一下这个动画应该如何来做。将这个动画按特征拆分一下,很容易得到以下几个部分:
- 始终沿着某条轨迹运动的路径动画
- 在运动过程中伴随着大小和透明度的属性动画
- 偏转角度也在以一个特殊的规则不断变化
首先,可以通过自定义View实现,动态的将不同的图片绘制在界面上,再加动画。
第一点,沿着轨迹运动。最简单的情况,可以让图片沿着y轴做垂直向上的运动,同时不断改变x坐标,做左右平移。这样可以简单的实现浮动效果,但是轨迹路径是比较模糊的,不太灵活。所以最好的方案还是根据需要画一条曲线,让图片沿着曲线移动,比如用贝塞尔曲线,绘制好路径 Path
,用路径的 length
构建一个 ValueAnimator
,再借助 PathMeasure
,获取每一时刻在曲线上的位置,再执行 Matrix
的 postTranslate
方法来移动。
第二点,在运动的过程中,我们可以监听动画运动的时间或者进度,来附加一个属性动画,用 Matrix
的 postScale
改变图片的大小,改变画笔 Paint
的透明度 alpha
来改变透明度,不过最好是在 postTranslate
之前先执行,这样便于获取图片缩放、旋转时的中心点。
第三点,偏转角度的变化的实现很简单,但是规则比较复杂。可以是完全不动的,也可以始终沿着路径的方向,或是不断左右摇摆,不断旋转等。完全不动很简单,不用做任何处理,固定角度、不断旋转和左右摇摆都可以通过给定一个固定偏转角度或一个 ValueAnimator
来计算,而想要始终沿着路径的方向,就需要借助 PathMeasure
和三角函数结合来计算了。
实战
首先以自定义View的形式,创建一个 AnimView
,在 onDraw()
方法中绘制图片:
1 |
|
接下来只需实现动画并在监听中不断绘制即可。
路径动画
首先需要用 Path
绘制需要的路径,这里用贝塞尔曲线来实现:
1 |
|
这里绘制了4个数据点,3个控制点,都是均分整个View的,效果如下:
图中我用绿色标记了控制点,蓝色标记了数据点,就形成了一条很自然的曲线。
路径绘制好了,接下来就是创建一个 ValueAnimator
:
1 |
|
这里最关键的方法是 getPosTan()
:
1 |
|
简而言之,我们在 UpdateListener
中监听到当前时刻路径长度,通过这个方法,传入长度,就可以计算得到两个值,一个是当前点的坐标 pos[]
,一个是该点的正切值 tan[]
。正切值的用途后面再说,这里已经获取了当前点的坐标 x
和 y
,那么直接绘制出当前图片即可:
1 | val matrix = Matrix() |
x
和 y
是前面获取的当前坐标位置, x-center.x
和 y-center.y
就可以获取到从原图位置到当前点位置,在横纵坐标上的偏移量,这样再用 Matrix
的 postTranslate()
方法即可完成运动效果:
大小、透明度的变化
大小、透明度等属性的变化就比较简单了,具体看需求是以什么样的规则。前面在 UpdateListener
的监听中已经获取到了当前已经运动了多远,也可以获取当前已经运动了多久,这两点都可以根据需要转化为动画执行的进度,我们可以以这个进度为依据,做属性的变化,比如随便写一个动画总的执行时间是3000ms,在执行到第2600ms时开始变小并渐隐的效果:
1 |
|
效果如下:
角度变化
角度变化,最简单的可以固定一个角度(什么都不处理),效果就如前文。
稍微复杂一点,可以设置一个不断旋转,或者左右摇摆的效果。这个效果也可以通过 ValueAnimator
来实现。以左右摇摆为例,在整个动画执行开始时,同步执行一个角度变化的动画:
1 | val mSwingDegree = 0f |
这是一个执行时间600ms的,从-24到24变化时不断更新当前角度值 mSwingDegree
,同时在 onDraw()
中加上旋转操作:
1 |
|
效果如下:
还有一种同样不断旋转,但是图片方向始终沿路径前进方向。实现这个就需要用到前面提到的正切值了。还记得前面的代码:
1 |
|
这里的 tan
就是当前点在曲线上的正切值。正切在中学数学中学过,引用百度百科的解释:
正切值是指是直角三角形中,某一锐角的对边与另一相邻直角边的比值。对于任意一个实数x,都对应着唯一的角,而这个角又对应着唯一确定的正切值tanx与它对应,按照这个对应法则建立的函数称为正切函数。
知道正切值,有现成的api可以直接反推出这个角的大小:
1 |
|
注意这里获取的是弧度值,还要再转化为角度值。另外这个角度是以x轴为准的,图片默认如果方向朝上的话要补个90度:
1 |
|
最后还是一样在 onDraw()
中加上旋转:
1 |
|
效果如下:
整合
前面分模块逐步实现了点赞动画的各个部分,当然具体使用的时候还是要封装一下比较好。这里我封装了一个 PathAnimView
,总体管理所有的动画。一个基类 BasePathAnim
,用来扩展不同风格的动画。再结合具体的场景,便能实现一个完整的动画组件了。
直接上代码:
首先是管理所有动画的 PathAnimView
:
1 |
|
然后是一个动画View的基类 BasePathAnim
:
1 |
|
使用时,只需在布局文件中添加:
1 | <com.netease.gamechat.animviewdemo.PathAnimView |
然后创建一个继承自 BasePathAnim
的对象:
1 |
|
这里举两个例子,一个是仿照开篇视频中那样实现的点赞动画,一个是路径飞行动画:
1 |
|
效果:
1 |
|
效果:
总结
以上就是一个简单的点赞动画实现,其实原理很简单,自定义View+Path+动画就可以搞定,我这个组件实现的也比较简陋,具体使用时可以再做优化。