What is Transition?
从一个页面切换到另一个页面,布局发生变化的时候,添加转场动画不管是触觉、视觉还是前后页面逻辑关系上,都会改善用户体验。
transition framework是AndroidX Transition Library中提供的一套API,兼容到14. 通过它可以简单地实现两个视图元素之间地动画切换。提供了几个内建动画类型,同时也允许用户自定义。
Transition 包含了完成场景切换动画所需的信息,它本身是一个抽象类,其子类可能包含多个子转场动画信息,TransitionSet或者自定义动画。任何一个转场动画包括两个主要部分:1. 捕获属性值,2. 属性值发生变化时应用的动画效果。
Note: 由于在屏幕上展示渲染方式, Transition与SurfaceView/TextureView同时使用效果可能不佳。比如SurfaceView,它的更新时在一个non-UI thread中进行,所以transitions带来的view更新(move,resize, UI thread)与渲染SurfaceView的不同步;TextureView与Transitions大体上兼容性更好,但是一些特殊的动画,比如Fade,由于它依赖于ViewOverlay方法,但是在TextureView上并不生效。
How to Apply Transition?
Mainly: Start an activity using an animation
Define Transition style
- 几种内建transition类型:
例如:fade:
<fade xmlns:android="http://schemas.android.com/apk/res/android"/>
expode:
<explode xmlns:android="http://schemas.android.com/apk/res/android"/>
move:
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android">
<changeBounds/>
<changeTransform/>
<changeClipBounds/>
<changeImageTransform/>
</transitionSet>
- 自定义style
新建transition动画,默认在main/res/transition目录下
Activity的theme中指定
<style name="BaseAppTheme" parent="android:Theme.Material">
<!-- enable window content transitions -->
<item name="android:windowActivityTransitions">true</item>
<!-- specify enter and exit transitions -->
<item name="android:windowEnterTransition">@transition/explode</item>
<item name="android:windowExitTransition">@transition/explode</item>
<!-- specify shared element transitions -->
<item name="android:windowSharedElementEnterTransition">
@transition/change_image_transform</item>
<item name="android:windowSharedElementExitTransition">
@transition/change_image_transform</item>
</style>
Start an activity using transitions
startActivity(intent, ActivityOptions.makeSceneTransitionAnimation(this).toBundle());
Start an activity with shared element
// get the element that receives the click event
val imgContainerView = findViewById<View>(R.id.img_container)
// get the common element for the transition in this activity
val androidRobotView = findViewById<View>(R.id.image_small)
// define a click listener
imgContainerView.setOnClickListener( {
val intent = Intent(this, Activity2::class.java)
// create the transition animation - the images in the layouts
// of both activities are defined with android:transitionName="robot"
val options = ActivityOptions
.makeSceneTransitionAnimation(this, androidRobotView, "robot")
// start the new activity
startActivity(intent, options.toBundle())
})
multiple shared elements
// Rename the Pair class from the Android framework to avoid a name clash
import android.util.Pair as UtilPair
...
val options = ActivityOptions.makeSceneTransitionAnimation(this,
UtilPair.create(view1, "agreedName1"),
UtilPair.create(view2, "agreedName2"))
常见问题
Approach #1: Exclude the status bar and navigation bar from the window's default exit/enter fade transition
The reason why the navigation/status bar are fading in and out during the transition is because by default all non-shared views (including the navigation/status bar backgrounds) will fade out/in in your calling/called Activitys respectively once the transition begins. You can, however, easily get around this by excluding the navigation/status bar backgrounds from the window's default exit/enter Fade transition. Simply add the following code to your Activitys' onCreate() methods:
Transition fade = new Fade();
fade.excludeTarget(android.R.id.statusBarBackground, true);
fade.excludeTarget(android.R.id.navigationBarBackground, true);
getWindow().setExitTransition(fade);
getWindow().setEnterTransition(fade);
This transition could also be declared in the activity's theme using XML (i.e. in your own res/transition/window_fade.xml file):
<?xml version="1.0" encoding="utf-8"?>
<fade xmlns:android="http://schemas.android.com/apk/res/android">
<targets>
<target android:excludeId="@android:id/statusBarBackground"/>
<target android:excludeId="@android:id/navigationBarBackground"/>
</targets>
</fade>
Approach #2: Add the status bar and navigation bar as shared elements - my choice
In calling Activity, use the following code to start the Activity:
View statusBar = findViewById(android.R.id.statusBarBackground);
View navigationBar = findViewById(android.R.id.navigationBarBackground);
List<Pair<View, String>> pairs = new ArrayList<>();
if (statusBar != null) {
pairs.add(Pair.create(statusBar, Window.STATUS_BAR_BACKGROUND_TRANSITION_NAME));
}
if (navigationBar != null) {
pairs.add(Pair.create(navigationBar, Window.NAVIGATION_BAR_BACKGROUND_TRANSITION_NAME));
}
pairs.add(Pair.create(mSharedElement, mSharedElement.getTransitionName()));
Bundle options = ActivityOptions.makeSceneTransitionAnimation(activity,
pairs.toArray(new Pair[pairs.size()])).toBundle();
startActivity(new Intent(context, NextActivity.class), options);
Add the following code in called Activity's onCreate() method in order to prevent the status bar and navigation bar from "blinking" during the transition:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_next);
// Postpone the transition until the window's decor view has
// finished its layout.
postponeEnterTransition();
final View decor = getWindow().getDecorView();
decor.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
decor.getViewTreeObserver().removeOnPreDrawListener(this);
startPostponedEnterTransition();
return true;
}
});
}
Adding the status bar and navigation bar backgrounds as shared elements will force them to be drawn on top of the window's default exit/enter fade transition, meaning that they will not fade during the transition.
transition给activity生命周期带来的影响
常见情况下是一样的,例如:Activity A -> B -> finish B -> finish A, 生命周期变化与加了transition动画是一样的,为:
onCreate: A -> onResume: A
onPause: A -> onCreate: B -> onResume: B
onPause: B -> onResume: A -> onDestroy: B
onPause: A -> onDestroy: A
BUT,对于中途finish某个Activity,例如:Activity A -> B -> C同时finish B, 正常的生命周期变化是:
onCreate: A -> onResume: A
onPause: A -> onCreate: B -> onResume: B
onPause: B -> onCreate: C -> onResume: C -> onDestroy: B
如果,Activity A -> transit B -> transit C同时finish B, 生命周期变化是:
onCreate: A -> onResume: A
onPause: A -> onCreate: B -> onResume: B
onPause: B -> onCreate: C -> onResume: C -> onDestroy: B -> onResume: A -> onPause: A
// or
onPause: B -> onCreate: C -> onResume: C -> onResume: A -> onPause: A -> onDestroy: B
除了对生命周期带来异常外,转场动画本身也有出现闪烁,why? how to fix?
why:
对于transition启动的Activity,就像启动那样makeSceneTransitionAnimation,它在结束的时候虽然可以指定要不要transition但是默认的会有一个scale的动画,这个是没法重写的,(有人说overridePendingTransition可以抹去,但是试过不行)。
how:
- start C without transition
- finish B with delay
这两种方式都能使生命周期恢复正常onPause: B -> onCreate: C -> onResume: C -> onDestroy: B,但是第一种会导致转场动画失效,从体验上还是第二种更优雅一些。