Android 动画大致可以分为帧动画(FrameAnimation),补间动画(TweenAnimation)和属性动画(Animator),这三类是Android最基础的动画,可以通过这三类动画实现更高级的动画样式,本篇文章就来讲讲Android的过渡动画(TransitionAnimation)
一、LayoutTransition
当我们对一个view进行操作的时候(仅限添加view,移除view或改变view的可见性),希望这个view和其他依赖其布局的view的变化过渡自然,如果单一使用属性动画实现起来相当繁琐,LayoutTransition则可以完美解决这个问题。
LayoutTransition字面翻译是布局的过渡也就是布局动画,这个类可以实现ViewGroup的布局改变时自动执行动画,LayoutTransition 从api11开始提供。给ViewGroup设置动画很简单,只需要生成一个LayoutTransition实例,然后调用ViewGroup的setLayoutTransition(LayoutTransition)函数就可以了。当设置了布局动画的ViewGroup添加或者删除内部view时就会触发动画。如果要设置定制的动画,需要调用setAnimator()方法。
1. 怎么做
在根布局添加android:animateLayoutChanges="true"
,或者在代码中指定,
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ViewGroup root = findViewById(R.id.root);
// 创建LayoutTransition实例
LayoutTransition transition = new LayoutTransition();
// 为根布局设置transition
root.setLayoutTransition(transition);
}
接下来只要在这个根布局下的子view被add/remove或者可见性变化就会出发过渡动画效果。
LayoutTransition定义了5种动画,分别是:
APPEARING
: view被添加(可见)到ViewGroup会触发的该view的动画。CHANGE_APPEARING
: view被添加(可见)到ViewGroup,会影响其他View,此时其它View会触发的动画。DISAPPEARING
: view被移除(不可见)ViewGroup会触发的该view的动画。CHANGE_DISAPPEARING
: view被移除(不可见)ViewGroup,会影响其他View,此时其它View会触发的动画。CHANGING
: 非以上四种的其他布局变化触发的动画,比如view边距,大小改变等。(api16加入)
以上5种动画中的前四种是默认开启的,
第5种
CHANGING
在api16中加入,默认是不开启的,使用enableTransitionType(LayoutTransition.CHANGING)
开启。
2. 为什么
先来看LayoutTransition的构造方法,发现只有一个构造方法,
public LayoutTransition() {
// 在没有自定义动画的时候,生成默认动画
if (defaultChangeIn == null) {
// "left" is just a placeholder; we'll put real properties/values in when needed
// 定义需要执行动画的属性,以及属性动画数值
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);
// 默认进入(显示)动画
defaultChangeIn = ObjectAnimator.ofPropertyValuesHolder((Object)null,
pvhLeft, pvhTop, pvhRight, pvhBottom, pvhScrollX, pvhScrollY);
// DEFAULT_DURATION = 300
defaultChangeIn.setDuration(DEFAULT_DURATION);
// mChangingAppearingDelay = 0
defaultChangeIn.setStartDelay(mChangingAppearingDelay);
// mChangingAppearingInterpolator = DecelerateInterpolator()
defaultChangeIn.setInterpolator(mChangingAppearingInterpolator);
// 默认退出(消失)动画,由defaultChangeIn复制而来
defaultChangeOut = defaultChangeIn.clone();
defaultChangeOut.setStartDelay(mChangingDisappearingDelay);
defaultChangeOut.setInterpolator(mChangingDisappearingInterpolator);
defaultChange = defaultChangeIn.clone();
defaultChange.setStartDelay(mChangingDelay);
defaultChange.setInterpolator(mChangingInterpolator);
// 默认淡入动画
defaultFadeIn = ObjectAnimator.ofFloat(null, "alpha", 0f, 1f);
defaultFadeIn.setDuration(DEFAULT_DURATION);
defaultFadeIn.setStartDelay(mAppearingDelay);
defaultFadeIn.setInterpolator(mAppearingInterpolator);
// 默认淡出动画
defaultFadeOut = ObjectAnimator.ofFloat(null, "alpha", 1f, 0f);
defaultFadeOut.setDuration(DEFAULT_DURATION);
defaultFadeOut.setStartDelay(mDisappearingDelay);
defaultFadeOut.setInterpolator(mDisappearingInterpolator);
}
// 赋值给5种动画(APPEARING,DISAPPEARING,CHANGE_APPEARING ,CHANGE_DISAPPEARING,CHANGING)
mChangingAppearingAnim = defaultChangeIn;
mChangingDisappearingAnim = defaultChangeOut;
mChangingAnim = defaultChange;
mAppearingAnim = defaultFadeIn;
mDisappearingAnim = defaultFadeOut;
}
那么动画在哪里触发的呢,我们以mAppearingAnim
为例,找到start()方法,在runAppearingTransition()
方法里,中间clone了一下,难怪通过“ mAppearingAnim.start()”找不到,不过这个方法名也很明确了。
// LayoutTransition
private void runAppearingTransition(final ViewGroup parent, final View child) {
Animator currentAnimation = currentDisappearingAnimations.get(child);
// 先取消当前动画
if (currentAnimation != null) {
currentAnimation.cancel();
}
// 如果未设置,则直接回调动画结束
if (mAppearingAnim == null) {
if (hasListeners()) {
ArrayList<TransitionListener> listeners =
(ArrayList<TransitionListener>) mListeners.clone();
for (TransitionListener listener : listeners) {
listener.endTransition(LayoutTransition.this, parent, child, APPEARING);
}
}
return;
}
Animator anim = mAppearingAnim.clone();
// 设置动画对象View
anim.setTarget(child);
anim.setStartDelay(mAppearingDelay);
anim.setDuration(mAppearingDuration);
if (mAppearingInterpolator != sAppearingInterpolator) {
anim.setInterpolator(mAppearingInterpolator);
}
if (anim instanceof ObjectAnimator) {
((ObjectAnimator) anim).setCurrentPlayTime(0);
}
// 设置动画监听
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator anim) {
currentAppearingAnimations.remove(child);
if (hasListeners()) {
ArrayList<TransitionListener> listeners =
(ArrayList<TransitionListener>) mListeners.clone();
for (TransitionListener listener : listeners) {
listener.endTransition(LayoutTransition.this, parent, child, APPEARING);
}
}
}
});
currentAppearingAnimations.put(child, anim);
// 启动动画
anim.start();
}
我们顺藤摸瓜,发现runAppearingTransition()
方法在addChild()
方法种被调用,而addChild()
最终是在ViewGroup的addViewInner()
中被调用,
// ViewGroup
private void addViewInner(View child, int index, LayoutParams params,
boolean preventRequestLayout) {
if (mTransition != null) {
// Don't prevent other add transitions from completing, but cancel remove
// transitions to let them complete the process before we add to the container
mTransition.cancel(LayoutTransition.DISAPPEARING);
}
if (child.getParent() != null) {
throw new IllegalStateException("The specified child already has a parent. " +
"You must call removeView() on the child's parent first.");
}
// addView时触发transition动画
if (mTransition != null) {
mTransition.addChild(this, child);
}
...
}
同样的,我们也可以在ViewGroup的onChildVisibilityChanged()
方法中看到LayoutTransition被调用执行,
// ViewGroup
protected void onChildVisibilityChanged(View child, int oldVisibility, int newVisibility) {
if (mTransition != null) {
if (newVisibility == VISIBLE) {
mTransition.showChild(this, child, oldVisibility);
} else {
mTransition.hideChild(this, child, newVisibility);
...
}
}
}
结构还是很清楚的,同时从下面源码也能看出5中动画执行的顺序:
// LayoutTransition
private void addChild(ViewGroup parent, View child, boolean changesLayout) {
...
if (changesLayout && (mTransitionTypes & FLAG_CHANGE_APPEARING) == FLAG_CHANGE_APPEARING) {
runChangeTransition(parent, child, APPEARING);
}
if ((mTransitionTypes & FLAG_APPEARING) == FLAG_APPEARING) {
runAppearingTransition(parent, child);
}
}
// LayoutTransition
private void removeChild(ViewGroup parent, View child, boolean changesLayout) {
...
if (changesLayout &&
(mTransitionTypes & FLAG_CHANGE_DISAPPEARING) == FLAG_CHANGE_DISAPPEARING) {
runChangeTransition(parent, child, DISAPPEARING);
}
if ((mTransitionTypes & FLAG_DISAPPEARING) == FLAG_DISAPPEARING) {
runDisappearingTransition(parent, child);
}
}
在上面两个方法中都调用了 runChangeTransition()
这个方法,在这个方法里处理了其他受影响的view的动画,
private void runChangeTransition(final ViewGroup parent, View newView, final int changeReason) {
...
int numChildren = parent.getChildCount();
for (int i = 0; i < numChildren; ++i) {
final View child = parent.getChildAt(i);
// only animate the views not being added or removed
if (child != newView) {
setupChangeAnimation(parent, changeReason, baseAnimator, duration, child);
}
}
if (mAnimateParentHierarchy) {
ViewGroup tempParent = parent;
while (tempParent != null) {
ViewParent parentParent = tempParent.getParent();
if (parentParent instanceof ViewGroup) {
setupChangeAnimation((ViewGroup)parentParent, changeReason, parentAnimator,
duration, tempParent);
tempParent = (ViewGroup) parentParent;
} else {
tempParent = null;
}
}
}
...
}
通过setupChangeAnimation()
来触发那些受影响的view的动画,这个方法的源码我就不贴了,有兴趣的小伙伴可自行查阅。
综上,默认情况下DISAPPEARING和CHANGE_APPEARING类型动画会立即执行,其它类型动画则会有个延迟(这个延迟来自代码执行顺序,而非认为设置的延迟时间)。比如说,删除view,被删除的view将先执行动画消失,经过一些延迟受影响的view会进行动画补上位置;反之添加view,受影响的view将会先给添加的view腾位置执行CHANGE_APPEARING动画,经过一些时间的延迟才会执行APPEARING动画。
二、自定义LayoutTransition
通过setAnimator(int transitionType, Animator animator) 设置不同类型的动画,
public void setAnimator(int transitionType, Animator animator) {
switch (transitionType) {
case CHANGE_APPEARING:
mChangingAppearingAnim = animator;
break;
case CHANGE_DISAPPEARING:
mChangingDisappearingAnim = animator;
break;
case CHANGING:
mChangingAnim = animator;
break;
case APPEARING:
mAppearingAnim = animator;
break;
case DISAPPEARING:
mDisappearingAnim = animator;
break;
}
}
参数说明:
-
transitionType
:动画类型,分为五种,分别是CHANGE_APPEARING,CHANGE_DISAPPEARING,CHANGING,APPEARING,DISAPPEARING。 -
animator
:相应的动画。
setAnimator方法为每种布局动画设置具体的动画,可以设置ObjectAnimator或者包含属性动画的AnimatorSet。
setStagger 可以设置每个子view动画的时间间隔。