Android 之 Navigation在目的地之间传递数据、ViewModel

 2023-09-06 阅读 420 评论 0

摘要:文章目录Android 之 Navigation在目的地之间传递数据、ViewModel一、在目的地之间传递数据1. 定义目的地参数2.使用 Safe Args 传递安全的数据3.在目的地之间添加动画过渡效果二、ViewModel1.实现 ViewModel2.ViewModel 的生命周期3.在 Fragment 之间共享数据4.将加载器替换为

文章目录

    • Android 之 Navigation在目的地之间传递数据、ViewModel
      • 一、在目的地之间传递数据
          • 1. 定义目的地参数
          • 2.使用 Safe Args 传递安全的数据
          • 3.在目的地之间添加动画过渡效果
      • 二、ViewModel
          • 1.实现 ViewModel
          • 2.ViewModel 的生命周期
          • 3.在 Fragment 之间共享数据
          • 4.将加载器替换为 ViewModel

Android 之 Navigation在目的地之间传递数据、ViewModel

参考网页

一、在目的地之间传递数据

Navigation 支持您通过定义目的地参数将数据附加到导航操作。例如,用户个人资料目的地可能会根据用户 ID 参数来确定要显示哪个用户。

通常情况下,强烈建议您仅在目的地之间传递最少量的数据。例如,您应该传递键来检索对象而不是传递对象本身,因为在 Android 上用于保存所有状态的总空间是有限的。如果您需要传递大量数据,不妨考虑使用 ViewModel(如在 Fragment 之间共享数据中所述)。

1. 定义目的地参数
  1. 在 Navigation Editor 中,点击接收参数的目的地。
  2. 在 Attributes 面板中,点击 Add (+)。
  3. 在显示的 Add Argument Link 窗口中,输入参数名称、参数类型、参数是否可为 null,以及默认值(如果需要)。
  4. 点击 Add。请注意,该参数现在会显示在 Attributes 面板的 Arguments 列表中。
  5. 接下来,点击会将您转到此目的地的相应操作。在 Attributes 面板中,您现在应该会在 Argument Default Values 部分中看到新添加的参数。
  6. 您还可以看到该参数已添加到 XML 中。点击 Text 标签页以切换到 XML 视图,就会发现您的参数已添加到接收该参数的目的地。相关示例如下所示:
 <fragment android:id="@+id/myFragment" ><argumentandroid:name="myArg"app:argType="integer"android:defaultValue="0" /></fragment>

支持参数
在这里插入图片描述

如果参数类型支持 null 值,您可以使用 android:defaultValue="@null" 声明默认值 null。

如果您选择其中一种自定义类型,则系统会显示 Select Class 对话框,提示您选择与该类型对应的类。您可以通过 Project 标签页从当前项目中选择类。

您可以选择 ,让 Navigation 库根据提供的值来确定类型。

您可以选中 Array,以指明参数应该是所选 Type 值的数组。请注意以下几点:

  • 不支持枚举数组和资源引用数组
  • 无论基础类型是否支持可为 null 的值,数组都支持可为 null 的值。例如,使用 app:argType=“integer[]” 时,您可以使用 app:nullable=“true” 来指示可传递 null 数组。
  • 数组仅支持一个默认值,即“@null”。数组不支持其他任何默认值。
2.使用 Safe Args 传递安全的数据

见网址

3.在目的地之间添加动画过渡效果

借助 Navigation 组件,您可以同时向操作添加属性动画和视图动画。如需创建您自己的动画,请参阅动画资源。

Navigation 组件还包含几个默认动画,以帮助您快速入门。如需向操作添加动画,请执行以下操作

  • 在 Navigation Editor 中,点击应发生动画的操作。
  • 在 Attributes 面板的 Animations 部分中,点击要添加的动画旁边的下拉箭头。您可以从以下类型中进行选择:
    进入目的地
    退出目的地
    通过弹出操作进入目的地,弹出操作是指在导航时从返回堆栈上弹出其他目的地的操作。
    通过弹出操作退出目的地
  • 从显示的项目动画列表中选择动画。

