Fragment自从Android 3.0引入开始,它所承担的角色就是显而易见的。它之于Activity就如html片段之于页面,好处无需赘述。
Fragment的生命周期和Activity一样,都不是由开发人员而是由系统维护的。自然而然的,每当它们被重建时,系统只会去调用它们的无参构造器,带参构造器会被无视。那如果要在它们创建时传入初始化数据咋办呢?这也是为什么类似OnCreateXXX方法里会有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*/ } 底部导航栏切换Fragment效果如下
底部是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。
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的下滑状态丢失,仍是从头部开始显示。