Android Material Design Animatio
1 View transitions(View转换效果)
1.1 什么是 Transition
?
Transition
的中文翻译是 “过渡”、“转换”、“转变”,这里对于 View Transition
个人粗浅的理解是:View
的场景切换,即 View
的入场和出场。
那么 View transition Aniamtion
就很好理解了,就是当 view 入场或出场时触发的动画。
1.2 如何实现 View Transition
动画
不管是自己实现,还是 Android 系统已经帮我们实现了的机制,大概都能总结为 3 步:
-
捕获view在开始场景和结束场景中的状态
-
基于view从一个场景到另一个场景的状态改变,创建动画
-
执行动画
1.3 Android Transition 框架是如何实现动画的呢?
-
beginDelayedTransition()
, 传入view
和Fade
,调用captureStartValues()
记录场景中各个 view 的可见性 -
设置 view 为不可见
-
在下一帧的时候,Framework 调用
captureEndValues()
,记录场景中每个 view 的可见性 -
Framework 根据可见性发生变化的 view 的前后值,创建并返回
AnimatorSet
-
Framework 运行 Animator,触发 view 逐渐进入或退出场景
-
其中 Android 已经预定义了
Fade
,Slide
,Explode
-
如果需要自定义动画的话,需要派生自
Visibility
, 并重载onAppear
和onDisappear
,分别返回View
入场和出场的动画Animator
-
示例代码如下:
TransitionManager.beginDelayedTransition(mRootView, new Fade()); toggleVisibility(mRedBox, mGreenBox, mBlueBox, mPurpleBox, mOrangeBox);
private static void toggleVisibility(View... views) { for (View view : views) { boolean isVisible = view.getVisibility() == View.VISIBLE; view.setVisibility(isVisible ? View.INVISIBLE : View.VISIBLE); } }
说明:这里通过设置 view 的
visibility
来触发 view 的出场和入场 -
效果如下:

