13、Launcher的动画框架:ObjectAnimator与ViewPropertyAnimator的使用、启动器动画(如返回桌面动画)、图标飞入动画
动画,是Launcher的灵魂。你想想看,用户每天解锁手机,第一眼看到的就是桌面。如果图标是硬生生蹦出来的,返回桌面时画面一闪而过,那体验得多糟糕。Android系统从早期就提供了动画框架,但真正让Launcher“活”起来的,是属性动画系统——ObjectAnimator和ViewPropertyAnimator。
我个人习惯把动画分为两类:一类是“一次性”的,比如图标飞入;另一类是“持续性”的,比如桌面滑动时的阻尼效果。今天咱们重点聊前者,也就是那些触发一次、执行完毕就结束的动画。嗯,这部分是Launcher动画的基础,搞懂了它,后面那些复杂的交互动画才能玩得转。
13.1 属性动画的核心:ObjectAnimator
ObjectAnimator是Android属性动画的基石。它直接操作对象的属性值,而不是像补间动画那样只改变绘制效果。说白了,补间动画只是“看起来”移动了,实际点击区域还在原地;而ObjectAnimator是真正改变了对象的位置、大小、透明度等属性。
我在项目中遇到过一个问题:用补间动画做图标移动,结果用户点击时老点不准。后来换成ObjectAnimator,一切就正常了。这就是属性动画和补间动画的本质区别。
来看一个最简单的例子——让一个图标从屏幕外飞入到指定位置:
// 创建一个图标的飞入动画
ImageView icon = findViewById(R.id.icon);
// 从屏幕右侧外飞入,目标位置是原始X坐标
ObjectAnimator animator = ObjectAnimator.ofFloat(
icon,
"translationX",
icon.getWidth(), // 起始位置:屏幕外
0f // 结束位置:原始位置
);
animator.setDuration(400);
animator.setInterpolator(new OvershootInterpolator());
animator.start();
这里有个细节要注意:translationX是相对于原始位置的偏移量。如果你直接操作x属性,那就是绝对坐标。我个人习惯用translationX,因为这样不影响布局参数,方便后续复用。
13.2 ViewPropertyAnimator:更简洁的写法
ObjectAnimator虽然强大,但写起来有点啰嗦。尤其是同时要改变多个属性时,代码会变得很长。这时候,ViewPropertyAnimator就派上用场了。
ViewPropertyAnimator是Android 3.1引入的,专门为View优化的动画API。它使用链式调用,一行代码就能搞定多个属性的动画。
// 用ViewPropertyAnimator实现图标飞入
icon.animate()
.translationX(0f) // 水平归位
.translationY(0f) // 垂直归位
.alpha(1f) // 从不透明到完全显示
.scaleX(1f) // 缩放回正常大小
.scaleY(1f)
.setDuration(350)
.setInterpolator(new FastOutSlowInInterpolator())
.start();
你看,代码是不是清爽多了?而且ViewPropertyAnimator内部做了优化,多个属性会合并到同一个动画帧里执行,性能比多个ObjectAnimator同时运行要好。
13.3 启动器动画:返回桌面动画
返回桌面动画,是Launcher里最核心的动画之一。用户按下Home键,所有应用窗口要缩放、移动、淡出,最终回到桌面图标。这个动画做得好不好,直接决定了用户对系统流畅度的第一印象。
我曾经在一个项目里调试这个动画,发现总是有卡顿。后来定位到问题:动画过程中,Launcher的布局在频繁重新测量。解决方案是——在动画开始前,先测量好所有目标位置,动画过程中只改变translationX/Y和scaleX/Y,不触发布局。
来看一个简化版的返回桌面动画实现:
// 假设我们有一个应用窗口的View,要缩放到桌面图标的位置
public void animateBackToHome(View appWindow, View iconView) {
// 1. 计算目标位置
int[] iconLocation = new int[2];
iconView.getLocationInWindow(iconLocation);
int[] windowLocation = new int[2];
appWindow.getLocationInWindow(windowLocation);
float targetX = iconLocation[0] - windowLocation[0];
float targetY = iconLocation[1] - windowLocation[1];
float targetScale = (float) iconView.getWidth() / appWindow.getWidth();
// 2. 执行动画
appWindow.animate()
.translationX(targetX)
.translationY(targetY)
.scaleX(targetScale)
.scaleY(targetScale)
.alpha(0f)
.setDuration(300)
.setInterpolator(new FastOutLinearInInterpolator())
.withEndAction(() -> {
// 动画结束后,隐藏应用窗口,显示桌面
appWindow.setVisibility(View.GONE);
// 恢复桌面图标
iconView.setAlpha(1f);
iconView.setScaleX(1f);
iconView.setScaleY(1f);
})
.start();
}
13.4 图标飞入动画:让桌面活起来
图标飞入动画,是用户解锁后看到的第一个动画。好的飞入动画应该是有序的、有节奏的,而不是所有图标一起飞进来。我习惯用“波浪式”飞入——从左到右、从上到下,每个图标依次飞入,间隔50毫秒左右。
来看一个实现方案:
// 为桌面上的所有图标创建飞入动画
public void animateIconsFlyingIn(ViewGroup workspace) {
int childCount = workspace.getChildCount();
long baseDelay = 100; // 基础延迟
for (int i = 0; i < childCount; i++) {
View icon = workspace.getChildAt(i);
// 初始状态:屏幕外、透明、缩小
icon.setTranslationY(icon.getHeight() * 2);
icon.setAlpha(0f);
icon.setScaleX(0.5f);
icon.setScaleY(0.5f);
// 计算延迟:根据行列位置
int row = i / 4; // 假设每行4个图标
int col = i % 4;
long delay = baseDelay + (row + col) * 30;
// 执行飞入动画
icon.animate()
.translationY(0f)
.alpha(1f)
.scaleX(1f)
.scaleY(1f)
.setDuration(400)
.setStartDelay(delay)
.setInterpolator(new OvershootInterpolator(1.5f))
.start();
}
}
这里用到了OvershootInterpolator,它会让图标在到达目标位置时稍微“弹”一下,看起来更生动。参数1.5f表示弹跳的幅度,你可以根据实际效果调整。
13.5 动画框架的知识体系
为了让你更直观地理解Launcher动画框架的组成,我画了一张图:
从图中你可以看到,ObjectAnimator和ViewPropertyAnimator是并列关系,它们都基于属性动画核心。在实际开发中,我通常这样选择:
| 场景 | 推荐API | 原因 |
|---|---|---|
| 单个View的简单动画 | ViewPropertyAnimator | 代码简洁,性能好 |
| 需要自定义属性 | ObjectAnimator | 可以操作任意属性 |
| 多个View同步动画 | ViewPropertyAnimator | 链式调用方便管理 |
| 需要精确控制每一帧 | ObjectAnimator + AnimatorUpdateListener | 可以获取实时属性值 |
13.6 避坑指南
做Launcher动画,有几个坑是绕不开的。我一个个说:
- 动画卡顿:我曾经在低端机上测试,发现图标飞入动画掉帧严重。后来定位到是动画过程中触发了布局重绘。解决方案是:动画开始前,用
setLayerType(View.LAYER_TYPE_HARDWARE, null)开启硬件加速,动画结束后再关闭。 - 动画冲突:用户快速点击Home键,可能会触发多个返回桌面动画同时执行。我的做法是:用一个
AnimatorSet管理所有动画,每次开始新动画前,先取消之前的。 - 内存泄漏:动画持有View的引用,如果Activity销毁时动画还在运行,就会导致内存泄漏。记得在
onDestroy()中调用animator.cancel()。
好了,关于Launcher的动画框架,核心就是这些。ObjectAnimator和ViewPropertyAnimator就像两把利器,用好了,你的桌面就能“活”起来。下一节我们会聊更复杂的交互动画,比如桌面滑动时的惯性效果和弹性阻尼。嗯,到时候再细说。
公众号:蓝海资料掘金营,微信deep3321