android-framework-fragment

一、概念

什么是fragment?我们先来看看官方的定义:

Fragment 表示应用界面中可重复使用的一部分。fragment 定义和管理自己的布局,具有自己的生命周期,并且可以处理自己的输入事件。fragment 不能独立存在。它们必须由 activity 或其他 fragment 托管。fragment 的视图层次结构会成为宿主的视图层次结构的一部分,或附加到宿主的视图层次结构。

我们来理解下上述的描述我可以总结如下:

  • Fragment不能独立存在,必须嵌入到Activity中或者其它Fragment里。
  • Fragment具有自己的生命周期,接收它自己的事件,并可以在Activity运行时被添加或删除。
  • Fragment 的ui属于 宿主的ui的一部分,是整块的嵌入。

如果用一句话来形容的话,fragment就是ui的模块化,集成化,芯片化。

那问题来了,这样做的目的是 什么呢?那就让我们来列下fragment的优点:

  • Fragment加载灵活,替换方便。定制你的UI,在不同尺寸的屏幕上创建合适的UI,提高用户体验。
  • 可复用,页面布局可以使用多个Fragment,不同的控件和内容可以分布在不同的Fragment上。
  • 使用Fragment,可以少用一些Activity。一个Activity可以管辖多个Fragment,使得Activity能更加的灵活和复杂。

二、模块化

fragment 允许您将界面划分为离散的区块,从而将模块化和可重用性引入 activity 的界面。activity 是围绕应用的界面放置全局元素(如抽屉式导航栏)的理想位置。相反,Fragment 更适合定义和管理单个屏幕或部分屏幕的界面。

假设有一个响应各种屏幕尺寸的应用。在大屏设备上,您可能希望应用以网格布局显示静态抽屉式导航栏和列表。在小屏设备上,您可能希望应用以线性布局显示底部导航栏和列表。

在 activity 中管理这些变体非常麻烦。将导航元素与内容分离可使此过程更易于管理。然后,activity 负责显示正确的导航界面,而 fragment 采用适当的布局显示列表。

上图中同一屏幕的采用不同屏幕尺寸的两个版本。在左侧,大屏幕包含一个由 activity 控制的抽屉式导航栏和一个由 fragment 控制的网格列表。在右侧,小屏幕包含一个由 activity 控制的底部导航栏和一个由 fragment 控制的线性列表。

将界面划分为 fragment 可让您更轻松地在运行时修改 activity 的外观。当 activity 处于 STARTED 生命周期状态或更高的状态时,可以添加、替换或移除 fragment。此外,可以将这些更改的记录保留在由 activity 管理的返回堆栈中,以便撤消这些更改。

可以在同一 activity 或多个 activity 中使用同一 fragment 类的多个实例,甚至可以将其用作另一个 fragment 的子级。考虑到这一点,请仅为 fragment 提供管理其自身界面所需的逻辑。避免让一个 fragment 依赖于另一个 fragment 或从一个 fragment 操控另一个 fragment。

下是的例子,展示如何在安卓应用程序中实现Fragment的模块化:

2.1 定义fragment

// 创建一个模块化的Fragment,用于展示某个功能或界面

public class MyModuleFragment extends Fragment {

    // 定义该模块的布局和逻辑

    @Override

    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {

        View view = inflater.inflate(R.layout.fragment_my_module, container, false);

        // 可以在此处进行布局的初始化和逻辑的处理

        return view;

    }

    // 可以在此处处理该模块的其他逻辑和事件

}

2.2 定义xml文件

<--简单的定义一个布局文件作为fragment_my_module->

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

    android:layout_width="match_parent"

    android:layout_height="match_parent"

    android:orientation="vertical">

    <TextView

        android:id="@+id/textView"

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:text="This is a Fragment"

        android:textSize="20sp"

        android:layout_gravity="center_horizontal"/>

    <!-- 可以在此处添加其他UI组件 -->

</LinearLayout>

将fragment的加入到目标Activity的布局中---->由于fragment的独立性和复用性,此fragment可以被任何的Activity引用

<--简单的定义一个布局文件作为fragment_my_module->

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

    xmlns:tools="http://schemas.android.com/tools"

    android:layout_width="match_parent"

    android:layout_height="match_parent"

    android:orientation="vertical"

    tools:context=".MainActivity">

    <TextView

        android:layout_width="match_parent"

        android:layout_height="wrap_content"

        android:text="This is the Activity"

        android:textSize="20sp"

        android:gravity="center"

        android:padding="16dp" />

    <FrameLayout

        android:id="@+id/container"

        android:layout_width="match_parent"

        android:layout_height="0dp"

        android:layout_weight="1" />

