2021-02-20

【从零开始撸一个App】Fragment和导航中的使用

Fragment简介

Fragment自从Android 3.0引入开始,它所承担的角色就是显而易见的。它之于Activity就如html片段之于页面,好处无需赘述。

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组件,各个菜单在另外app:menu="xxx"指定。此处菜单定义如下

<?

然后在代码中设置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。

上述手动控制Fragment的切换太麻烦。2018 I/O大会上,Google隆重推出一个新的架构组件:Navigation。它提供了多Fragment之间的转场、栈管理。在抽屉式/底部/顶部导航栏的需求中都可以使用这个组件。

使用:在res目录下新建navigation文件夹,然后新建一个navigation graph设为bottom_navigation:

<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方式最好不要同时使用,它俩的返回堆栈似乎不是同一个,回退时会有问题。不能同时使用还使得下面两个问题不好解决。

  1. 使用Navigation,Fragment可以相互跳转没问题,但状态丢失了。比如A下滑一定距离后跳转到B,B回退到A,A的下滑状态丢失,仍是从头部开始显示。

  2. 每次点击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()









原文转载:http://www.shaoqun.com/a/571586.html

跨境电商:https://www.ikjzd.com/

auditor:https://www.ikjzd.com/w/2437

派代:https://www.ikjzd.com/w/2197


Fragment简介Fragment自从Android3.0引入开始,它所承担的角色就是显而易见的。它之于Activity就如html片段之于页面,好处无需赘述。Fragment实例由Activity的FragmentManager管理,其生命周期和Activity一样,都不是由开发人员而是由系统维护的。自然而然的,每当它们被重建时,系统只会去调用它们的无参构造器,带参构造器会被无视。那如果要在它
贝贝网:https://www.ikjzd.com/w/1321
pocket:https://www.ikjzd.com/w/1903
海拍客:https://www.ikjzd.com/w/1742
口述:在结婚纪念日决定与他离婚:http://lady.shaoqun.com/m/a/130574.html
终极指南:如何销售季节性产品?:https://www.ikjzd.com/tl/107734
新手课堂 | Hold住FBA库存管理,让不必要的物流费用和损失统统消失:https://www.ikjzd.com/home/21288

No comments:

Post a Comment