介绍
相信绝大多数的Android开发者对fragment都有或多或少的使用经历。相对于activity,fragment具有更优异的性能表现。相对于View,fragment更易管理。当然还有fragment诞生的原因-平板适配。fragment虽然有这些优点,但是在使用的时候也是有比较棘手的地方:
- 复杂的生命周期
- 嵌套情况下的各种问题
- 回退栈的维护
面对以上问题,不少前辈针对
fragment的管理造了一个个优秀的轮子,比如 Fragmentation,这些优秀的轮子现在也已经大多不维护了。而我们在使用Fragmentation时,虽然不需要自己过于去关心fragment的嵌套,生命周期,以及回退栈等,当然大部分情况下他也是能正常工作的。但是相应的,遇到的问题也是层出不穷。如果有官方的fragment管理类的轮子那该多好。
其实Google早在Jetpack推出时就已经附带了一个fragment管理组件Navigation:
导航是指支持用户导航、进入和退出应用中不同内容片段的交互。
Android Jetpack的导航组件可帮助您实现导航,无论是简单的按钮点击,还是应用栏和抽屉式导航栏等更为复杂的模式,该组件均可应对。导航组件还通过遵循一套既定原则来确保一致且可预测的用户体验。
组成
Navigation由以下三部分组成:
- 导航图: 存放在
res/navigation目录下,用来描述导航相关信息的 XML 资源 NavHost: 显示导航图中目标的空白容器,其实就是用来放置fragment的父容器NavController: 真正的用于导航动作实现的控制者
集成
build.gradle中:
def nav_version = "2.3.5"
// Java language implementation
implementation "androidx.navigation:navigation-fragment:$nav_version"
implementation "androidx.navigation:navigation-ui:$nav_version"
// Kotlin
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
// Feature module Support
implementation "androidx.navigation:navigation-dynamic-features-fragment:$nav_version"
// Testing Navigation
androidTestImplementation "androidx.navigation:navigation-testing:$nav_version"
// Jetpack Compose Integration
implementation "androidx.navigation:navigation-compose:2.4.0-rc01"
使用
之后我们就可以在相应的module创建navigation所属的resource目录
然后新建一个
navigation的resource文件
<?xml version="1.0" encoding="utf-8"?>
<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"
android:id="@+id/main_navigation"
app:startDestination="...">
</navigation>
这个时候还没有向里面添加 fragment,我们向项目中添加三个 fragment,然后添加到导航文件中
<?xml version="1.0" encoding="utf-8"?>
<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"
android:id="@+id/mobile_navigation"
app:startDestination="@+id/navigation_home">
<fragment
android:id="@+id/navigation_home"
android:name="com.example.myapplication.ui.home.HomeFragment"
android:label="@string/title_home"
tools:layout="@layout/fragment_home" />
<fragment
android:id="@+id/navigation_dashboard"
android:name="com.example.myapplication.ui.dashboard.DashboardFragment"
android:label="@string/title_dashboard"
tools:layout="@layout/fragment_dashboard" />
<fragment
android:id="@+id/navigation_notifications"
android:name="com.example.myapplication.ui.notifications.NotificationsFragment"
android:label="@string/title_notifications"
tools:layout="@layout/fragment_notifications" />
</navigation>
此时就可以在Android Studio中可视化编辑了,我们可以随意拖动来实现不同 fragment间的跳转路径,这时切回xml可以看到生成了名叫action的子节点,子结点中的destination则定义了要跳转的目标fragment
<fragment
android:id="@+id/navigation_home"
android:name="com.example.myapplication.ui.home.HomeFragment"
android:label="@string/title_home"
tools:layout="@layout/fragment_home" >
<action
android:id="@+id/action_navigation_home_to_navigation_dashboard3"
app:destination="@id/navigation_dashboard" />
</fragment>
然后就是
Activity 的配置,在相应的布局文件中添加一个 fragment或者FragmentContainerView并将其name属性设置为:
"androidx.navigation.fragment.NavHostFragment"
NavHostFragment其实是作为Navigation中fragment的显示载体来出现的。在来看,还有个属性defaultNavHost,app:defaultNavHost="true" 表示让当前的导航容器处理系统back操作,所有的入栈出栈行为都会在当前的容器之上。
而navGraph属性则指向编写的导航图
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="?attr/actionBarSize">
<fragment
android:id="@+id/nav_host_fragment_activity_main"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="@navigation/main_navigation" />
</androidx.constraintlayout.widget.ConstraintLayout>
举个栗子,在HomeFragment跳转至DashboardFragment:
class HomeFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val textView: TextView = binding.textHome
textView.setOnClickListener {
Navigation.findNavController(it).navigate(R.id.action_navigation_home_to_navigation_dashboard3)
}
return root
}
}
嵌套
可以封装其他的导航图,具体的实现可以是navigation节点下接着描述navigation节点,也可以是include引用其他导航图的资源
deepLink
深层链接,在需要配置的导航图中fragment节点下
<deepLink app:uri="www.deeplink.com/{params}" />
为相应的Activity设置 <nav-graph/> 标签
<activity android:name=".DeepLinkActivity">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<!-- 为Activity设置<nav-graph/>标签 -->
<nav-graph android:value="@navigation/graph_deep_link_activity" />
</activity>
deeplink 变相提供了一种路由功能,适用于 app 内的各种业务跳转以及推送跳转等
问题
生命周期
使用Navigation进行fragment的切换时,观察fragment的生命周期,发现每次都会进行view的销毁与重建,当然这不在我们的预期之内,我们的预期是,已经初始化过的fragment不需要在初始化,回退回去的时候,他不用重建,还是原来的样子就好。但是为什么fragment会重新初始化界面呢,这里有详细的解答,一言概之就是Navigation是通过replace来实现fragment的切换的,因此每次切换都会重建。已经有大佬实现了一个通过hide,show来实现fragment切换的Navigation
当然,Google设计Navigation的意图,也可能是希望开发者充分利用ViewModel与LiveData,配合Navigation来达到最好的效果。因为ViewModel是不受Fragment重建影响的,在 replace 之后,前一个fragment并不会走onDestroy而是会走onDestroyView,而onDestroyView并不会造成ViewModel的销毁,当再次回到这个fragment页面重新回调onViewCreated的时候,页面对ViewModel中的LiveData重新进行了obderve,LiveData之前保存过这个页面的数据,因此页面上也会重新填充。
2.4.0 版本后已修复这个特性