背景
因为项目需求要为CheckBox和RadioButton添加切换动画,以达到个性化的UI组件效果,具体来说项目需要的切换动画为复杂动画,即无法通过简单的平移,旋转,缩放等基本图形变换来模拟。经过查找资料,发现有如下几种实现动效的方式:
- ObjectAnimator,用来实现如平移,旋转,缩放等最基本的动效,无法满足项目要求。
- 自定义View专门来实现动画,这种成本很高,另外完全自定义也无法直接使用CheckBox的各项功能。
- VectorAnimatorDrawable,看起来很靠谱,能够实现很复杂的动画。但最大的难题是UI无法直接输出对应的资源,UI同学一般提供的动效资源是动效json,GIF,视频等资源,想要将其转化为VectorAnimatorDrawable资源非常困难,复杂一点的动效,要想完全还原设计稿基本不可能。
- Lottie动画,这也是Android开发领域较为主流的实现复杂动画的手段,这也是我最终采用的方案。但一般Lottie动画都是直接通过的LottieAnimationView(继承自ImageView)来使用的,如何将其与CheckBox进行结合是需要考虑的问题,下边也将详细描述Lottie动画如何结合CheckBox来完成切换动效功能。
分析&实现
首先上边的介绍都是关于CheckBox如何进行动画的,但对于RadioButton其实没提到。这是因为CheckBox和RadioButton本质上是同一类切换按钮,他们实现动效的思路也基本一致。另外从Android实现的角度来说他们也都是继承自CompoundButton的,该组件的特点是有选中和未选中两种状态,会根据点击事件切换状态。后边我们也将仅介绍基于CheckBox的动效实现,RadioButton基本可以复用该实现。
问题1. CheckBox动画该如何做,切状态的时机是在播动画前还是后。(借鉴Switch组件实现)
首先CheckBox切换动画为setChecked(),该方法直接在其父类CompundButton中定义
@Override
public void setChecked(boolean checked) {
if (mChecked != checked) {
mCheckedFromResource = false;
mChecked = checked;
refreshDrawableState();
// ... 省略部分无关代码
}
}
该方法直接修改了mChecked状态,并没有提供任何关于动画播放的hook点。
一个小插曲,由于此次需求中还有Switch组件动效的开发,于是通过对Switch组件的研究,找到了播放动画的切入点。Switch组件通过重写setChecked()实现了动画播放(文章最后有展开介绍,实际是在SwitchCompat类中)。
@Override
public void setChecked(boolean checked) {
super.setChecked(checked);
// Calling the super method may result in setChecked() getting called
// recursively with a different value, so load the REAL value...
checked = isChecked();
if (checked) {
setOnStateDescriptionOnRAndAbove();
} else {
setOffStateDescriptionOnRAndAbove();
}
// 如果View仍然在View树上,则播动画;否则不播,直接切换状态
if (getWindowToken() != null && ViewCompat.isLaidOut(this)) {
animateThumbToCheckedState(checked);
} else {
// Immediately move the thumb to the new position.
cancelPositionAnimator();
setThumbPosition(checked ? 1 : 0);
}
}
在Switch组件中我们找到了动画播放的方式:先切换check状态,再播动画(animateThumbToCheckedState),于是现在我们可以得出CheckBox的动画播放框架(重写setChecked())
@Override
public void setChecked(boolean checked) {
super.setChecked(checked);
checked = isChecked();
// 如果View仍然在View树上,则播动画;否则不播,直接切换状态
if (getWindowToken() != null && ViewCompat.isLaidOut(this)) {
animateThumbToCheckedState(checked);
} else {
cancelAnimator();
}
}
剩下的工作就是填充下面两个方法,整体来说就是处理具体动画该怎么播
- animateThumbToCheckedState(checked)
- cancelAnimator()
问题2:动画该怎么播
经过前边的分析,我们已经选定了Lottie动画作为播放动画的方案。并且预期的CheckBox切换流程为(以uncheck -> checked为例