说明:红色点击对应 Fade
, 绿色点击对应 Slide
, 蓝色点击对应 Explode
,黄色点击对应自定义的圆形消失动画
1.4 基于 Transition Framework 创建动画的优点
简单查看如下:
-
抽象了 Animator 的概念,使用者并不需要知道底层是通过 Animator 实现的
-
减少了代码量,仅需要指定view的前后不同状态,Transition会自动创建动画
-
有利于代码复用,自定义的
View Transition Visibility
可以直接给其他模块复用,且能使代码结构清晰
2 Activity Transitions(Activity 转换效果)
2.1 版本区别
-
Android 5.0 之前
可以通过
Activity.overridePendingTransition();
和FragmentTransaction#setCustomAnimation();
来实现 Activity 之间的切换效果。示例代码如下:
activity.finish(); overridePendingTransition(R.anim.anim_fade_in, R.anim.anim_fade_out);
缺点:只能对一个
Activity
和Fragment
的整个 view 做动画 -
Android 5.0
- 允许对Activity/Fragment的单个view做动画
- 允许定义共享view并创建动画
2.2 分类
-
按触发时间划分
-
Exit transition (A启动B, A发生exit)
-
Enter transition (A启动B, B发生enter)
-
Return transition (B返回A, B发生return)
-
Reenter transition (B返回A, A发生reenter)
-
-
按内容划分
-
Content Transition (内容切换)
-
Shared Element Transition (共享元素切换)
-
2.3 创建 Activity Transition 的步骤
-
在调用和被调用的 Activity 中,通过设定
Window.FEATURE_ACTIVITY_TRANSITIONS
和Window.FEATURE_CONTENT_TRANSITIONS
来启动transition api
。具体代码如下:
java 代码中设置
getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITION);
style.xml
文件中设置<item name=“android:windowContentTransitions”>true</item>
注:使用 Material Design 主题的 app 默认已启动
-
分别在调用与被调用的
Activity
中设置exit
和enter transition
。-
Material
主题默认会将exit
的transition
设置为null
, 而enter
的transition
设置成Fade
-
如果
reenter
或return transition
没有明确设置,则将用exit
和enter
的transition
替代
-
-
分别在调用与被调用的
Activity
中设置exit
和enter
共享元素的transition
。-
Material
主题会默认将exit
的共享元素transition
设置成null
,而enter
的共享元素transition
设置成@android:transition/move
-
如果
reenter
或return transition
没有明确设置,则将用exit
和enter
的共享元素transition
替代
-
-
调用
startActivity(Context, Bundle)
启动Activity
-
第二个参数
ActivityOptions.makeSceneTransitionAnimation(activity, pairs).toBundle();
-
其中
pairs
参数是一个数组:Pair<View, String>
,该数组列出了activity
中共享的view
和view
的名称
-
-
调用
finishAfterTransition()
触发返回动画,而不是finish()
-
默认情况下,
Material
主题的 app 中enter
/return
的content transition
会在exit
/reenter
的content transitions
结束之前开始播放(只是稍微早于)- 可调用
setWindowAllowEnterTransitionOverlap()
和setWindowAllowReturnTransitionOverlap()
方法屏蔽
- 可调用
2.4 创建 Fragment Transition 的步骤
-
页面的
exit
,enter
,reenter
, 和return transition
需要调用 fragment 的相应方法来设置,或者通过fragment 的 xml 属性来设置。 -
共享元素的
enter
和return transition
也需要调用 fragment 的相应方法来设置,或者通过 fragment 的 xml 属性来设置。 -
虽然在 Activity 中 transition 是被
startActivity()
和finishAfterTransition()
触发的,但是 fragment 的 transition 却是在其被FragmentTransaction
执行下列动作的时候自动发生的。added
,removed
,attached
,detached
,shown
,hidden
-
在 fragment commit 之前,共享元素需要通过调用
addSharedElement(View, String)
方法来成为FragmentTransaction
的一部分
3 Acvitivity - Content Transitions
Content Transition
的概念:Activity 或 Fragment 切换的时候,定义非共享 view 进入或离开场景的方式(动画)
相关的 api 有:setExitTransition()
, setEnterTransition()
, setReturnTransition()
, setReenterTransition()
3.1 Content transition 如何触发
假设从 Activity A 启动 B,则过程如下:
-
当 Activity A 调用 startActivity
-
Framework 遍历 A 的场景树,并判断哪些 view 会离开场景
-
A 的
exit transition
捕获transition view
的初始状态 -
Framework 设置全部的
transition view
为INVISIBLE
-
在下一帧的时候,A 的
exit transition
捕获transition view
的终止状态 -
A 的
exit transition
根据前后的状态改变,创建 Animator 后并运行
-
-
当 Activity B 启动
-
Framework 遍历 B 的场景树,并判断哪些 view 会进入,将这些 view 初始化为
INVISIBLE
-
B 的
enter transition
捕获transition view
的初始状态 -
Framework 设置全部的
transition view
为VISIBLE
-
在下一帧的时候,B 的
enter transition
捕获transition view
的终止状态 -
B 的
enter transition
根据前后的状态改变,创建Animator
后并运行
-
3.2 演示效果 1

3.3 Content Transition 的对象
看到这里,相信大家已经对 Activity Content Transition 已经有了比较清晰的理解了。不过如果仔细查看演示效果,发现还是有问题,那就是页面中的 WebView
并没有做动画。那么问题来了:
-
什么样的对象可以当做 Transition Objects
-
能否把 ViewGroup 和它的 children 当做一个整体做动画
查看源码:
/** @hide */
@Override
public void captureTransitioningViews(List<View> transitioningViews) {
if (getVisibility() != View.VISIBLE) {
return;
}
if (isTransitionGroup()) {
transitioningViews.add(this);
} else {
int count = getChildCount();
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
child.captureTransitioningViews(transitioningViews);
}
}
}
我们会发现如果接口 isTransitionGroup()
返回 true
的话,那这个 View
或 ViewGroup
就可以当做一个整体来对待。然后再查看下 WebView.isTransitionGroup()
的原始返回值,发现是 false
。到这里,我们就发现前面页面中的 WebView
并没有做动画的原因了,由于 Framework 捕获 WebView
的时候,isTransitionGroup()
返回 false
, 由此开始遍历 WebView
的子控件,然后 getChildCount()
返回 0
,那这里可以理解为 WebView
被忽略过去了,导致我们设置的 Content Transition
动画无法在 WebView
上生效。我们只需调用 WebView.setTransitionGroup(true)
就能让系统捕获住 WebView
。
3.4 演示效果 2

