Android 底部導(dǎo)航欄+頁面切換

λ:

2021.9.9更新: 之前版本把頁面切換直接放進(jìn)了Activity, 跳轉(zhuǎn)其他頁是靠啟動(dòng)新Activity厂镇。但實(shí)踐中都是單Activity模式,所以重新整理一下诗箍。

# 倉庫地址: https://github.com/lzyprime/android_demos
# branch: bottom_navigation

git clone -b bottom_navigation https://github.com/lzyprime/android_demos

底部導(dǎo)航配合多頁面切換是常見邏輯政恍,微信敬辣,qq,抖音蟆豫,淘寶等等议忽,常見app里幾乎都有這種設(shè)計(jì)。

底部導(dǎo)航欄:BottomNavgationView + menu, 涉及到圖標(biāo)和標(biāo)題在點(diǎn)擊時(shí)的變化(顏色十减,大小栈幸,選中與未選中圖標(biāo)變化)

頁面切換有兩個(gè)方案:

  1. Navigation組件

  2. ViewPager2

參考 使用 NavigationUI 更新界面組件。 會(huì)發(fā)現(xiàn)用navigation組件實(shí)現(xiàn)頁面切換可以一行代碼搞定帮辟。但是單Activity模式下速址,會(huì)有NavHost嵌套,需要處理層級問題由驹。并且默認(rèn)情況下芍锚,頁面是不保存狀態(tài)的,切頁面每次都重建蔓榄,2.4.0會(huì)加saveState處理并炮。

ViewPager2,底層是RecyclerView, 相比Navgation會(huì)有頁面緩存甥郑,支持滑動(dòng)切頁手勢逃魄,也不會(huì)有NavHost嵌套問題。

Activity

Activity
|- MainNavHost
  |- MainNavFragment(home)
  | |- HomePageNavHost
  | | |- Item1Fragment(home)
  | | |- Item2Fragment
  | | |- Item3Fragment
  | | 
  | |- BottomNavgationView
  |  
  |  
  |- SecondaryFragment
    |- ViewPager
    | |- Item3Fragment(home)
    | |- Item2Fragment
    | |- Item1Fragment
    |- BottomNavgationView

項(xiàng)目總體層次圖澜搅。問題主要出在MainNavHost,HomePageNavHost嵌套問題上伍俘。

<!-- activity_main.xml -->
<androidx.coordinatorlayout.widget.CoordinatorLayout ...>
    <androidx.fragment.app.FragmentContainerView
        ...
        android:id="@+id/mainNavHost"
        android:name="androidx.navigation.fragment.NavHostFragment"
        app:defaultNavHost="true"
        app:navGraph="@navigation/main_graph" />

</androidx.coordinatorlayout.widget.CoordinatorLayout>

Activity中只放一個(gè)NavHostFragment組織全局導(dǎo)航。 defaultNavHost=true攔截系統(tǒng)返回按鈕, 執(zhí)行navigateUp勉躺。

main_graph:

main_graph

Navigation 實(shí)現(xiàn)頁面切換

<!-- main_nav_fragment.xml -->
<LinearLayout ...>
    <androidx.fragment.app.FragmentContainerView
        ...
        android:id="@+id/homePageNavHost"
        android:name="androidx.navigation.fragment.NavHostFragment"
        app:navGraph="@navigation/main_nav_graph" />
    
    <com.google.android.material.bottomnavigation.BottomNavigationView
        ...
        android:id="@+id/mainBtmNavView"
        app:menu="@menu/main_nav_menu" />
</LinearLayout>

main_nav_graph:

main_nav_graph

main_nav_menu:

main_nav_menu
class MainNavFragment : Fragment(R.layout.main_nav_fragment) {
    private val binding by viewBinding<MainNavFragmentBinding>()
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        val navController =
            (childFragmentManager.findFragmentById(R.id.homePageNavHost) as NavHostFragment).navController
        binding.mainBtmNavView.setupWithNavController(navController)
    }
}

通過setupWithNavController將NavHost與BottomNavgationView綁定。內(nèi)部就是給BottomNavgationView設(shè)置Listener, 監(jiān)聽onNavigationItemSelected(MenuItem item)事件饵溅。然后通過item.itemId導(dǎo)航到同id目的地柳弄。

所以,menu 中id要與 導(dǎo)航圖 中一致

但是如果想在子頁面Item1, Item2, Item3操作全局導(dǎo)航概说,就要想辦法拿到MainNavHost的navController:

class Item1Fragment : Fragment(R.layout.item1_fragment) {
    private val binding: Item1FragmentBinding by viewBinding()
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        
        val globalNavHostController = requireActivity().findNavController(R.id.mainNavHost)
        ...
    }
}

