Fragment API 提供了两种方法来使用运动效果和转换,以便在导航期间直观地连接 Fragment。其中一种是 Animation 框架,它同时使用 Animation
和 Animator
。另一种是 Transition 框架,其中包括共享元素转场。
您可以为 Fragment 进入和退出效果以及 Fragment 之间共享元素的转场指定自定义效果。
- 进入效果决定了 Fragment 如何进入屏幕。例如,您可以创建一个效果,在导航到 Fragment 时,让其从屏幕边缘滑入。
- 退出效果决定了 Fragment 如何退出屏幕。例如,您可以创建一个效果,在离开 Fragment 时,让其淡出屏幕。
- 共享元素转场决定了两个 Fragment 之间共享的视图如何在其间移动。例如,当 Fragment B 可见时,Fragment A 中
ImageView
中显示的一张图片会转场到 Fragment B。
设置动画
首先,您需要为进入和退出效果创建动画,这些动画会在导航到新 Fragment 时运行。您可以将动画定义为 Tween 动画资源。这些资源允许您定义 Fragment 在动画期间应如何旋转、拉伸、淡入淡出和移动。例如,您可能希望当前 Fragment 淡出,而新 Fragment 从屏幕右边缘滑入,如图 1 所示。

这些动画可以在 res/anim
目录中定义
<!-- res/anim/fade_out.xml -->
<?xml version="1.0" encoding="utf-8"?>
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="@android:integer/config_shortAnimTime"
android:interpolator="@android:anim/decelerate_interpolator"
android:fromAlpha="1"
android:toAlpha="0" />
<!-- res/anim/slide_in.xml -->
<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="@android:integer/config_shortAnimTime"
android:interpolator="@android:anim/decelerate_interpolator"
android:fromXDelta="100%"
android:toXDelta="0%" />
您还可以为弹出返回堆栈时运行的进入和退出效果指定动画,这可能发生在用户点击“向上”或“返回”按钮时。这些动画称为 popEnter
和 popExit
动画。例如,当用户返回到上一个屏幕时,您可能希望当前 Fragment 从屏幕右边缘滑出,并且上一个 Fragment 淡入。

popEnter
和 popExit
动画。当前 Fragment 从屏幕右侧滑出,而上一个 Fragment 淡入。这些动画可以定义如下
<!-- res/anim/slide_out.xml -->
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="@android:integer/config_shortAnimTime"
android:interpolator="@android:anim/decelerate_interpolator"
android:fromXDelta="0%"
android:toXDelta="100%" />
<!-- res/anim/fade_in.xml -->
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="@android:integer/config_shortAnimTime"
android:interpolator="@android:anim/decelerate_interpolator"
android:fromAlpha="0"
android:toAlpha="1" />
定义动画后,通过调用 FragmentTransaction.setCustomAnimations()
来使用它们,传入动画资源的资源 ID,如以下示例所示
Kotlin
supportFragmentManager.commit { setCustomAnimations( R.anim.slide_in, // enter R.anim.fade_out, // exit R.anim.fade_in, // popEnter R.anim.slide_out // popExit ) replace(R.id.fragment_container, fragment) addToBackStack(null) }
Java
Fragment fragment = new FragmentB(); getSupportFragmentManager().beginTransaction() .setCustomAnimations( R.anim.slide_in, // enter R.anim.fade_out, // exit R.anim.fade_in, // popEnter R.anim.slide_out // popExit ) .replace(R.id.fragment_container, fragment) .addToBackStack(null) .commit();
设置转场
您还可以使用转场来定义进入和退出效果。这些转场可以在 XML 资源文件中定义。例如,您可能希望当前 Fragment 淡出,而新 Fragment 从屏幕右边缘滑入。这些转场可以定义如下
<!-- res/transition/fade.xml -->
<fade xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="@android:integer/config_shortAnimTime"/>
<!-- res/transition/slide_right.xml -->
<slide xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="@android:integer/config_shortAnimTime"
android:slideEdge="right" />
定义转场后,通过调用进入 Fragment 上的 setEnterTransition()
以及退出 Fragment 上的 setExitTransition()
来应用它们,传入展开的转场资源的资源 ID,如以下示例所示
Kotlin
class FragmentA : Fragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val inflater = TransitionInflater.from(requireContext()) exitTransition = inflater.inflateTransition(R.transition.fade) } } class FragmentB : Fragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val inflater = TransitionInflater.from(requireContext()) enterTransition = inflater.inflateTransition(R.transition.slide_right) } }
Java
public class FragmentA extends Fragment { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); TransitionInflater inflater = TransitionInflater.from(requireContext()); setExitTransition(inflater.inflateTransition(R.transition.fade)); } } public class FragmentB extends Fragment { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); TransitionInflater inflater = TransitionInflater.from(requireContext()); setEnterTransition(inflater.inflateTransition(R.transition.slide_right)); } }
Fragment 支持 AndroidX 转场。虽然 Fragment 也支持 框架转场,但我们强烈建议使用 AndroidX 转场,因为它们支持 API 级别 14 及更高版本,并且包含旧版框架转场中不存在的 bug 修复。
使用共享元素转场
作为 Transition 框架的一部分,共享元素转场决定了在 Fragment 转场期间,两个 Fragment 之间对应的视图如何移动。例如,您可能希望 Fragment A 中 ImageView
中显示的一张图片在 Fragment B 可见后转场到 Fragment B,如图 3 所示。

