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()方法。
-
接入布局动画
在根布局添加android:animateLayoutChanges="true"
,
<!-- activity_main.xml -->
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:animateLayoutChanges="true" >
</androidx.constraintlayout.widget.ConstraintLayout>
或者在代码中指定,
// MainActivity.kt
class MainActivity : Activity() {
private val mViewBinding = ActivityMainBinding.inflate(layoutInflater)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(mViewBinding.root)
// 设置默认的布局动画
mViewBinding.root.layoutTransition = LayoutTransition()
}
}
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加入)
以上类型对应LayoutTransition源码中的这5个动画,
其中前四种动画是默认开启的,CHANGING
默认不开启,需要手动开启,调用enableTransitionType
开启指定动画类型,
mViewBinding.root.layoutTransition = LayoutTransition().apply {
enableTransitionType(LayoutTransition. CHANGING )
}
同时LayoutTransition
也提供了禁用某个动画的接口disableTransitionType()
.
-
常用接口
方法 | 说明 |
---|---|
void enableTransitionType(int transitionType) | 启用transitionType类型的动画,参数APPEARING 、CHANGE_APPEARING 、DISAPPEARING 、CHANGE_DISAPPEARING 、CHANGING 之一。 |
void disableTransitionType(int transitionType) | 禁用transitionType类型的动画,参数同上。 |
void setDuration(long duration) | 设置动画时长。 |
void setDuration(int transitionType, long duration) | 设置transitionType类型动画时长。 |
void setStartDelay(int transitionType, long delay) | 设置transitionType类型动画延迟执行毫秒数。 |
void setStagger(int transitionType, long duration) | 设置transitionType类型动画之间间隔时长毫秒数。参数CHANGE_APPEARING 、CHANGE_DISAPPEARING 、CHANGING 之一。 |
void setInterpolator(int transitionType, TimeInterpolator interpolator) | 设置transitionType类型动画插值器。 |
void setAnimator(int transitionType, Animator animator) | 自定义transitionType类型的布局动画。 |
void addTransitionListener(TransitionListener listener) | 设置布局动画监听。 |
-
自定义布局动画
LayoutTransition
提供了自定义特定类型动画的接口setAnimator(int transitionType, Animator animator)
。假如我希望View在变得不可见的时候背景色从黑色过渡到白色,动画时长2s;显示的时候从红色过渡到绿色,动画时长1s
mViewBinding.root.layoutTransition = LayoutTransition().apply {
setAnimator(
LayoutTransition.APPEARING,
ObjectAnimator.ofArgb(null, "backgroundColor", Color.RED, Color.GREEN)
)
setAnimator(
LayoutTransition.DISAPPEARING,
ObjectAnimator.ofArgb(null, "backgroundColor", Color.BLACK, Color.WHITE)
)
setDuration(LayoutTransition.DISAPPEARING, 2000)
setDuration(LayoutTransition.APPEARING, 1000)
}
二、原理
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
为例,在runAppearingTransition()
方法里,
// 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()
中被调用。
// LayoutTransition
private void addChild(ViewGroup parent, View child, boolean changesLayout) {
...
if ((mTransitionTypes & FLAG_APPEARING) == FLAG_APPEARING) {
runAppearingTransition(parent, child);
}
}
// 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);
...
}
}
}
mTransition.showChild()
内部调用了addChild()
;mTransition.hideChild()
内部调用了removeChild()
// 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的动画,这个方法的源码我就不贴了,有兴趣的小伙伴可自行查阅。