按照層級關(guān)系,在子頁面調(diào)用findNavController()會(huì)返回 HomePageNavHost 的 controller嚣伐。所以要額外操作糖赔,去拿Activity中MainNavHost。 凡是HomePageNavHost嵌套圖中的View都會(huì)有這種問題轩端。

ViewPager2

ViewPager2就不會(huì)有這些問題放典,因?yàn)樵谕粋€(gè)NavHost下。

<!-- secondary_nav_fragment.xml -->
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout ...>
    <androidx.viewpager2.widget.ViewPager2
        android:id="@+id/secondaryViewPager"
        ... />

    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/secondaryBtmNavView"
        app:menu="@menu/secondary_nav_menu" 
        ... />
</LinearLayout>
class SecondaryNavFragment : Fragment(R.layout.secondary_nav_fragment) {
    private val binding by viewBinding<SecondaryNavFragmentBinding>()

    private val fragments = arrayOf(Item3Fragment(), Item2Fragment(), Item1Fragment())
    private val itemIds = arrayOf(R.id.item3, R.id.item2, R.id.item1)

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        binding.secondaryViewPager.adapter = object : FragmentStateAdapter(this) {
            override fun getItemCount(): Int = fragments.size
            override fun createFragment(position: Int): Fragment = fragments[position]
        }

        binding.secondaryViewPager.offscreenPageLimit = fragments.size

        //binding.secondaryViewPager.isUserInputEnabled = false // 禁用滑動(dòng)

        binding.secondaryViewPager.registerOnPageChangeCallback(object :
            ViewPager2.OnPageChangeCallback() {
            override fun onPageSelected(position: Int) {
                binding.secondaryBtmNavView.selectedItemId = itemIds[position]
            }
        })

        binding.secondaryBtmNavView.setOnItemSelectedListener {
            binding.secondaryViewPager.currentItem = itemIds.indexOf(it.itemId)
            true
        }
    }

}

ViewPager2設(shè)置:

  • 設(shè)置 FragmentStateAdapter。由于ViewPager2底層是RecyclerView, 所以這肯定是個(gè)RecyclerView.Adapter子類奋构。
  • offscreenPageLimit壳影, 緩存頁面數(shù)
  • 注冊 OnPageChangeCallback,監(jiān)聽頁面滑動(dòng)弥臼,頁面切換完成時(shí)宴咧,設(shè)置底部導(dǎo)航欄選中項(xiàng)。
  • 如果不想滑動(dòng)切換頁面, isUserInputEnabled = false

BottomNavigationView設(shè)置:

設(shè)置點(diǎn)擊事件監(jiān)聽径缅,ViewPager2 跳轉(zhuǎn)對應(yīng)頁面掺栅。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市纳猪,隨后出現(xiàn)的幾起案子氧卧,更是在濱河造成了極大的恐慌,老刑警劉巖氏堤,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件沙绝,死亡現(xiàn)場離奇詭異,居然都是意外死亡鼠锈,警方通過查閱死者的電腦和手機(jī)闪檬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來脚祟,“玉大人谬以,你說我怎么就攤上這事∮勺溃” “怎么了为黎?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長行您。 經(jīng)常有香客問我铭乾,道長,這世上最難降的妖魔是什么娃循? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任炕檩,我火速辦了婚禮,結(jié)果婚禮上捌斧,老公的妹妹穿的比我還像新娘笛质。我一直安慰自己,他們只是感情好捞蚂,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布妇押。 她就那樣靜靜地躺著,像睡著了一般姓迅。 火紅的嫁衣襯著肌膚如雪敲霍。 梳的紋絲不亂的頭發(fā)上俊马,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天,我揣著相機(jī)與錄音肩杈,去河邊找鬼柴我。 笑死,一個(gè)胖子當(dāng)著我的面吹牛扩然,可吹牛的內(nèi)容都是我干的艘儒。 我是一名探鬼主播,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼与学,長吁一口氣:“原來是場噩夢啊……” “哼彤悔!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起索守,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤晕窑,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后卵佛,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體杨赤,經(jīng)...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年截汪,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了疾牲。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,779評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡衙解,死狀恐怖阳柔,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情蚓峦,我是刑警寧澤舌剂,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站暑椰,受9級特大地震影響霍转,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜一汽,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一避消、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧召夹,春花似錦岩喷、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至枫虏,卻和暖如春妇穴,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背隶债。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工腾它, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人死讹。 一個(gè)月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓瞒滴,卻偏偏與公主長得像,于是被迫代替她去往敵國和親赞警。 傳聞我的和親對象是個(gè)殘疾皇子妓忍,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評論 2 354

推薦閱讀更多精彩內(nèi)容