</LinearLayout>

2.3 代码中加载碎片

public class MainActivity extends AppCompatActivity {

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        // 实例化并添加Fragment到Activity中

        MyModuleFragment moduleFragment = new MyModuleFragment();

        getSupportFragmentManager().beginTransaction()

                .add(R.id.container, moduleFragment)

                .commit();

    }

}

三、生命周期

每个 Fragment 实例都有自己的生命周期。当用户浏览应用并与之互动时,Fragment 会在添加、移除时以及进入或退出屏幕时完成其生命周期内各种状态的转换。

为了管理生命周期,Fragment 会实现 LifecycleOwner,公开可通过 getLifecycle() 方法访问的 Lifecycle 对象。

每种可能的 Lifecycle 状态均在 Lifecycle.State 枚举中表示。

通过在 Lifecycle 的基础上构建 Fragment,够受益于那些可助您实现使用生命周期感知型组件处理生命周期的技术和类。例如,可以使用生命周期感知型组件在屏幕上显示设备的位置。当 Fragment 转为活跃状态时,此组件会自动开始监听,当 Fragment 转为非活跃状态时,此组件会自动停止监听。

作为使用 LifecycleObserver 的替代方法,Fragment 类包含与 Fragment 生命周期中每项变化相对应的回调方法。其中包括 onCreate()onStart()onResume()onPause()onStop() 和 onDestroy()

Fragment 的视图有一个单独的 Lifecycle,独立于 Fragment 的 Lifecycle 进行管理。Fragment 为其视图维护一个 LifecycleOwner,可使用 getViewLifecycleOwner() 或 getViewLifecycleOwnerLiveData() 进行访问。如果生命周期感知型组件只应在 Fragment 的视图存在时执行工作(例如观察只是为了在屏幕上显示的 LiveData),那么有权访问视图的 Lifecycle 会很有用。

本主题详细讨论了 Fragment 生命周期,介绍了一些确定 Fragment 生命周期状态的规则,并展示了 Lifecycle 状态与 Fragment 生命周期回调之间的关系。

3.1 Fragment 和 Fragment 管理器

在实例化 Fragment 之后,它会从 INITIALIZED 状态开始。为使 Fragment 在其剩余的生命周期内顺利完成状态转换,必须将其添加到 FragmentManagerFragmentManager 负责确定其 Fragment 应处于哪个状态,然后将其转为该状态。

在 Fragment 生命周期之外,FragmentManager 还负责将 Fragment 附加到其宿主 Activity,并在 Fragment 不再使用时进行分离。Fragment 类有两个回调方法(onAttach() 和 onDetach()),当发生上述任一事件时,您可以替换这些方法以执行工作。

将 Fragment 添加到 FragmentManager 并附加到其宿主 Activity 后,系统将调用 onAttach() 回调。此时,该 Fragment 处于活跃状态,FragmentManager 管理其生命周期状态。此时,findFragmentById() 等 FragmentManager 方法会返回此 Fragment。

在发生任何生命周期状态变更之前,系统都始终会调用 onAttach()

将 Fragment 从 FragmentManager 中移除并将其与其宿主 Activity 分离后,系统会调用 onDetach() 回调。该 Fragment 不再处于活跃状态,无法再使用 findFragmentById() 检索到。

在发生生命周期状态变更之后,系统始终都会调用 onDetach()

注意:

1.这些回调与 FragmentTransaction 方法 attach() 和 detach() 无关

2.将 Fragment 实例从 FragmentManager 中移除之后,请避免重复使用这些实例。虽然 Fragment 会处理自己的内部状态清理,但您可能会无意中将上述状态带到重复使用的实例中。

3.2 Fragment 生命周期状态和回调

在确定 Fragment 的生命周期状态时,FragmentManager 会考虑以下方面:

  • Fragment 的状态极限由其 FragmentManager 确定。Fragment 不能超过其 FragmentManager 的状态。
  • 作为 FragmentTransaction 的一部分,您可以使用 setMaxLifecycle() 在 Fragment 上设置生命周期状态极限。
  • Fragment 的生命周期状态绝对不能超过其父级。例如,父 Fragment 或 Activity 必须在其子 Fragment 之前启动。同样,子 Fragment 必须在其父 Fragment 或 Activity 之前停止。
