【从零开始撸一个App】Fragment和导航中的使用
Fragment简介#
Fragment
自从Android 3.0引入开始,刚接触的同学会把它与Activity
的关系类比于html片段和html页面的关系,其实是不准确的。前者更多的是组件的概念,需要在运行时有一套管理机制;而后者侧重于布局编写阶段,Android中复用布局我们一般使用<include layout="@layout/xxx"/>
标签。
Fragment实例由Activity的FragmentManager
管理,其生命周期和Activity一样,都不是由开发人员而是由系统维护的。自然而然的,每当它们被重建时,系统只会去调用它们的无参构造器,带参构造器会被无视。那如果要在它们创建时传入初始化数据咋办呢?这也是为什么会有Bundle
这个玩意儿的存在,就是用于开发人员存取相关数据,如下所示:
/**
* Use the [ThumbnailsFragment.newInstance] factory method to
* create an instance of this fragment.
*/
class ThumbnailsFragment() : Fragment() {
private var albumTag: String? = null
companion object {
@JvmStatic
fun newInstance(albumTag: String?) =
ThumbnailsFragment().apply {
arguments = Bundle().apply {
putString("albumTag", albumTag)
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
albumTag = it.getString("albumTag")
}
}
/*other code*/
}
arguments由FragmentManager维护(跨fragment生命周期),可参看Android解惑 - 为什么要用Fragment.setArguments(Bundle bundle)来传递参数
底部导航栏切换Fragment#
BottomNavigationView#
底部是BottomNavigationView
组件,各个菜单在另外xml中定义,然后通过app:menu="xxx"
指定。此处菜单定义如下
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/navigation_home"
android:icon="@drawable/ic_home_black_24dp"
android:title="@string/title_home" />
<item
android:id="@+id/navigation_dashboard"
android:icon="@drawable/ic_dashboard_black_24dp"
android:title="@string/title_dashboard" />
<item
android:id="@+id/navigation_notifications"
android:icon="@drawable/ic_notifications_black_24dp"
android:title="@string/title_notifications" />
</menu>
然后在代码中设置BottomNavigationView.setOnNavigationItemSelectedListener
,判断当前选中的菜单项,手动切换Fragment,需要用到FragmentTransaction
。如下示例
override fun onClick(view: View?) {
val trans = activity.supportFragmentManager.beginTransaction()
val fragments = activity.supportFragmentManager.fragments
fragments.forEach {
if (it.isVisible) {
trans.hide(it) //隐藏当前显示的fragment
}
}
val tag = (view as TextView).text.toString()
val thumbnailsFragment = ThumbnailsFragment.newInstance(tag)
//fragment_main_container就是居中的那块区域,用于显示各个fragment
trans.add(R.id.fragment_main_container, thumbnailsFragment, tag)
trans.show(thumbnailsFragment)
trans.addToBackStack(null) //将本次操作入栈
trans.commitAllowingStateLoss() //提交
}
注意addToBackStack
方法,该方法是为了实现回退时——用户按返回按钮或程序执行回退(配合popBackStack
)——界面能返回到本次操作前的状态。也可指定tag,在跨[多次]操作回退时有用。注意此处入栈的是操作信息,而非fragment。
BottomNavigationView也可搭配ViewPager
使用,但回退操作依然需要另外实现。
Navigation#
上述手动控制Fragment的切换太麻烦。2018 I/O大会上,Google隆重推出一个新的架构组件:Navigation
。它提供了多Fragment之间的转场、栈管理。在抽屉式/底部/顶部导航栏的需求中都可以使用这个组件。
使用:在res目录下新建navigation文件夹,然后新建一个navigation graph
设为bottom_navigation:
<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.eixout.presearchapplication.ui.home.HomeFragment"
android:label="@string/title_home"
tools:layout="@layout/fragment_home" />
<fragment
android:id="@+id/navigation_dashboard"
android:name="com.eixout.presearchapplication.ui.dashboard.DashboardFragment"
android:label="@string/title_dashboard"
tools:layout="@layout/fragment_dashboard" />
<fragment
android:id="@+id/navigation_notifications"
android:name="com.eixout.presearchapplication.ui.notifications.NotificationsFragment"
android:label="@string/title_notifications"
tools:layout="@layout/fragment_notifications" />
</navigation>
注意每个fragment的id要和之前定义的menu的id保持一致。可以设置转场动画,还可以设置每个fragment跳转的目标(destination),目标可以是 Activity或Fragment,也可以自定义。
然后在Activity布局文件中添加一个Fragment,设置name属性为android:name="androidx.navigation.fragment.NavHostFragment"
。在传统的单Activity多Fragment场景中,我们往往需要为Activity添加一个FrameLayout
作为Fragment的容器。在Navigation中HavHostFragment
就是Fragment的容器(HavHostFragment
继承了NavHost
。The NavHost
interface enables destinations to be swapped in and out.),其中设置app:navGraph="@navigation/bottom_navigation"
使之与navigation graph
建立联系。
<fragment
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
app:defaultNavHost="true"
app:navGraph="@navigation/bottom_navigation"
other_config="..." />
app:defaultNavHost
: If set to true
, the navigation host will intercept the Back button.
最后将导航栏与Navigation关联
val navController = findNavController(R.id.nav_host_fragment)
bottomNavigationView.setupWithNavController(navController)
如此便大功告成了。
如果不依赖导航栏,而是手动跳转,则可以使用NavController
的相关方法,比如在Activity里navController.navigate(actionId)
。
问题#
Navigation和FragmentTransaction方式最好不要同时使用,它俩的返回堆栈似乎不是同一个,回退时会有问题。不能同时使用还使得下面两个问题不好解决。
-
使用Navigation,Fragment可以相互跳转没问题,但状态丢失了。比如A下滑一定距离后跳转到B,B回退到A,A的下滑状态丢失,仍是从头部开始显示。
-
每次点击
BottomNavigationView
的菜单项,对应的Fragment会recreate,这其实不是我们想要的,我们预期的应该是Fragment第一次创建后就一直复用,既保留了当前状态也不会对后端造成不必要的调用。
如果使用FragmentTransaction很好处理,只要缓存一个Fragment集合即可(若要保留Fragment的状态,比如滑动位置,可以使用supportFragmentManager.saveFragmentInstanceState(fragment)
和fragment.setInitialSavedState(savedState)
加载,也可以使用hide/show(fragment)
的方式),但用了Navigation后就没办法了。可以看看Navigation, Saving fragment state评论区的吐槽,里面也有临时的一些解决方案(不实用)。
FragmentTransaction本身也有对状态信息的处理考量,参看commit(), commitNow()和commitAllowingStateLoss()
参考资料#
嵌套Fragment的使用及常见错误
Fragment 生命周期和使用
BottonNavigationView+Fragment切换toolbar标题栏
手把手教你使用Android官方组件Navigation
Playing with Navigation Architecture Components
The Navigation Architecture Component Tutorial: Getting Started
Handle Complex Navigation Flow with Single-Activity Architecture and Android Jetpack’s Navigation component
导航到目的地-popUpTo 和 popUpToInclusive
Difference between add(), replace(), and addToBackStack()
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· .NET Core 托管堆内存泄露/CPU异常的常见思路
· PostgreSQL 和 SQL Server 在统计信息维护中的关键差异
· C++代码改造为UTF-8编码问题的总结
· DeepSeek 解答了困扰我五年的技术问题
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 实操Deepseek接入个人知识库
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
2019-02-20 供应链金融&区块链应用