Android元素分散-飘移-聚合动效

效果预览

元素分散-飘移-聚合动效.gif

功能说明

  • 使用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(动效内部坐标系是以自身左上角为坐标原点进行绘制的),如不一致需要进行坐标转换。

实现原理

利用二阶贝塞尔曲线控制点决定曲线路径的特性,通过算法生成每一个元素的控制点,实现不同元素通过不同的路径从起点飘向终点,再加上不同元素移动动画开始的时间差,最终组合实现这么一种效果。

二阶贝塞尔曲线原理图如下:


二阶贝塞尔曲线原理图.gif

实现步骤

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


二阶贝塞尔曲线数学公式.png

具体实现详见quadToFunValue(float t, Point p0, Point p1, Point p2)函数。


2)通过算法生成每个元素的控制点,为了好看控制点需在起点与终点所在直线的中垂线上随机分布;

所以最终控制点是一个与中点M(Mx,My)距离为D的坐标,且它所在的直线是起点与终点所在直线的中垂线。

控制点计算原理.png

如何计算控制点坐标呢?
首先通过起点终点所在的直线可以计算出中垂线的斜率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等等
最终得到控制点的精确坐标值;

控制点坐标.png


如果本文对你有帮助就点个赞支持下吧~~~
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容