注意:请勿使用 <fragment> 标记通过 XML 添加 fragment,因为 <fragment> 标记允许 fragment 超出其 FragmentManager 的状态。而是应始终使用 FragmentContainerView 通过 XML 添加 Fragment。

下图 显示了 fragment 的每个 Lifecycle 状态,以及它们与 fragment 的生命周期回调和 fragment 的视图 Lifecycle 之间的关系。

在 Fragment 历经其生命周期的各个阶段时,会上下切换其状态。例如,添加到返回堆栈顶部的 Fragmen 会从 CREATED 向上转为 STARTED,再向上转为 RESUMED。相反,将 Fragment 从返回堆栈中弹出时,其会向下转换这些状态,即从 RESUMED 转为 STARTED,再转为 CREATED,最后转为 DESTROYED

3.2.1 向上状态转换

当 Fragment 在其生命周期状态中向上移动时,系统会首先调用其新状态关联的生命周期回调。此回调完成后,Fragment 的 Lifecycle 会向观察者发出相关 Lifecycle.Event,如果 Fragment 已实例化,那么 Fragment 的视图 Lifecycle 也会紧随其后向观察者发出此事件。

3.2.1.1  Fragment 状态为 CREATED

如果您的 Fragment 达到 CREATED 状态时,就说明它已添加到 FragmentManager,并且已调用 onAttach() 方法。

此时恰好可以通过 Fragment 的 SavedStateRegistry 恢复与 Fragment 本身关联的所有已保存状态。请注意,此时尚未创建 Fragment 的视图,只有在创建该视图后,才应恢复与该 Fragment 的视图关联的任何状态。

此转换会调用 onCreate() 回调。此回调还会收到 savedInstanceStateBundle 参数,其中包含之前由 onSaveInstanceState() 保存的任何状态。请注意,savedInstanceState 会在第一次创建 Fragment 时具有 null 值,但在后续重新创建时,该值始终为非 null,即使您不替换 onSaveInstanceState() 也是如此。如需了解详情,请参阅保存与 Fragment 相关的状态

3.2.1.2 Fragment 状态为 CREATED,视图状态为 INITIALIZED

仅当 Fragment 提供有效的 View 实例时,才会创建 Fragment 的视图 Lifecycle。在大多数情况下,您可以使用接受 @LayoutId 的 Fragment 构造函数,这会在适当的时间自动膨胀视图。您也可以替换 onCreateView(),以便以编程方式膨胀或创建 Fragment 的视图。

当且仅当 Fragment 的视图已使用非 null View 实例化后,该 View 才会在 Fragment 上设置,才能使用 getView() 检索到。然后,getViewLifecycleOwnerLiveData() 通过与 Fragment 的视图相对应的新 INITIALIZED LifecycleOwner 进行更新。此时也会调用 onViewCreated() 生命周期回调。

此时是一个适当的时机,可以设置视图的初始状态以开始观察 LiveData 实例(其回调会更新 Fragment 的视图),并且可以在 Fragment 的视图中对任何 RecyclerView 或 ViewPager2 实例设置适配器。

3.2.1.3 Fragment 和视图的状态均为 CREATED

创建 Fragment 的视图后,系统会恢复之前的视图状态(如有),然后视图的 Lifecycle 将转为 CREATED 状态。视图生命周期所有者还会向观察者发出 ON_CREATE 事件。您应在此处恢复与 Fragment 的视图相关的任何其他状态。

此转换也会调用 onViewStateRestored() 回调。

3.2.1.4 Fragment 和视图的状态均为 STARTED

强烈建议将生命周期感知型组件与 Fragment 的 STARTED 状态相关联,因为该状态可确保 Fragment 的视图(如已创建)可用,并且可确保在 Fragment 的子 FragmentManager 上安全地执行 FragmentTransaction。如果 Fragment 的视图为非 null,在 Fragment 的 Lifecycle 转为 STARTED 后,Fragment 的视

图 Lifecycle 会立即转为 STARTED

当 Fragment 转为 STARTED 时,系统会调用 onStart() 回调。

3.2.1.5 Fragment 和视图的状态均为 RESUMED

如果 Fragment 可见,即表示所有 Animator 和 Transition 效果均已完成,且 Fragment 已做好与用户互动的准备。该 Fragment 的 Lifecycle 会转为 RESUMED 状态,并且系统会调用 onResume() 回调。

