这是对于导航 (Navigation) 的第二个 MAD Skills 系列,本文是导航组件系列的第四篇文章,如果您想回顾过去公布的内容,请通过上面链接查看:
- 导航组件概览
- 导航到对话框
- 在利用中导航时应用 SafeArgs
- 应用深层链接导航
- 打造您的首个 app bundle
- 深入浅出 NavigationUI
- 应用导航组件: 条件导航
- 导航: 嵌套导航图和 <include>
如果您更偏向于观看视频而非阅读文章,请 点击这里 查看视频内容。
概述
在 上一篇文章 中,您曾经学会了如何在多模块工程中应用导航 (Navigation)。在本文中,咱们将更进一步,将咖啡模块转换成功能模块 (Feature Module)。如果对功能模块不太熟悉,您能够先查看这个 视频 内容。
功能模块在装置时并未下载到本地,而是当利用应用到某个性能时才会下载相应的功能模块。这不仅节俭了利用下载和装置时的工夫和带宽,也节俭了设施存储空间。
那么让咱们为用户节俭一些空间!当初间接开始编程吧!
功能模块
因为我在 上一篇文章 中曾经将 DonutTracker 利用进行了模块化,我会从将现有的咖啡模块转换成功能模块开始。
首先,我在咖啡模块的 build.gradle 中将库插件 (library plugin) 替换为动静性能插件 (dynamic-feature plugin):
id 'com.android.dynamic-feature'
接着,我在 AndroidManifest.xml
中将咖啡模块申明为按需 (on-demand) 模块:
<?xml version="1.0" encoding="utf-8"?><manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:dist="http://schemas.android.com/apk/distribution" package="com.android.samples.donuttracker.coffee"> <dist:module dist:instant="false" dist:title="@string/title_coffee"> <dist:delivery> <dist:on-demand /> </dist:delivery> <dist:fusing dist:include="true" /> </dist:module></manifest>
当初咖啡模块曾经转换实现,我将该模块增加为动静性能 (dynamicFeature
):
android { //... packagingOptions { exclude 'META-INF/atomicfu.kotlin_module' } dynamicFeatures = [':coffee']}
同时在 app 模块的 build.gradle
中,我从依赖列表中移除了咖啡模块并增加了 navigation-dynamic-features 依赖:
implementation "androidx.navigation:navigation-dynamic-features-fragment:$navigationVersion"
当 Gradle 同步实现时,即可更新导航图了。我将 include 标签改为 include-dynamic
,并增加 id
、graphResName
以及指向功能模块的 moduleName
:
<include-dynamic android:id="@+id/coffeeGraph" app:moduleName="coffee" app:graphResName="coffee_graph"/>
此时,我能够平安地移除 coffee_graph.xml
中 navigation
标签的 id
属性,起因在于,如果导航图是应用 include 标签引入的,那么 Dynamic Navigator 库会疏忽根元素的 id 属性。
<navigation xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" app:startDestination="@id/coffeeList"> <fragment android:id="@+id/coffeeList" android:name="com.android.samples.donuttracker.coffee.CoffeeList" android:label="@string/coffee_list"> <action android:id="@+id/action_coffeeList_to_coffeeEntryDialogFragment" app:destination="@id/coffeeEntryDialogFragment" /> </fragment> <dialog android:id="@+id/coffeeEntryDialogFragment" android:name="com.android.samples.donuttracker.coffee.CoffeeEntryDialogFragment" android:label="CoffeeEntryDialogFragment"> <argument android:name="itemId" android:defaultValue="-1L" app:argType="long" /> </dialog></navigation>
在 activity_main
布局中,我将 FragmentContainerView
的 name
属性值由 NavHostFragment
改为 DynamicNavHostFragment
:
<androidx.fragment.app.FragmentContainerView android:id="@+id/nav_host_fragment" android:name="androidx.navigation.dynamicfeatures.fragment.DynamicNavHostFragment" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" app:defaultNavHost="true" app:navGraph="@navigation/nav_graph" />
与通过 include 引入导航图相似,要使动静引入 (include-dynamic) 失效,咖啡菜单项的 id 值须要与导航图名称相匹配,而不是目的地页面 id
:
<menu xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@id/donutList" android:icon="@drawable/donut_with_sprinkles" android:title="@string/donut_name" /> <item android:id="@id/coffeeGraph" android:icon="@drawable/coffee_cup" android:title="@string/coffee_name" /></menu>
这就是增加动静导航所需的全副工作。当初我将应用 bundletool 来测试功能模块,您也能够应用 Play 控制台来测试功能模块。如果您想理解更多对于如何应用 bundletool 和 Play 控制台来测试功能模块装置的内容,请查看这个 视频。
我也想测试当模块无奈装置时会产生什么。为此,在 Run/Debug Configurations 弹窗中,我从待部署列表中勾销勾选了 donuttracker.coffee
。这时当我再次运行利用并导航到 coffeeList
页面时,将会显示一条通用错误信息。
△ 通用错误信息
至此,功能模块的设置曾经实现,是时候打磨用户体验了。当功能模块处于下载过程时,向用户显示自定义反馈信息或者显示一条更有意义的报错信息而不是通用的信息会不会更好?
为此,我能够增加一个监听器,当用户停留在同一个页面时,它能够解决装置状态、进度变动或错误信息。或者,当功能模块正在下载时,我能够增加一个自定义进度 Fragment 来展现进度。
导航库曾经内置了对 进度 Fragment 的反对。我所须要做的就是创立一个继承了 AbstractProgressFragment 的 Fragment。
class ProgressFragment : AbstractProgressFragment(R.layout.fragment_progress) {}
我增加了一个 ImageView
、一个 TextView
和一个 ProgressBar
来展现下载状态。
<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" android:paddingLeft="@dimen/default_margin" android:paddingTop="@dimen/default_margin" android:paddingRight="@dimen/default_margin" android:paddingBottom="@dimen/default_margin"> <ImageView android:id="@+id/progressImage" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/coffee_cup" android:layout_marginBottom="@dimen/default_margin" android:layout_gravity="center"/> <TextView android:id="@+id/message" android:layout_width="wrap_content" android:layout_height="wrap_content" tools:text="@string/installing_coffee_module"/> <ProgressBar android:id="@+id/progressBar" style="@style/Widget.AppCompat.ProgressBar.Horizontal" android:layout_width="match_parent" android:layout_height="wrap_content" tools:progress="10" /></LinearLayout>
接着,我覆写了 onProgress()
函数来更新 progressBar
,我还覆写了 onFailed()
和 onCanceled()
函数来更新 TextView
以向用户展现相干反馈。
override fun onProgress(status: Int, bytesDownloaded: Long, bytesTotal: Long) { progressBar?.progress = (bytesDownloaded.toDouble() * 100 / bytesTotal).toInt()} override fun onFailed(errorCode: Int) { message?.text = getString(R.string.install_failed)} override fun onCancelled() { message?.text = getString(R.string.install_cancelled)}
我须要将 progressFragment
目的地增加到导航图中。最初,将 progressFragment
申明为导航图的 progressDestination
。
<navigation xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" app:startDestination="@id/donutList" app:progressDestination="@+id/progressFragment"><fragment android:id="@+id/donutList" android:name="com.android.samples.donuttracker.donut.DonutList" android:label="@string/donut_list" > <action android:id="@+id/action_donutList_to_donutEntryDialogFragment" app:destination="@id/donutEntryDialogFragment" /> <action android:id="@+id/action_donutList_to_selectionFragment" app:destination="@id/selectionFragment" /> </fragment> <dialog android:id="@+id/donutEntryDialogFragment" android:name="com.android.samples.donuttracker.donut.DonutEntryDialogFragment" android:label="DonutEntryDialogFragment"> <deepLink app:uri="myapp://navdonutcreator.com/donutcreator" /> <argument android:name="itemId" app:argType="long" android:defaultValue="-1L" /> </dialog> <fragment android:id="@+id/selectionFragment" android:name="com.android.samples.donuttracker.setup.SelectionFragment" android:label="@string/settings" tools:layout="@layout/fragment_selection" > <action android:id="@+id/action_selectionFragment_to_donutList" app:destination="@id/donutList" /> </fragment> <fragment android:id="@+id/progressFragment" android:name="com.android.samples.donuttracker.ProgressFragment" android:label="ProgressFragment" /> <include-dynamic android:id="@+id/coffeeGraph" app:moduleName="coffee" app:graphResName="coffee_graph"/></navigation>
此时,我再次勾销勾选咖啡模块,运行利用并导航至 coffeeList
页面时,利用展现了自定义进度页面 progressFragment
。
△ 自定义 progressFragment
相似地,我能够应用 bundletool 测试利用以查看当咖啡模块正在下载时,进度条会如何工作。
小结
感激大家!在本系列中,咱们再次应用了 Chet 的 DonutTracker 利用 并增加了咖啡记录性能。因为...我喜爱咖啡。
新性能带来了新责任。为了提供更好的用户体验,首先我应用导航增加了 NavigationUI
以集成 UI 组件。而后,我实现了一次性流程和条件导航。之后,我应用了嵌套图和 include 标签来组织导航图并将利用模块化以节俭用户的网络和存储空间。至此,咱们曾经实现了该利用,是时候去享受一杯美味的咖啡和甜甜圈了!
欢迎您 点击这里 向咱们提交反馈,或分享您喜爱的内容、发现的问题。您的反馈对咱们十分重要,感谢您的反对!