说明:当点击最下面中间的按钮之后,调用了 setTransitionGroup
之后,WebView
也开始跟着做动画了
4 Acvitivity - Shared Element Transition
4.1 概念及 api
Shared Element Transition
的 概念 :定义的共享 view
,在场景切换的时候,触发从场景1运动到场景2的对应的位置的动画
相关的 api 有:
-
setSharedElementEnterTransition()
-
setSharedElementReturnTransition()
-
setSharedElementExitTransition()
-
setSharedElementReenterTransition()
4.2 Shared Element Transition 触发过程
-
当 Activity A 启动 B,B 被创建,并且 B 的场景树被 measured 和 laid out 在一个透明的窗口
-
Framework 将 B 中的
shared element
放置在 A 对应的位置上( resize和reposition ) -
B
enter transition
捕获shared element
在 B 上的终止状态 -
B
enter transition
比较shared element
的初始和终止状态,创建Animator
-
Framework 隐藏 A 上的
shared element
,并运行Animator
-
当
Animator
结束或者将要结束的时候,B 的窗口背景色逐渐转为不透明,并触发 B 的enter content transition
注:默认情况下,在 transition
运行过程中 shared element
在整个场景树的顶层绘制。可以通过调用 Window#setSharedElementsUseOverlay(false)
来禁用默认效果
4.3 Shared Element Transition 对象
和 Content Transition
不同的是,Shared Element
需要主动设置。
为了将上面的演示结果中悬浮按钮指定为一个 Shared Element
,程序猿需要写如下代码:
Transition1Activity.java
ActivityOptions transitionActivityOptions = ActivityOptions.makeSceneTransitionAnimation(
Transition1Activity.this,
Pair.create(mFabButton, "fab"));
Transition1Activity.xml
<Button
android:id="@+id/fab_button"
android:layout_width="@dimen/fab_big_size"
android:layout_height="@dimen/fab_big_size"
android:transitionName="fab" />
Transition2Activity.xml
<Button
android:id="@+id/fab_button"
android:layout_width="@dimen/fab_size"
android:layout_height="@dimen/fab_big_size"
android:transitionName="fab" />
说明:对于 mFabButton
来说,需要在当前 Activity 的 xml 文件中指定 transitionName
,和后面跳转的 Activity 的 xml 文件中也指定相同的 transitionName
。在 java 代码中,明确指定控件对象和设定的 transitionName
。
4.4 Shared Element Transition 延迟启动
-
为什么需要延迟启动
-
当
shared element
在 Fragment 中,FragmentTransactions 不会被立马执行 -
当
shared element
是大尺寸高分辨率的图片时,会触发一次额外的排版 -
当
shared element
依赖于异步加载的数据时,导致排版不及时
-
- 如何延迟启动
Android 给我们提供了 2 个接口 postponeEnterTransition()
和 startPostponedEnterTransition()
具体使用代码如下:
postponeEnterTransition();
mSharedElement.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
mSharedElement.getViewTreeObserver().removeOnPreDrawListener(this);
mSharedElement.requestLayout();
startPostponedEnterTransition();
return true;
}
});
4.5 演示效果

4.6 注意点
-
在调用
postponeEnterTransition
之后,别忘记调用startPostponeEnterTransition
,忘记调用将导致 app 进入一种死锁状态,无法进入下一个 activity/Fragment -
延迟时间最好不要超过1秒钟