Fragment API 提供了两种方法,供您利用运动效果和转换效果在导航期间直观地衔接 fragment。一个是动画框架,它使用 Animation
和 Animator
。另一个是转换框架,其中包括共享元素转换。
您可以指定进入和退出 Fragment 时的自定义效果,以及 Fragment 之间共享元素的自定义过渡效果。
- “进入”效果决定了 Fragment 进入屏幕的方式。例如,您可以创建一个效果,在导航到 Fragment 时使其从屏幕边缘滑入。
- “退出”效果决定了 Fragment 退出屏幕的方式。例如,您可以创建一个效果,在导航离开 Fragment 时使其淡出。
- “共享元素转换”决定了两个 Fragment 之间共享的视图如何在 Fragment 之间移动。例如,当 Fragment B 出现时,在 Fragment A 的
ImageView
中显示的图像将转换到 Fragment B 中。
设置动画
首先,您需要为进入和退出效果创建动画,这些动画会在导航到新的 Fragment 时出现。您可以将动画定义为补间动画资源。您可以通过这些资源定义 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 传入动画资源,如以下示例所示:
val fragment = FragmentB()
supportFragmentManager.commit {
setCustomAnimations(
enter = R.anim.slide_in,
exit = R.anim.fade_out,
popEnter = R.anim.fade_in,
popExit = R.anim.slide_out
)
replace(R.id.fragment_container, fragment)
addToBackStack(null)
}
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 传入这些资源,如以下示例所示:
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)
}
}
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 和更高级别中得到支持,并且包含旧版本的框架转换中不提供的问题修复功能。
使用共享元素转换
共享元素转换是转换框架的一部分,它决定了 Fragment 转换期间,对应视图如何在两个 Fragment 之间移动。例如,您可能希望 Fragment A 上 ImageView
中显示的图片在 B 变为可见后转换到 Fragment B 中,如图 3 所示。

就更高层次而言,以下展示了如何使用共享元素进行 Fragment 转换:
- 为每个共享元素视图指定唯一的转换名称。
- 将共享元素视图和转换名称添加到
FragmentTransaction
。 - 设置共享元素转换动画。
首先,您必须为每个共享元素视图分配唯一的转换名称,以允许视图从一个 Fragment 映射到下一个 Fragment。使用可与 API 级别 14 及更高级别兼容的 ViewCompat.setTransitionName()
为每个 Fragment 布局中的共享元素设置转换名称。例如,Fragment A 和 B 中 ImageView
的转换名称可按如下方式分配:
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”)
}
}
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 映射到下一个 Fragment。通过调用 FragmentTransaction.addSharedElement()
将各个共享元素添加至 FragmentTransaction
,传入视图和下一个 Fragment 中相应视图的转换名称,如以下示例所示:
val fragment = FragmentB()
supportFragmentManager.commit {
setCustomAnimations(...)
addSharedElement(itemImageView, “hero_image”)
replace(R.id.fragment_container, fragment)
addToBackStack(null)
}
Fragment fragment = new FragmentB();
getSupportFragmentManager().beginTransaction()
.setCustomAnimations(...)
.addSharedElement(itemImageView, “hero_image”)
.replace(R.id.fragment_container, fragment)
.addToBackStack(null)
.commit();
如需指定共享元素如何从一个 Fragment 转换到下一个 Fragment,您必须在需导航到的 Fragment 上设置“进入” 转换。在 Fragment 的 onCreate()
方法中调用 Fragment.setSharedElementEnterTransition()
,如以下示例所示:
class FragmentB : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
sharedElementEnterTransition = TransitionInflater.from(requireContext())
.inflateTransition(R.transition.shared_image)
}
}
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 转换。例如,您可能需要稍加等待,以便系统对要进入的 Fragment 中的所有视图完成测量和布局,以便 Android 可以准确捕获转换的起始和结束状态。
此外,您可能需要推迟某些转换,直到已加载一些必要的数据。例如,您可能需要等待系统加载用于共享元素的图像。否则,如果图片在转换期间或转换之后完成加载,转换可能会变得模糊。
如需延迟转换,您必须先确保 Fragment 事务允许重新排序 Fragment 状态更改。如需允许重新排序 Fragment 状态更改,请调用 FragmentTransaction.setReorderingAllowed()
,如以下示例所示:
val fragment = FragmentB()
supportFragmentManager.commit {
setReorderingAllowed(true)
setCustomAnimation(...)
addSharedElement(view, view.transitionName)
replace(R.id.fragment_container, fragment)
addToBackStack(null)
}
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()
:
class FragmentB : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
...
postponeEnterTransition()
}
}
public class FragmentB extends Fragment {
@Override
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
...
postponeEnterTransition();
}
}
完成加载数据并为开始转换准备就绪后,请调用 Fragment.startPostponedEnterTransition()
。以下示例使用 Glide 库将图片加载到共享 ImageView
中,推迟相应的转换直到完成图片加载。
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)
}
}
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
项准备好绘制之后才可以开始转换。示例如下:
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()
}
}
}
}
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,则它可以用作转换名称,如以下示例所示:
class ExampleViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val image = itemView.findViewById<ImageView>(R.id.item_image)
fun bind(id: String) {
ViewCompat.setTransitionName(image, id)
...
}
}
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 转换,请参阅以下其他资源。