Transition
?
Transition
的中文翻译是“过渡”、“转换”、“转变”,这里对于 View Transition
个人粗浅的理解是:View
的场景切换,即 View
的入场和出场。
那么 View transition Aniamtion
就很好理解了,就是当 view 入场或出场时触发的动画。
View Transition
动画
不管是自己实现,还是 Android 系统已经帮我们实现了的机制,大概都能总结为 3 步:
捕获view在开始场景和结束场景中的状态
基于view从一个场景到另一个场景的状态改变,创建动画
执行动画
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
,黄色点击对应自定义的圆形消失动画
简单查看如下:
抽象了 Animator 的概念,使用者并不需要知道底层是通过 Animator 实现的
减少了代码量,仅需要指定view的前后不同状态,Transition会自动创建动画
有利于代码复用,自定义的 View Transition Visibility
可以直接给其他模块复用,且能使代码结构清晰
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
按触发时间划分
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 (共享元素切换)
在调用和被调用的 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()
方法屏蔽
页面的 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
的一部分
Content Transition
的概念:Activity 或 Fragment 切换的时候,定义非共享 view 进入或离开场景的方式(动画)
相关的 api 有:setExitTransition()
, setEnterTransition()
, setReturnTransition()
, setReenterTransition()
假设从 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
后并运行
看到这里,相信大家已经对 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
。
说明:当点击最下面中间的按钮之后,调用了 setTransitionGroup
之后,WebView
也开始跟着做动画了
Shared Element Transition
的 概念 :定义的共享 view
,在场景切换的时候,触发从场景1运动到场景2的对应的位置的动画
相关的 api 有:
setSharedElementEnterTransition()
setSharedElementReturnTransition()
setSharedElementExitTransition()
setSharedElementReenterTransition()
当 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)
来禁用默认效果
和 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
。
为什么需要延迟启动
当 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;
}
});
在调用 postponeEnterTransition
之后,别忘记调用 startPostponeEnterTransition
,忘记调用将导致 app 进入一种死锁状态,无法进入下一个 activity/Fragment
延迟时间最好不要超过1秒钟
相关阅读:Material Design Animation学习小结一
本文来自网易实践者社区,经作者张云龙授权发布。