18 年底做 Readhub APP 时就加入了这个返回动画效果。一直到现在,才有时间来简单总结和封装一下。



不知道这个返回手势动画到底是 MIUI 还是「即刻」APP 首创,因为我那会儿还没用上 MIUI 全面屏。不过 MIUI 全面屏那个返回手势相对简单,固定位置。从这个效果上看,我觉得是 MIUI 先有这个效果,然后 「即刻」APP 优化丰富了一下。当然,可能还有另外一种情况,这是早就有的效果图,只是在目前,我已知有使用的就上面两个场景。

总结起来很简单,就是一个「贝塞尔曲线」的绘制,再外加一个箭头绘制。箭头什么的的绘制在之前的仓库中已经练习过很多。这次着重说说这个特殊图形需要怎么绘制。

对于贝塞尔曲线绘制,之前玩过两阶、三阶的。第一次看到这个效果,觉得贝塞尔曲能实现,但是这是几阶的,高阶的怎么玩,那就是从头开始。

在经过一番把玩探索后,最终确定这就是五阶的贝塞尔曲线, Android 似乎默认没有高阶对应的 api。


那这怎么办呢?曲线就是函数嘛,肯定有公式,网上就搜到公式写法。

private fun calculateY(i: Int, j: Int, t: Float): Float {
    return if (i == 1) {
        (1 - t) * controlPoints[j].y + t * controlPoints[j + 1].y
    } else (1 - t) * calculateY(i - 1, j, t) + t * calculateY(i - 1, j + 1, t)
}
复制代码

controlPoints 对应的就是那个五个控制点的集合。

addControlPoint(0, 0f, yResult - maxPeakValue * 1.5f)
addControlPoint(1, 0f, yResult - maxPeakValue * 1.5f * GOLDEN_RATIO)
addControlPoint(2, min, yResult)
addControlPoint(3, 0f, yResult + maxPeakValue * 1.5f * GOLDEN_RATIO)
addControlPoint(4, 0f, yResult + maxPeakValue * 1.5f)
复制代码

接着就是控制是否拦截事件,我现在是做成 Helper 这种工具类型,和对应的 ViewGroup 是解耦的,其实就是 ViewDragHelper 的一个简单实现。

对了,最后考虑下来,实现了左右两边的滑动效果。至于上下,我觉得这种场景不大,就懒得去做了。

具体代码细节就不贴了,源码也没多少,这里讲遇到的一些细节问题或者写出来的 bug

返回退出应用后,后台程序预览中存在返回手势效果

这里其实就是一个先后问题,一开始是同步执行 invalidate()onBackReleased() 。后来使用 Runnable + postDalay() 来延迟 onBackReleased() 执行。

贝塞尔曲线贴近屏幕的地方总有一个像素的白线

这里后面排查出来是我添加控制点,for 循环时角标是[0,length-1],最后一个没有添加计算到,所以计算出来的控制点就少了一个。最终效果就是绘制出来的图形 x 轴没有完全对称。偏差就在 1px 左右。 最后的效果就是总感觉下方有一跟白线。

for 循环处理好之后,发现右边绘制出来还是会有这个情况,具体原因不清楚,因为单看数据层面,它肯定是贴边的。最后很讨巧,使用到 translate()Canvas 平移一个像素规避掉,这简直是程序员的小巧思,哈哈 ?。

控制点数量及缓存

因为是用公式算的控制点,所以每一次绘制,其实简单理解就是讲一个一个点连接成一条曲线的。那么问题就是,点多,曲线当然最逼真,但是单位时间处理的数据量就上去了。 点少,可能你看到就是折线图了。 最后均衡在 50 个控制点,点与点之间的比例就是 2% 。在绘制等方法中肯定不能频繁创建对象,所以这 50 个点需要复用。