1. 程式人生 > >【從零開始擼一個App】Fragment和導航中的使用

【從零開始擼一個App】Fragment和導航中的使用

## Fragment簡介 `Fragment`自從Android 3.0引入開始,它所承擔的角色就是顯而易見的。它之於`Activity`就如html片段之於頁面,好處無需贅述。 Fragment例項由Activity的`FragmentManager`管理,其生命週期和Activity一樣,都不是由開發人員而是由系統維護的。自然而然的,每當它們被重建時,系統只會去呼叫它們的無參構造器,帶參構造器會被無視。那如果要在它們建立時傳入初始化資料咋辦呢?這也是為什麼會有`Bundle`這個玩意兒的存在,就是用於開發人員存取相關資料,如下所示: ```kotlin /** * 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)來傳遞引數](https://blog.csdn.net/tu_bingbing/article/details/24143249) ## 底部導航欄切換Fragment 效果如下 ![](https://img2020.cnblogs.com/blog/474041/202102/474041-20210219095644348-432809992.gif) ### BottomNavigationView 底部是`BottomNavigationView`元件,各個選單在另外xml中定義,然後通過`app:menu="xxx"`指定。此處選單定義如下 ```xml ``` 然後在程式碼中設定`BottomNavigationView.setOnNavigationItemSelectedListener`,判斷當前選中的選單項,手動切換Fragment,需要用到`FragmentTransaction`。如下示例 ```kotlin 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: ```xml
``` 注意每個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`建立聯絡。 ```xml
``` `app:defaultNavHost`: If set to `true`, the navigation host will intercept the Back button. 最後將導航欄與Navigation關聯 ```kotlin 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](https://github.com/android/architecture-components-samples/issues/530)評論區的吐槽,裡面也有臨時的一些解決方案(不實用)。 *FragmentTransaction本身也有對狀態資訊的處理考量,參看[commit(), commitNow()和commitAllowingStateLoss()](https://www.cnblogs.com/mengdd/p/5827045.html)* ## 參考資料 [巢狀Fragment的使用及常見錯誤](https://www.cnblogs.com/mengdd/p/5552721.html) [Fragment 生命週期和使用](https://www.jianshu.com/p/70d7bfae18f3) [BottonNavigationView+Fragment切換toolbar標題欄](https://blog.csdn.net/Johan666/article/details/90761232) [手把手教你使用Android官方元件Navigation](https://blog.csdn.net/bug_bug_chen/article/details/80541101) [Playing with Navigation Architecture Components](https://joaoalves.dev/posts/playing-with-navigation-architecture-components/) [The Navigation Architecture Component Tutorial: Getting Started](https://www.raywenderlich.com/6014-the-navigation-architecture-component-tutorial-getting-started) [Handle Complex Navigation Flow with Single-Activity Architecture and Android Jetpack’s Navigation component](https://proandroiddev.com/handle-complex-navigation-flow-with-single-activity-and-android-jetpacks-navigation-component-6ad988602902) [導航到目的地-popUpTo 和 popUpToInclusive](https://developer.android.com/guide/navigation/navigation-navigate#pop) [Difference between add(), replace(), and addToBackStack()](https://stackoverflow.com/a/5