转换为 RESUMED 即表示用户现在可以与您的 Fragment 互动。如果 Fragment 的状态并非为 RESUMED,您就不应对其视图手动设置焦点,也不应尝试处理输入法可见性

3.2.2 向下状态转换

当 Fragment 向下转为更低的生命周期状态时,Fragment 的视图 Lifecycle 会向观察者发出 Lifecycle.Event,如果 Fragment 已实例化,那么 Fragment 的 Lifecycle 也会向观察者发出此事件。发出 Fragment 的生命周期事件后,Fragment 会调用关联的生命周期回调。

3.2.2.1  Fragment 和视图的状态均为 STARTED

当用户开始离开 Fragment,但是 Fragment 仍然可见时,Fragment 及其视图的 Lifecycle 会返回 STARTED 状态,并向其观察者发出 ON_PAUSE 事件。然后,Fragment 会调用其 onPause() 回调。

3.2.2.2 Fragment 和视图的状态均为 CREATED

Fragment 不再可见后,Fragment 及其视图的 Lifecycle 将转为 CREATED 状态,并向其观察者发出 ON_STOP 事件。不仅停止父 Activity 或 Fragment 会触发该状态转换,而且父 Activity 或 Fragment 保存状态也会触发该状态转换。此行为可保证在保存 Fragment 的状态之前调用 ON_STOP 事件。这使得 ON_STOP 事件成为能够

安全地在子 FragmentManager 上执行 FragmentTransaction 的最后一个时间点。

如下图所示,onStop() 回调与使用 onSaveInstanceState() 保存状态之间的顺序因 API 级别而异。对于 API 28 之前的所有 API 级别,在 onStop() 之前调用 onSaveInstanceState()。对于 API 28 及更高级别,调用顺序正好相反。

onStop() 与 onSaveInstanceState() 的调用顺序差异

3.2.2.3 fragment 状态为 CREATED,视图状态为 DESTROYED

完成所有退出动画和转换并且 Fragment 的视图与窗口分离之后,Fragment 的视图 Lifecycle 会转为 DESTROYED 状态并向其观察者发出 ON_DESTROY 事件。然后,Fragment 会调用其 onDestroyView() 回调。此时,Fragment 的视图的生命周期结束,并且 getViewLifecycleOwnerLiveData() 会返回 null 值。

此时,应移除对 Fragment 的视图的所有引用,以允许对 Fragment 的视图进行垃圾回收。

3.2.2.4 Fragment 状态为 DESTROYED

如果 Fragment 已被移除,或者 FragmentManager 已被销毁,Fragment 的 Lifecycle 会转为 DESTROYED 状态,并向其观察者发送 ON_DESTROY 事件。然后,Fragment 会调用其 onDestroy() 回调。此时,Fragment 的生命周期结束。

四、例子-使用动画在 fragment 之间导航

Fragment API 提供了两种方法,供您利用运动效果和转换效果在导航期间直观地衔接 fragment。一个是动画框架,它使用 Animation 和 Animator。另一个是转换框架,其中包括共享元素转换。

注意:我们使用“动画”一词来描述动画框架中的效果,使用“转换”一词来描述转换框架中的效果。这两种框架互斥,不应同时使用。

您可以指定进入和退出 Fragment 时的自定义效果,以及 Fragment 之间共享元素的自定义过渡效果。

  • “进入”效果决定了 Fragment 进入屏幕的方式。例如,您可以创建一个效果,在导航到 Fragment 时使其从屏幕边缘滑入。
  • “退出”效果决定了 Fragment 退出屏幕的方式。例如,您可以创建一个效果,在导航离开 Fragment 时使其淡出。
  • “共享元素转换”决定了两个 Fragment 之间共享的视图如何在 Fragment 之间移动。例如,当 Fragment B 出现时,在 Fragment A 的 ImageView 中显示的图像将转换到 Fragment B 中。

4.1 设置动画

首先,您需要为进入和退出效果创建动画,这些动画会在导航到新的 Fragment 时出现。您可以将动画定义为补间动画资源。您可以通过这些资源定义 Fragment 在动画播放期间应如何旋转、拉伸、淡出和移动。例如,您可能需要使当前 Fragment 淡出,并从屏幕右边缘滑入新的 Fragment,如下图 所示。

进入和退出动画。当前 Fragment 淡出,而下一个 Fragment 从右边滑入。

