RecyclerView使用LayoutTransition实现布局过渡动画
一、目标
上图为普通状态和编辑状态的界面图。
普通状态切换到编辑状态,需要切换删除按钮和拖拽按钮的可见性,并实现切换过程的动画效果。
二、体验地址
神马笔记最新版本:【神马笔记 版本1.3.0.apk】
三、功能设计
如果不设置动画效果,整个界面的切换过程将会非常生硬。
因此,我们添加如下的过渡动画。
- 切换到编辑状态
- 删除按钮——从左侧淡入(translationX + alpha)
- 拖拽按钮——从右侧进入(translationX)
- 切换到普通状态
- 删除按钮——从左侧淡出(translationX + alpha)
- 拖拽按钮——从右侧退出(translationX)
四、准备工作
1. LayoutTransition
ViewGroup
使用LayoutTransition
即可为布局变化应用动画效果。
ViewGroup#setLayoutTransition(LayoutTransition)
android:animateLayoutChanges="true"
通过代码或者资源文件方式,都可以启用ViewGroup
的布局过渡动画效果。
-
LayoutTransition
支持5中情景的动画,但经常使用的只有4种。
常量定义 | 动画效果 |
---|---|
CHANGE_APPEARING | 其他控件出现时,自身的变化动画 |
CHANGE_DISAPPEARING | 其他控件消失时,自身的变化动画 |
APPEARING | 自身出现时的动画 |
DISAPPEARING | 自身消失时的动画 |
CHANGING | ViewGroup自身布局发生变化时,而不是子控件发生变化。默认关闭。 |
private int mTransitionTypes = FLAG_CHANGE_APPEARING | FLAG_CHANGE_DISAPPEARING |
FLAG_APPEARING | FLAG_DISAPPEARING;
-
LayoutTransition
动画的默认值。
动画常量 | Delay | Interpolator | Animator |
---|---|---|---|
CHANGE_APPEARING | 0 | DECEL | CHANGE动画 |
CHANGE_DISAPPEARING | 300 | DECEL | CHANGE动画 |
APPEARING | 300 | ACCEL_DECEL | ObjectAnimator.ofFloat(null, “alpha”, 0f, 1f) |
DISAPPEARING | 0 | ACCEL_DECEL | ObjectAnimator.ofFloat(null, “alpha”, 1f, 0f) |
CHANGING | 0 | DECEL | CHANGE动画 |
CHANGE动画
PropertyValuesHolder pvhLeft = PropertyValuesHolder.ofInt("left", 0, 1);
PropertyValuesHolder pvhTop = PropertyValuesHolder.ofInt("top", 0, 1);
PropertyValuesHolder pvhRight = PropertyValuesHolder.ofInt("right", 0, 1);
PropertyValuesHolder pvhBottom = PropertyValuesHolder.ofInt("bottom", 0, 1);
PropertyValuesHolder pvhScrollX = PropertyValuesHolder.ofInt("scrollX", 0, 1);
PropertyValuesHolder pvhScrollY = PropertyValuesHolder.ofInt("scrollY", 0, 1);
ObjectAnimator.ofPropertyValuesHolder((Object)null,
pvhLeft, pvhTop, pvhRight, pvhBottom,
pvhScrollX, pvhScrollY);
LayoutTransition
提供了set/get接口,可以修改动画属性。
- 注意事项
- 控件
APPEARING
时,默认有300s的延迟,等到CHANGE_APPEARING
空出位置后才会开始动画; - 控件CHANGE_DISAPPEARING时,默认有300s的延迟,等到
DISAPPEARING
释放出位置才会开始动画; -
APPEARING
,DISAPPEARING
的动画效果为淡入淡出,因此决定了以上2个默认值; -
DISAPPEARING
动画结束后,会恢复动画前的alpha值;
- 控件
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator anim) {
currentDisappearingAnimations.remove(child);
child.setAlpha(preAnimAlpha);
// ……
}
});
- 总结
LayoutTransition
默认的动画效果已经非常不错,通常不需要进行其他设置。
如果需要自身设置动画,除了设置Animator
之外,还需要注意设置StartDelay
、Interpolator
、Stagger
等参数。
2. TransitionListener
我门设计的2组的动画效果。
- 切换到编辑状态
- 从左侧淡入(translationX + alpha)
- 从右侧进入(translationX)
- 切换到普通状态
- 从左侧淡出(translationX + alpha)
- 从右侧退出(translationX)
显然LayoutTransition
默认的APPEARING
,DISAPPEARING
动画效果,无法满足需求。
另外,2个按钮应用了不同的动画效果,需要分开来设置Animator
才能达到效果。
因此,我们需要设置TransitionListener
动态设置参数。
public interface TransitionListener {
public void startTransition(LayoutTransition transition, ViewGroup container,
View view, int transitionType);
public void endTransition(LayoutTransition transition, ViewGroup container,
View view, int transitionType);
}
五、组合起来
1. 设置LayoutTransition参数
this.layoutTransition = new LayoutTransition();
layoutTransition.addTransitionListener(this);
layoutTransition.setInterpolator(LayoutTransition.APPEARING,
layoutTransition.getInterpolator(LayoutTransition.CHANGE_APPEARING));
layoutTransition.setInterpolator(LayoutTransition.DISAPPEARING,
layoutTransition.getInterpolator(LayoutTransition.CHANGE_DISAPPEARING));
layoutTransition.setStartDelay(LayoutTransition.APPEARING, 0);
layoutTransition.setStartDelay(LayoutTransition.CHANGE_DISAPPEARING, 0);
- 因为从左右同时出现,将
APPEARING
和CHANGE_DISAPPEARING
的延迟时间均设置为0。 - 将Interpolator设置为相同值,保持动画一致。
2. 设置初始值
view.addOnLayoutChangeListener((v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
if (!isEdit) {
removeBtn.setAlpha(0);
removeBtn.setTranslationX(-iconView.getRight());
dragBtn.setTranslationX(iconView.getRight());
}
});
因为removeBtn和dragBtn默认的可见性为GONE,不会被布局。如果使用默认值,动画前后的数值是相同的,不会产生动画效果。
因此,必须要布局完成后,设置初始值。
这里使用iconView来设置初始值,也可以使用其他方式。
3. 设置动画
public void startTransition(LayoutTransition transition, ViewGroup container, View view, int transitionType) {
if (view == removeBtn) {
Animator animator = null;
if (transitionType == LayoutTransition.APPEARING) {
animator = ObjectAnimator.ofPropertyValuesHolder(view,
PropertyValuesHolder.ofFloat(View.ALPHA, 1.f),
PropertyValuesHolder.ofFloat(View.TRANSLATION_X, 0.f));
} else if (transitionType == LayoutTransition.DISAPPEARING) {
animator = ObjectAnimator.ofPropertyValuesHolder(view,
PropertyValuesHolder.ofFloat(View.ALPHA, 0.f),
PropertyValuesHolder.ofFloat(View.TRANSLATION_X, -view.getRight()));
}
transition.setAnimator(transitionType, animator);
}
if (view == dragBtn) {
ObjectAnimator animator = null;
if (transitionType == LayoutTransition.APPEARING) {
animator = ObjectAnimator.ofPropertyValuesHolder(view,
PropertyValuesHolder.ofFloat(View.TRANSLATION_X, 0));
} else if (transitionType == LayoutTransition.DISAPPEARING) {
animator = ObjectAnimator.ofPropertyValuesHolder(view,
PropertyValuesHolder.ofFloat(View.TRANSLATION_X, itemView.getWidth() - view.getLeft()));
}
transition.setAnimator(transitionType, animator);
}
}
因为removeBtn和dragBtn的动画不相同,因此需要在startTransition
事件中进行设置。
4. 处理Alpha
public void endTransition(LayoutTransition transition, ViewGroup container, View view, int transitionType) {
if (view == removeBtn) {
if (transitionType == LayoutTransition.DISAPPEARING) {
view.setAlpha(0);
}
}
super.endTransition(transition, container, view, transitionType);
}
由于DISAPPEARING
动画结束后,会将alpha设置会默认值。
因此动画结束后,重新将alpha设置为0。
5. 检查动画效果
void animateEdit(boolean value) {
{
ViewGroup layout = itemView.findViewById(R.id.swipe_view);
if (layout.getLayoutTransition() == null) {
layout.setLayoutTransition(layoutTransition);
}
}
{
removeBtn.setVisibility(value? View.VISIBLE: View.GONE);
}
{
dragBtn.setVisibility(value? View.VISIBLE: View.GONE);
}
}
调用animateEdit
实现切换编辑时的动画效果。
六、Finally
在动画完成后必须调用RecyclerView.Adapter#notifyDataSetChanged()通知数据发生变化,否则滑动RecyclerView会出现控件错误的情况。
因为RecyclerView对控件进行缓存,所有必须要调用该接口。
~一觉游仙好梦~任它竹冷松寒~