概括来说,以下是使用共享元素进行 Fragment 转场的方法
- 为每个共享元素视图分配唯一的转场名称。
- 将共享元素视图和转场名称添加到
FragmentTransaction
。 - 设置共享元素转场动画。
首先,您必须为每个共享元素视图分配唯一的转场名称,以便可以将视图从一个 Fragment 映射到下一个。使用 ViewCompat.setTransitionName()
在每个 Fragment 布局中共享元素上设置转场名称,这提供了对 API 级别 14 及更高版本的兼容性。例如,Fragment A 和 B 中 ImageView
的转场名称可以按如下方式分配
Kotlin
class FragmentA : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { ... val itemImageView = view.findViewById<ImageView>(R.id.item_image) ViewCompat.setTransitionName(itemImageView, “item_image”) } } class FragmentB : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { ... val heroImageView = view.findViewById<ImageView>(R.id.hero_image) ViewCompat.setTransitionName(heroImageView, “hero_image”) } }
Java
public class FragmentA extends Fragment { @Override public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { ... ImageView itemImageView = view.findViewById(R.id.item_image); ViewCompat.setTransitionName(itemImageView, “item_image”); } } public class FragmentB extends Fragment { @Override public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { ... ImageView heroImageView = view.findViewById(R.id.hero_image); ViewCompat.setTransitionName(heroImageView, “hero_image”); } }
要将共享元素包含在 Fragment 转场中,您的 FragmentTransaction
必须知道每个共享元素的视图如何从一个 Fragment 映射到下一个。通过调用 FragmentTransaction.addSharedElement()
将每个共享元素添加到您的 FragmentTransaction
,传入视图以及下一个 Fragment 中对应视图的转场名称,如以下示例所示
Kotlin
val fragment = FragmentB() supportFragmentManager.commit { setCustomAnimations(...) addSharedElement(itemImageView, “hero_image”) replace(R.id.fragment_container, fragment) addToBackStack(null) }
Java
Fragment fragment = new FragmentB(); getSupportFragmentManager().beginTransaction() .setCustomAnimations(...) .addSharedElement(itemImageView, “hero_image”) .replace(R.id.fragment_container, fragment) .addToBackStack(null) .commit();
要指定共享元素如何从一个 Fragment 转场到下一个,您必须在导航到的 Fragment 上设置进入转场。在 Fragment 的 onCreate()
方法中调用 Fragment.setSharedElementEnterTransition()
,如以下示例所示
Kotlin
class FragmentB : Fragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) sharedElementEnterTransition = TransitionInflater.from(requireContext()) .inflateTransition(R.transition.shared_image) } }
Java
public class FragmentB extends Fragment { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Transition transition = TransitionInflater.from(requireContext()) .inflateTransition(R.transition.shared_image); setSharedElementEnterTransition(transition); } }
shared_image
过渡定义如下
<!-- res/transition/shared_image.xml -->
<transitionSet>
<changeImageTransform />
</transitionSet>
Transition
的所有子类都支持作为共享元素转场。如果您想创建自定义 Transition
,请参阅创建自定义转场动画。上一个示例中使用的 changeImageTransform
是可用的预构建转场之一,您可以利用它。您可以在 Transition
类的 API 参考中找到其他 Transition
子类。
默认情况下,共享元素进入转场也用作共享元素的返回转场。返回转场决定了当 Fragment 事务从返回堆栈中弹出时,共享元素如何转场回上一个 Fragment。如果您想指定不同的返回转场,可以在 Fragment 的 onCreate()
方法中使用 Fragment.setSharedElementReturnTransition()
来实现。
预测性返回兼容性
您可以将预测性返回与许多(但不是所有)跨 Fragment 动画结合使用。在实现预测性返回时,请牢记以下注意事项
- 导入
Transitions 1.5.0
或更高版本以及Fragments 1.7.0
或更高版本。 Animator
类及其子类以及 AndroidX Transition 库都受支持。Animation
类和框架Transition
库不受支持。- 预测性 Fragment 动画仅适用于运行 Android 14 或更高版本的设备。
- 预测性返回支持
setCustomAnimations
、setEnterTransition
、setExitTransition
、setReenterTransition
、setReturnTransition
、setSharedElementEnterTransition
和setSharedElementReturnTransition
。
如需了解详情,请参阅添加对预测性返回动画的支持。
推迟转场
在某些情况下,您可能需要将 Fragment 转场推迟一小段时间。例如,您可能需要等到进入 Fragment 中的所有视图都已测量和布局完毕,以便 Android 能够准确捕获它们在转场中的开始和结束状态。
此外,您的转场可能需要推迟,直到加载了一些必要数据。例如,您可能需要等到共享元素的图片加载完毕。否则,如果图片在转场期间或之后完成加载,转场可能会显得不协调。
要推迟转场,您必须首先确保 Fragment 事务允许重新排序 Fragment 状态更改。要允许重新排序 Fragment 状态更改,请调用 FragmentTransaction.setReorderingAllowed()
,如以下示例所示
Kotlin
val fragment = FragmentB() supportFragmentManager.commit { setReorderingAllowed(true) setCustomAnimation(...) addSharedElement(view, view.transitionName) replace(R.id.fragment_container, fragment) addToBackStack(null) }
Java
Fragment fragment = new FragmentB(); getSupportFragmentManager().beginTransaction() .setReorderingAllowed(true) .setCustomAnimations(...) .addSharedElement(view, view.getTransitionName()) .replace(R.id.fragment_container, fragment) .addToBackStack(null) .commit();
要推迟进入转场,请在进入 Fragment 的 onViewCreated()
方法中调用 Fragment.postponeEnterTransition()
Kotlin
class FragmentB : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { ... postponeEnterTransition() } }
Java
public class FragmentB extends Fragment { @Override public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { ... postponeEnterTransition(); } }
加载完数据并准备好开始转场后,调用 Fragment.startPostponedEnterTransition()
。以下示例使用 Glide 库将图片加载到共享的 ImageView
中,并将相应的转场推迟到图片加载完成。
Kotlin
class FragmentB : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { ... Glide.with(this) .load(url) .listener(object : RequestListener<Drawable> { override fun onLoadFailed(...): Boolean { startPostponedEnterTransition() return false } override fun onResourceReady(...): Boolean { startPostponedEnterTransition() return false } }) .into(headerImage) } }
Java
public class FragmentB extends Fragment { @Override public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { ... Glide.with(this) .load(url) .listener(new RequestListener<Drawable>() { @Override public boolean onLoadFailed(...) { startPostponedEnterTransition(); return false; } @Override public boolean onResourceReady(...) { startPostponedEnterTransition(); return false; } }) .into(headerImage) } }
在处理用户网络连接缓慢等情况时,您可能需要推迟的转场在一定时间后开始,而不是等待所有数据加载完毕。对于这些情况,您可以改为在进入 Fragment 的 onViewCreated()
方法中调用 Fragment.postponeEnterTransition(long, TimeUnit)
,传入持续时间和时间单位。一旦指定时间过去,推迟的转场便会自动开始。
将共享元素转场与 RecyclerView
结合使用
推迟的进入转场不应在进入 Fragment 中的所有视图都已测量和布局完毕之前开始。使用 RecyclerView
时,您必须等待任何数据加载完毕,并且 RecyclerView
项准备好绘制,然后才能开始转场。示例如下
Kotlin
class FragmentA : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { postponeEnterTransition() // Wait for the data to load viewModel.data.observe(viewLifecycleOwner) { // Set the data on the RecyclerView adapter adapter.setData(it) // Start the transition once all views have been // measured and laid out (view.parent as? ViewGroup)?.doOnPreDraw { startPostponedEnterTransition() } } } }
Java
public class FragmentA extends Fragment { @Override public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { postponeEnterTransition(); final ViewGroup parentView = (ViewGroup) view.getParent(); // Wait for the data to load viewModel.getData() .observe(getViewLifecycleOwner(), new Observer<List<String>>() { @Override public void onChanged(List<String> list) { // Set the data on the RecyclerView adapter adapter.setData(it); // Start the transition once all views have been // measured and laid out parentView.getViewTreeObserver() .addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { @Override public boolean onPreDraw(){ parentView.getViewTreeObserver() .removeOnPreDrawListener(this); startPostponedEnterTransition(); return true; } }); } }); } }
请注意,Fragment 视图的父级上设置了 ViewTreeObserver.OnPreDrawListener
。这是为了确保 Fragment 的所有视图都已测量和布局完毕,因此在开始推迟的进入转场之前已准备好绘制。
在使用共享元素转场与 RecyclerView
结合时,另一个需要考虑的问题是,您无法在 RecyclerView
项的 XML 布局中设置转场名称,因为任意数量的项共享该布局。必须分配一个唯一的转场名称,以便转场动画使用正确的视图。
您可以通过在绑定 ViewHolder
时为每个项的共享元素分配唯一的转场名称。例如,如果每个项的数据包含唯一的 ID,则可以将其用作转场名称,如以下示例所示
Kotlin
class ExampleViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { val image = itemView.findViewById<ImageView>(R.id.item_image) fun bind(id: String) { ViewCompat.setTransitionName(image, id) ... } }
Java
public class ExampleViewHolder extends RecyclerView.ViewHolder { private final ImageView image; ExampleViewHolder(View itemView) { super(itemView); image = itemView.findViewById(R.id.item_image); } public void bind(String id) { ViewCompat.setTransitionName(image, id); ... } }
更多资源
如需详细了解 Fragment 转场,请参阅以下更多资源。