这些动画可以在 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/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" />

注意:强烈建议您使用转换以实现涉及多种类型动画的效果,因为使用嵌套 AnimationSet 实例存在已知问题。

还可以为弹出返回堆栈时显示的进入和退出效果指定动画,用户点按“向上”或“返回”按钮时会弹出返回堆栈。这称为 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 传入动画资源,如以下示例所示:

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();

注意:FragmentTransaction.setCustomAnimations() 可将自定义动画应用于 FragmentTransaction 中的所有未来 Fragment 操作。事务中的先前的操作不受影响。

4.2 设置转换

您还可以使用转换来定义进入和退出效果。这些转换可以在 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 传入这些资源,如以下示例所示:

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 和更高级别中得到支持,并且包含旧版本的框架转换中不提供的问题修复功能。

4.3 使用共享元素转换

共享元素转换是转换框架的一部分,它决定了 Fragment 转换期间,对应视图如何在两个 Fragment 之间移动。例如,您可能希望 Fragment A 上 ImageView 中显示的图片在 B 变为可见后转换到 Fragment B 中,如下图所示。

就更高层次而言,以下展示了如何使用共享元素进行 Fragment 转换:

  1. 为每个共享元素视图指定唯一的转换名称。
  2. 将共享元素视图和转换名称添加到 FragmentTransaction
  3. 设置共享元素转换动画。

首先,您必须为每个共享元素视图分配唯一的转换名称,以允许视图从一个 Fragment 映射到下一个 Fragment。使用可与 API 级别 14 及更高级别兼容的 ViewCompat.setTransitionName() 为每个 Fragment 布局中的共享元素设置转换名称。例如,Fragment A 和 B 中 ImageView 的转换名称可按如下方式分配:

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”);

    }

}

注意:对于仅支持 API 级别 21 及更高级别的应用,您还可以使用 XML 布局中的 android:transitionName 属性将转换名称分配给特定视图。

如需在 Fragment 转换中包含共享元素,您的 FragmentTransaction 必须了解每个共享元素的视图如何从一个 Fragment 映射到下一个 Fragment。通过调用 FragmentTransaction.addSharedElement() 将各个共享元素添加至 FragmentTransaction,传入视图和下一个 Fragment 中相应视图的转换名称,如以下示例所示:

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(),如以下示例所示:

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() 实现。

4.4 推迟转换

在某些情况下,您可能需要在短时间内推迟 Fragment 转换。例如,您可能需要稍加等待,以便系统对要进入的 Fragment 中的所有视图完成测量和布局,以便 Android 可以准确捕获转换的起始和结束状态。

此外,您可能需要推迟某些转换,直到已加载一些必要的数据。例如,您可能需要等待系统加载用于共享元素的图像。否则,如果图片在转换期间或转换之后完成加载,转换可能会变得模糊。

如需延迟转换,您必须先确保 Fragment 事务允许重新排序 Fragment 状态更改。如需允许重新排序 Fragment 状态更改,请调用 FragmentTransaction.setReorderingAllowed(),如以下示例所示:

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()

public class FragmentB extends Fragment {

    @Override

    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {

        ...

        postponeEnterTransition();

    }

}

完成加载数据并为开始转换准备就绪后,请调用 Fragment.startPostponedEnterTransition()。以下示例使用 Glide 库将图片加载到共享 ImageView 中,推迟相应的转换直到完成图片加载。

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),传入时长和时间单位。在指定的时间过去后,将自动开始推迟的转换。

4.5 通过 RecyclerView 使用共享元素转换

推迟进入转换应在进入 Fragment 完成测量和布局之后开始。使用 RecyclerView 时,您必须等到所有数据加载完毕,并且 RecyclerView 项准备好绘制之后才可以开始转换。示例如下:

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 的 Fragment 转换到另一个 Fragment 时,您必须仍使用 RecyclerView 推迟 Fragment,以确保返回的共享元素转换在弹回 RecyclerView 时能够正常运行。

使用具有 RecyclerView 的共享元素转换时,另一个需考虑的因素是,您无法在 RecyclerView 项的 XML 布局中设置转换名称,因为有任意数量的项共享该布局。必须分配一个唯一的转换名称,以便转换动画使用正确的视图。

您可以在绑定 ViewHolder 时进行分配,为每个项目的共享元素提供唯一的转换名称。例如,如果每一项的数据包含唯一 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);

        ...

    }

}

  • 16
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值