普通转场动画
1. 准备工作
方式一
在Activity.onCreate()
的setContentView()
前调用以下代码。
getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS);
方式二
在 AndroidManifest.xml 里:
<application
...
android:windowContentTransitions="true"/>
2. 创建Transition
方式一:通过XML创建
- 创建 res/transition/details_window_return_transition.xml 文件:
<?xml version="1.0" encoding="utf-8"?> <transitionSet xmlns:android="http://schemas.android.com/apk/res/android" android:transitionOrdering="together" android:duration="500"> <fade> <targets> <target android:excludeId="@android:id/statusBarBackground"/> <target android:excludeId="@android:id/navigationBarBackground"/> </targets> </fade> <slide android:slideEdge="top"> <targets> <target android:targetId="@id/details_header_container"/> </targets> </slide> <slide android:slideEdge="bottom"> <targets> <target android:targetId="@id/details_text_container"/> </targets> </slide> </transitionSet>
- 在代码中载入 XML 文件定义的
Transition
:TransitionInflater transitionInflater = TransitionInflater.from(this); Transition transition = transitionInflater.inflateTransition(R.transition.details_window_return_transition);
方式二:通过代码创建
Explode explode = new Explode();
explode.addTarget(android.R.id.statusBarBackground);
explode.excludeTarget(android.R.id.navigationBarBackground, true);
Fade fade = new Fade();
Slide slide = new Slide();
TransitionSet transitionSet = new TransitionSet()
.setDuration(500)
.addTransition(explode)
.addTransition(slide)
.addTransition(fade);
3. 设置Transition
代码设置
设置方法
- 在
Activity
的onCreate()
或onCreateView()
中使用getWindow()
设置动画。 - 在
Fragment
的onCreate()
或onCreateView()
中使用getActivity().getWindow()
设置动画。
动画种类
-
setExitTransition()
:当 A 启动 B 时,使 A 中的View
退出场景的transition
-
setEnterTransition()
:当 A 启动 B 时,使 B 中的View
进入场景的transition
-
setReturnTransition()
:当 B 返回 A 时,使 B 中的View
退出场景的transition
-
setReenterTransition()
:当 B 返回 A 时,使 A 中的View
进入场景的transition
XML设置
在 AndroidManifest.xml 里:
<activity
...
android:theme="@style/AppTheme.Details"/>
在 res/values/theme.xml 里:
<resources>
<style name="AppTheme.Details" parent="android:Theme.Material.Light.NoActionBar">
<item name="android:statusBarColor">@android:color/black</item>
<item name="android:windowExitTransition">@transition/details_window_enter_transition</item>
<item name="android:windowEnterTransition">@transition/...</item>
<item name="android:windowReenterTransition">@transition/...</item>
<item name="android:windowReturnTransition">@transition/...</item>
</style>
</resources>
4. 针对ViewGroup
处理
- 默认情况下,无法将
ViewGroup
当做一个view
来处理,需要在ViewGroup
的对应 XML 文件中开启TransitionGroup
属性。设置ViewGroup
背景色属性也有同样的效果,即便背景是透明的。 - 若一个
Transition
中包含了没有开启TransitionGroup
属性的ViewGroup
的targetId
,则此Transition
不会运行。
5. 启动Activity
startActivity(intent, ActivityOptionsCompat.makeSceneTransitionAnimation(getActivity(), (Pair<View, String>[]) null).toBundle());
监听Transition
动画
getWindow().getEnterTransition().addListener(new TransitionListenerAdapter() { });
常见问题
- 如果没有任何效果,检查是否把 动画种类 弄错了。
共享元素转场动画
简单方式
1. 为共享元素设置TransitionName
为两个场景想要共享的View
调用setTransitionName()
为相同可标识的字符串。
view.setTransitionName(/* 可标识字符串 */);
2. 启动Activity
ActivityOptionsCompat optionsCompat = ActivityOptionsCompat.makeSceneTransitionAnimation(getActivity(), view, view.getTransitionName());
startActivity(intent, optionsCompat.toBundle());
常见问题
- 状态栏 和 导航栏 显示不正常:可以把 导航栏 和 状态栏 作为共享元素。
View statusBar = getActivity().findViewById(android.R.id.statusBarBackground);
View navigationBar = getActivity().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));
}
// noinspection unchecked
startActivity(intent, ActivityOptionsCompat.makeSceneTransitionAnimation(getActivity(), pairs.toArray(new Pair[pairs.size()])).toBundle());
复杂方式
- 以下场景需要使用复杂方式来实现共享元素动画:
- 当共享的
View
延迟加载时,如网络请求、Fragment
或ViewPager
等场景,需要复杂方式来处理。 - 共享的
View
在 A启动B 和 B返回A 发生了改变,导致不是同一个View
时。
- 当共享的
0. 执行流程
工具类:SharedElementTransitionHelper
1. 为共享元素设置TransitionName
为两个场景想要共享的View
设置setTransitionName()
为相同可标识的字符串。
view.setTransitionName(/* 可标识字符串 */);
2. 手动设置共享元素
方法简介
-
setEnterSharedElementCallback()
:- 设置的是 A->B 时, B 的动画。
- 同时也是 B->A 时, B 的动画,虽然动画是相反的,但是会自动做倒序处理,可以看 执行流程 。
- 若 B 是
Activity
,则是由Activity.makeSceneTransitionAnimation()
触发的。若 B 是Fragment
,则是在Fragment
的attached()
或detached()
触发的。
-
setExitSharedElementCallback()
:- 设置的是 A->B 时, A 的动画。
- 同时也是 B->A 时, A 的动画,虽然动画是相反的,但是会自动做倒序处理,可以看 执行流程 。
- 若 A 是
Activity
,则是由Activity.makeSceneTransitionAnimation()
触发的。若 A 是Fragment
,则是在Fragment
的attached()
或detached()
触发的。
步骤
- 定义
SharedElementCallback
对象。重写SharedElementCallback.onMapSharedElements()
。 - 在
Activity
或者Fragment
中调用setEnterSharedElementCallback()
或setExitSharedElementCallback()
,重新设置SharedElementCallback
对象以实现重新设置 共享元素 。
简单示例
背景
-
A 、 B 均为
Activity
。 - 在 B 返回 A 时, 共享元素 发生了变化。
A.java
必须调用子Activity
的setResult()
才会调用父Activity
的onActivityReenter()
。
// 在Activity.onActivityReenter()中设置
SharedElementTransitionHelper.setExitSharedElementCallbackOnce(this, new SharedElementCallback() {
@Override
public void onMapSharedElements(List<String> names, Map<String, View> sharedElements) {
View newSharedElement = /* 获取新共享元素 */;
String newTransitionName = /* 获取新TransitionName */;
names.clear();
sharedElements.clear();
names.add(newTransitionName);
sharedElements.put(newTransitionName, newSharedElement);
}
});
B.java
此处不要在Activity.supportFinishAfterTransition()
中设置,而应该在Activity.finishAfterTransition()
。
// 在Activity.finishAfterTransition()中设置
// 必须调用setResult()才会调用父Activity的onActivityReenter()
setResult(RESULT_OK);
SharedElementTransitionHelper.setEnterSharedElementCallbackOnce(this, new SharedElementCallback() {
@Override
public void onMapSharedElements(List<String> names, Map<String, View> sharedElements) {
View newSharedElement = /* 获取新共享元素 */;
String newTransitionName = /* 获取新TransitionName */;
names.clear();
sharedElements.clear();
names.add(newTransitionName);
sharedElements.put(newTransitionName, newSharedElement);
}
});
4. 延迟和开始共享元素动画
- 用于处理在 转场动画 开始时, 共享元素 尚未加载的情况,此时延迟 转场动画 ,直至 共享元素 加载完毕。当有多个 共享元素 的时候也要确保所有这些 共享元素 全部加载完毕。
- 在场景刚刚开始的地方,如
Activity.onCreate()
:SharedElementTransitionHelper.pauseEnterTranstion(/* Activity */);
- 在能访问到 共享元素 且 共享元素 加载完毕的地方,如
SharedElementTransitionHelper
中监听了View
的 PreDraw 阶段:SharedElementTransitionHelper.startEnterTranstionWhenViewIsReady(getActivity(), /* 共享元素 */);
5. 启动Activity
ActivityOptionsCompat optionsCompat = ActivityOptionsCompat.makeSceneTransitionAnimation(getActivity(), view, view.getTransitionName());
startActivity(intent, optionsCompat.toBundle());
监听共享元素动画
getWindow().getSharedElementExitTransition().addListener(/* SharedElementTransitionHelper#TransitionListenerAdapter */);
getWindow().getSharedElementEnterTransition().addListener(/* SharedElementTransitionHelper#TransitionListenerAdapter */);
重叠属性
控制两个场景是否允许重叠。
代码
getWindow().setAllowEnterTransitionOverlap(false);
getWindow().setAllowReturnTransitionOverlap(false);
效果
自定义共享元素动画【Todo】
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android">
<explode/>
<changeBounds/>
<changeTransform/>
<changeClipBounds/>
<changeImageTransform/>
</transitionSet>
- 路径:
changeBounds
- 大小、缩放:
changeTransform
- 图片矩阵变换:
ChangeImageTransform
- 裁剪区域:
ChangeClipBounds
教程
Material-Animations
Android Transition Framework
Postponed Shared Element Transitions