效果预览

功能说明
- 使用Canvas绘制元素移动动效,极致高效;
- 支持任意方向元素漂移(起点、终点任意);
- 支持修改元素图标及其大小(单位dp);
- 支持修改动效持续时间及动画结束时回调;
- 支持修改元素飘动个数;
使用方式
- 在工程根目录的build.gradle中添加
allprojects {
repositories {
maven { url 'https://www.jitpack.io' }
...
}
}
- 添加依赖
implementation 'com.gitee.chockqiu:animation-views:1.1'
- 在xml中添加布局
<com.chockqiu.view.ElementFloatTogetherAnimation
android:layout_width="match_parent"
android:layout_height="match_parent" />
- 确定起点及终点,开始动画&处理回调
val p0 = Point().apply {
x = (viewStart.left + viewStart.right) / 2
y = (viewStart.top + viewStart.bottom) / 2
}
val p1 = Point().apply {
x = (endView.left + endView.right) / 2
y = (endView.top + endView.bottom) / 2
}
mElement.startAnimation(p0, p1) {
...
}
PS: 为了起/终点坐标与预期一致,建议将
View
放置在跟起点终点View
同一个ViewGroup
中,并且动效View
的宽高与ViewGroup
一致,也就是match_parent
(动效内部坐标系是以自身左上角为坐标原点进行绘制的),如不一致需要进行坐标转换。
实现原理
利用二阶贝塞尔曲线控制点
决定曲线路径的特性,通过算法生成每一个元素的控制点
,实现不同元素通过不同的路径从起点飘向终点,再加上不同元素移动动画开始的时间差,最终组合实现这么一种效果。
二阶贝塞尔曲线原理图如下:

实现步骤
1)实现二阶贝塞尔曲线数学公式;

具体实现详见quadToFunValue(float t, Point p0, Point p1, Point p2)
函数。
2)通过算法生成每个元素的控制点,为了好看控制点需在起点与终点所在直线的中垂线上随机分布;
所以最终控制点是一个与中点M(Mx,My)距离为D的坐标,且它所在的直线是起点与终点所在直线的中垂线。

如何计算控制点坐标呢?
首先通过起点终点所在的直线可以计算出中垂线的斜率k2:
private double centerk(Point a, Point b) {
//已知A(Ax,Ay),B(Bx,By); 中垂线的斜率为:
//-1/k=-1/[(By-Ay)/(Bx-Ax)]=-(Bx-Ax)/(By-Ay)
if (b.y == a.y) {
return 0;
}
if (b.x == a.x) {
return Double.MAX_VALUE;
}
return -(((b.x - a.x) * 1f / (b.y - a.y)));
}
已知随机的确定正值D,可以计算出控制点与坐标中点M(Mx,My)的坐标距离dx和dy, 数学公式如下:
等式1: dy/dx = k2
等式2: dx*dx + dy*dy = D*D
只要起点/终点确认, 则k1是一个定值,那么它的中垂线斜率k2也是定值,再加上随机的正值D,经过转换可得
dx = D*D/(k2*k2+1)
dy = (D*D*k2*k2)/(k2*k2+1)
/**
* 斜率k时距离d对应的dx值
*
* @param k 斜率k
* @param d 距离d
* @return
*/
private double dx(double k, double d) {
if (k == 0) {
return 0;
}
if (k == Double.MAX_VALUE) {
return d;
}
double dx = Math.sqrt((d * d) / (k * k + 1));
if (d < 0) {
return -dx;
} else {
return dx;
}
}
/**
* 斜率k时距离d对应的dy值
*
* @param k 斜率k
* @param d 距离d
* @return
*/
private double dy(double k, double d) {
if (k == 0) {
return d;
}
if (k == Double.MAX_VALUE) {
return 0;
}
double dy = Math.sqrt((d * d) * (k * k) / (k * k + 1));
if (d < 0) {
return -dy;
} else {
return dy;
}
}
在根据起点(Sx,Sy)、终点(Ex,Ey)的朝向不同,确定dx与dy的符号,可能是-dx,-dy,也可能是dx,-dy等等
最终得到控制点的精确坐标值;

网友评论