添加动画后,点击 Text 标签页,以切换到 XML 文本视图。动画的 XML 现在显示在相应的 元素中。在以下示例中,specifyAmountFragment 是 confirmationAction 操作的源目的地:

<fragmentandroid:id="@+id/specifyAmountFragment"android:name="com.example.buybuddy.buybuddy.SpecifyAmountFragment"android:label="fragment_specify_amount"tools:layout="@layout/fragment_specify_amount"><actionandroid:id="@+id/confirmationAction"app:destination="@id/confirmationFragment"app:enterAnim="@anim/slide_in_right"app:exitAnim="@anim/slide_out_left"app:popEnterAnim="@anim/slide_in_left"app:popExitAnim="@anim/slide_out_right" />
</fragment>

在目的地之间添加共享元素过渡
到 Fragment 目的地的共享元素过渡
到 Activity 目的地的共享元素过渡
将弹出动画应用于 Activity 过渡
详见网页,此处不与记录

二、ViewModel

参考网页
ViewModel 类旨在以注重生命周期的方式存储和管理界面相关的数据。ViewModel 类让数据可在发生屏幕旋转等配置更改后继续留存。
Android 框架可以管理界面控制器(如 Activity 和 Fragment)的生命周期。Android 框架可能会决定销毁或重新创建界面控制器,以响应完全不受您控制的某些用户操作或设备事件。

如果系统销毁或重新创建界面控制器,则存储在其中的任何瞬态界面相关数据都会丢失。例如,应用可能会在它的某个 Activity 中包含用户列表。为配置更改重新创建 Activity 后,新 Activity 必须重新提取用户列表。对于简单的数据,Activity 可以使用 onSaveInstanceState() 方法从 onCreate() 中的捆绑包恢复其数据,但此方法仅适合可以序列化再反序列化的少量数据,而不适合数量可能较大的数据,如用户列表或位图。

另一个问题是,界面控制器经常需要进行可能需要一些时间才能返回的异步调用。界面控制器需要管理这些调用,并确保系统在其销毁后清理这些调用以避免潜在的内存泄漏。此项管理需要大量的维护工作,并且在为配置更改重新创建对象的情况下,会造成资源的浪费,因为对象可能需要重新发出已经发出过的调用。

诸如 Activity 和 Fragment 之类的界面控制器主要用于显示界面数据、对用户操作做出响应或处理操作系统通信(如权限请求)。如果要求界面控制器也负责从数据库或网络加载数据,那么会使类越发膨胀。为界面控制器分配过多的责任可能会导致单个类尝试自己处理应用的所有工作,而不是将工作委托给其他类。以这种方式为界面控制器分配过多的责任也会大大增加测试的难度。

从界面控制器逻辑中分离出视图数据所有权的操作更容易且更高效。

1.实现 ViewModel

架构组件为界面控制器提供了 ViewModel 辅助程序类,该类负责为界面准备数据。在配置更改期间会自动保留 ViewModel 对象,以便它们存储的数据立即可供下一个 Activity 或 Fragment 实例使用。例如,如果您需要在应用中显示用户列表,请确保将获取和保留该用户列表的责任分配给 ViewModel,而不是 Activity 或 Fragment,如以下示例代码所示:

public class MyViewModel extends ViewModel {private MutableLiveData<List<User>> users;public LiveData<List<User>> getUsers() {if (users == null) {users = new MutableLiveData<List<User>>();loadUsers();}return users;}private void loadUsers() {// Do an asynchronous operation to fetch users.}
}

然后,您可以从 Activity 访问该列表,如下所示:

public class MyActivity extends AppCompatActivity {public void onCreate(Bundle savedInstanceState) {// Create a ViewModel the first time the system calls an activity's onCreate() method.// Re-created activities receive the same MyViewModel instance created by the first activity.MyViewModel model = new ViewModelProvider(this).get(MyViewModel.class);model.getUsers().observe(this, users -> {// update UI});}
}

如果重新创建了该 Activity,它接收的 MyViewModel 实例与第一个 Activity 创建的实例相同。当所有者 Activity 完成时,框架会调用 ViewModel 对象的 onCleared() 方法,以便它可以清理资源。

ViewModel 对象存在的时间比视图或 LifecycleOwners 的特定实例存在的时间更长。这还意味着,您可以更轻松地编写涵盖 ViewModel 的测试,因为它不了解视图和 Lifecycle 对象。ViewModel 对象可以包含 LifecycleObservers,如 LiveData 对象。但是,ViewModel 对象绝不能观察对生命周期感知型可观察对象(如 LiveData 对象)的更改。如果 ViewModel 需要 Application 上下文(例如,为了查找系统服务),它可以扩展 AndroidViewModel 类并设置用于接收 Application 的构造函数,因为 Application 类会扩展 Context。

2.ViewModel 的生命周期

ViewModel 对象存在的时间范围是获取 ViewModel 时传递给 ViewModelProvider 的 Lifecycle。ViewModel 将一直留在内存中,直到限定其存在时间范围的 Lifecycle 永久消失:对于 Activity,是在 Activity 完成时;而对于 Fragment,是在 Fragment 分离时。

您通常在系统首次调用 Activity 对象的 onCreate() 方法时请求 ViewModel。系统可能会在 Activity 的整个生命周期内多次调用 onCreate(),如在旋转设备屏幕时。ViewModel 存在的时间范围是从您首次请求 ViewModel 直到 Activity 完成并销毁。

3.在 Fragment 之间共享数据

Activity 中的两个或更多 Fragment 需要相互通信是一种很常见的现象。想象一下拆分视图 (master-detail) Fragment 的常见情况,假设您有一个 Fragment,在该 Fragment 中,用户从列表中选择一项,还有另一个 Fragment,用于显示选定项的内容。这种情况不太容易处理,因为这两个 Fragment 都需要定义某种接口描述,并且所有者 Activity 必须将两者绑定在一起。此外,这两个 Fragment 都必须处理另一个 Fragment 尚未创建或不可见的情况。

可以使用 ViewModel 对象解决这一常见的难点。这两个 Fragment 可以使用其 Activity 范围共享 ViewModel 来处理此类通信,如以下示例代码所示:

public class SharedViewModel extends ViewModel {private final MutableLiveData<Item> selected = new MutableLiveData<Item>();public void select(Item item) {selected.setValue(item);}public LiveData<Item> getSelected() {return selected;}
}public class MasterFragment extends Fragment {private SharedViewModel model;public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {super.onViewCreated(view, savedInstanceState);model = new ViewModelProvider(requireActivity()).get(SharedViewModel.class);itemSelector.setOnClickListener(item -> {model.select(item);});}
}public class DetailFragment extends Fragment {public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {super.onViewCreated(view, savedInstanceState);SharedViewModel model = new ViewModelProvider(requireActivity()).get(SharedViewModel.class);model.getSelected().observe(getViewLifecycleOwner(), item -> {// Update the UI.});}
}

请注意,这两个 Fragment 都会检索包含它们的 Activity。这样,当这两个 Fragment 各自获取 ViewModelProvider 时,它们会收到相同的 SharedViewModel 实例(其范围限定为该 Activity)。

此方法具有以下优势:

  • Activity 不需要执行任何操作,也不需要对此通信有任何了解。
  • 除了 SharedViewModel 约定之外,Fragment 不需要相互了解。如果其中一个 Fragment 消失,另一个 Fragment 将继续照常工作。
  • 每个 Fragment 都有自己的生命周期,而不受另一个 Fragment 的生命周期的影响。如果一个 Fragment 替换另一个 Fragment,界面将继续工作而没有任何问题。
4.将加载器替换为 ViewModel

ViewModel 与 Room 和 LiveData 一起使用可替换加载器。ViewModel 确保数据在设备配置更改后仍然存在。Room 在数据库发生更改时通知 LiveData,LiveData 进而使用修订后的数据更新界面。

版权声明:本站所有资料均为网友推荐收集整理而来,仅供学习和研究交流使用。

原文链接:https://808629.com/10170.html

发表评论:

本站为非赢利网站,部分文章来源或改编自互联网及其他公众平台,主要目的在于分享信息,版权归原作者所有,内容仅供读者参考,如有侵权请联系我们删除!

Copyright © 2022 86后生记录生活 Inc. 保留所有权利。

底部版权信息