Animation KTX:属性动画与补间动画的KTX扩展
动画,说白了就是让界面「活」起来的东西。我做了这么多年Android开发,最怕的就是那种生硬的界面跳转——按钮点下去没反馈,页面切换像翻书一样干巴巴的。用户可能说不出来哪里不对,但就是觉得「这App用着不舒服」。
Kotlin出来之后,Google给动画API也套了一层KTX扩展。说实话,我第一次看到这些扩展函数时,心里想的是:「早该这么做了!」
今天我们就聊聊Animation KTX,看看怎么用Kotlin的方式把属性动画和补间动画写得既简洁又流畅。
为什么需要Animation KTX?
先问一个问题:你写过这样的代码吗?
val animator = ObjectAnimator.ofFloat(view, "translationX", 0f, 300f)
animator.duration = 500
animator.interpolator = AccelerateDecelerateInterpolator()
animator.start()
这段代码没什么毛病,但每次写动画都要重复这些模板代码,说实话挺烦的。尤其是项目里动画多了以后,你会发现大量重复的duration、interpolator设置。
Animation KTX解决的就是这个问题。它提供了一系列扩展函数,让你用更少的代码写出同样的动画效果。
核心思想:用Kotlin的扩展函数和lambda表达式,把动画的声明和执行合并成一行代码。
属性动画的KTX扩展
属性动画是Android动画体系的核心。它直接修改对象的属性值,不像补间动画那样只改变绘制效果。我个人习惯在需要「真正改变View状态」的场景下使用属性动画——比如拖拽、滑动、展开收起。
animate() 扩展函数
这是Animation KTX里最常用的扩展。它直接挂载在View上,用法非常直观:
// 以前的方式
val animator = ObjectAnimator.ofFloat(myView, View.ALPHA, 0f, 1f)
animator.duration = 300
animator.start()
// 使用KTX
myView.animate()
.alpha(1f)
.duration = 300
等等,这看起来只是语法糖?嗯,确实有一部分是语法糖,但KTX还做了另一件事——它把多个属性组合在一起变得特别简单:
myView.animate()
.translationX(200f)
.translationY(100f)
.scaleX(1.5f)
.scaleY(1.5f)
.alpha(0.5f)
.setDuration(400)
.start()
你看,五个动画同时执行,代码却只有五行。这在以前得写五个ObjectAnimator,然后塞进AnimatorSet里。我在项目中遇到过类似的需求——做一个卡片点击放大的效果,用KTX扩展写起来简直不要太爽。
ViewPropertyAnimator 的KTX增强
其实ViewPropertyAnimator本身就已经很简洁了,但KTX给它加了一个小功能——withEndAction:
myView.animate()
.alpha(0f)
.setDuration(200)
.withEndAction {
// 动画结束后执行
myView.visibility = View.GONE
}
这个withEndAction比addListener简洁多了。你想想看,以前要写一个AnimatorListenerAdapter,重写onAnimationEnd方法,就为了在动画结束后做一件事。现在一行lambda搞定。
小技巧:withEndAction里如果只是修改View的状态,可以直接用属性赋值,不用再开线程。因为动画结束回调已经在主线程了。
补间动画的KTX扩展
补间动画(Tween Animation)现在用得少了,但在一些简单场景下——比如Activity切换、View进出——它依然有优势。KTX对补间动画的扩展主要体现在AnimationUtils上。
AnimationUtils的KTX版本
// 传统方式
val anim = AnimationUtils.loadAnimation(context, R.anim.fade_in)
view.startAnimation(anim)
// KTX方式
view.startAnimation(R.anim.fade_in)
嗯,就这么简单。startAnimation直接接受资源ID,省去了加载Animation对象的步骤。
不过说实话,补间动画在KTX里的扩展并不多。为什么?因为Google自己也在推属性动画。我个人的建议是:新项目尽量用属性动画,补间动画只用在那些「必须用XML定义动画」的场景——比如一些第三方库的动画配置。
AnimatorSet的KTX扩展
AnimatorSet是组合动画的核心。以前写组合动画,代码量很大:
val anim1 = ObjectAnimator.ofFloat(view, "translationX", 0f, 200f)
val anim2 = ObjectAnimator.ofFloat(view, "translationY", 0f, 100f)
val anim3 = ObjectAnimator.ofFloat(view, "alpha", 1f, 0f)
val set = AnimatorSet()
set.play(anim1).before(anim2)
set.play(anim2).with(anim3)
set.start()
KTX提供了playSequentially和playTogether两个扩展函数:
// 顺序执行
AnimatorSet().apply {
playSequentially(
ObjectAnimator.ofFloat(view, "translationX", 0f, 200f),
ObjectAnimator.ofFloat(view, "translationY", 0f, 100f),
ObjectAnimator.ofFloat(view, "alpha", 1f, 0f)
)
start()
}
// 同时执行
AnimatorSet().apply {
playTogether(
ObjectAnimator.ofFloat(view, "translationX", 0f, 200f),
ObjectAnimator.ofFloat(view, "translationY", 0f, 100f)
)
start()
}
代码量减少了一半不止。而且可读性更强——一眼就能看出动画是顺序执行还是同时执行。
注意:playSequentially和playTogether接受的是vararg参数,也就是你可以传任意多个Animator进去。但别传太多,超过10个动画组合在一起,性能会明显下降。我曾经在一个列表动画里传了20多个Animator,结果低端机上卡成PPT。
流畅动画的编写技巧
有了KTX扩展,写动画确实方便了。但「流畅」二字,光靠扩展函数是不够的。这里分享几个我踩过的坑。
1. 避免在动画中频繁触发布局
属性动画修改的是View的translationX/Y、scaleX/Y、alpha这些属性,它们不会触发onLayout。但如果你修改的是left/top/right/bottom或者margin,就会触发布局重新计算。布局计算是很耗时的操作,尤其是在复杂布局里。
// 推荐:使用translation
myView.animate().translationX(200f)
// 不推荐:直接修改left
myView.layout(myView.left + 200, myView.top, myView.right + 200, myView.bottom)
2. 使用硬件加速
Android 3.0之后默认开启了硬件加速,但有些情况下会被关闭。如果你发现动画掉帧,可以检查一下View的layerType:
myView.setLayerType(View.LAYER_TYPE_HARDWARE, null)
myView.animate()
.alpha(0f)
.withEndAction {
myView.setLayerType(View.LAYER_TYPE_NONE, null)
}
硬件加速会把View渲染到一个单独的纹理上,动画过程中不需要重新绘制View本身,性能提升很明显。但记得动画结束后要恢复LAYER_TYPE_NONE,否则会占用额外的内存。
3. 合理使用插值器
插值器决定了动画的速度变化曲线。默认的AccelerateDecelerateInterpolator在大多数场景下够用,但有些场景需要定制:
// 弹跳效果
myView.animate()
.translationY(-100f)
.setInterpolator(BounceInterpolator())
.setDuration(600)
// 自定义贝塞尔曲线
myView.animate()
.translationX(300f)
.setInterpolator(PathInterpolator(0.4f, 0f, 0.2f, 1f))
.setDuration(400)
PathInterpolator是我比较喜欢用的。它可以精确控制动画的加速和减速阶段,做出很自然的动画效果。
知识体系总览
下面这张图总结了Animation KTX的核心内容:
避坑指南
最后,说几个我踩过的坑,希望能帮你少走弯路。
- 动画未取消导致内存泄漏:Activity或Fragment销毁时,如果动画还在运行,View会持有Activity的引用导致泄漏。记得在onDestroy或onStop中调用animator.cancel()。
- 动画重复启动:快速点击按钮时,动画可能会被多次触发。我习惯在动画开始前先判断isRunning状态,或者用View.isAnimating()检查。
- 动画与触摸事件冲突:如果View在动画过程中需要响应点击事件,记得在动画结束后再设置点击监听。否则用户点击的是动画前的坐标位置,体验很差。
- 低端机上的性能问题:我曾经在一个低端机上测试动画,发现scale动画特别卡。后来发现是View的层级太深,硬件加速也没开。解决办法是减少View层级,或者用Bitmap缓存。
嗯,关于Animation KTX就聊这么多。其实动画这东西,写起来不难,难的是写出「让人感觉舒服」的动画。多看看Material Design的动画规范,多调调插值器的参数,慢慢就有感觉了。
我的建议:刚开始用KTX写动画时,先别追求花哨效果。把基础的alpha、translation、scale用熟练,再慢慢尝试组合动画和自定义插值器。稳扎稳打,比什么都强。