舊機寶開發筆記之:BottomNavigationView+ViewPager+Fragment實現的APP首頁

舊機寶原本打算採用類似於QQ的側邊劃出菜單的首頁佈局,但是鑑於目前的全面屏手機傾向於用屏幕側邊滑動來代替之前的返回按鈕,導致側邊劃出菜單的體驗不是很好,容易混淆,故而選用下方導航欄的經典頁面佈局。又因爲之前有人評價首頁直接可以左右滑動切換頁面的方式很貼心,故而選用BottomNavigationView+ViewPager的組合來實現首頁的可滑動佈局。爲了代碼更有條理,ViewPager的每一個頁面都通過一個Fragment來實現具體業務。效果如下:
在這裏插入圖片描述

第一步:佈局

在這裏插入圖片描述
很簡單,上面是ViewPager,下面是BottomNavigationView

第二步:爲ViewPager設置adapter來填充頁面

ViewPager需要通過adapter來填充內容,考慮到ViewPager+Fragment是非常常用的組合,安卓api爲我們提供了FragmentPagerAdapter來實現這一點,不過需要進行一些小小的補充來使FragmentPagerAdapter具有更好的普適性,比如通過添加PageTitle可以使得之後在使用tablayout+ViewPager+Fragment的組合的時候避免很多錯誤。

class MyFragmentPagerAdapter : FragmentPagerAdapter {
    private var fragmentArrayList: ArrayList<Fragment> = ArrayList()
    private var titles: ArrayList<String> = ArrayList()

    constructor(fm: FragmentManager) : super(fm) {}

    constructor(
        fm: FragmentManager,
        fragments: Array<Fragment>,
        titles: Array<String>
    ) : super(fm) {
        this.titles.addAll(Arrays.asList(*titles))
        fragmentArrayList.addAll(Arrays.asList(*fragments))
    }

    constructor(
        fm: FragmentManager,
        fragments: ArrayList<Fragment>,
        titles: ArrayList<String>
    ) : super(fm) {
        this.titles = titles
        fragmentArrayList = fragments
    }

    override fun getPageTitle(position: Int): CharSequence? {
        return titles[position]
    }

    override fun getItem(position: Int): Fragment {
        return fragmentArrayList[position]
    }

    override fun getCount(): Int {
        return fragmentArrayList.size
    }
}

構造方法上包含了對數組和list的兼容,因爲很多時候數組使用起來更簡潔,而adapter內部的處理list卻更勝一籌。我們新建這個自定義的adapter並傳入三個fragment和對用頁面的標題來爲viewpager來填充數據:

val fragmentPagerAdapter = MyFragmentPagerAdapter(
            supportFragmentManager,
            arrayOf(DeviceFragment(), PlanFragment(), MineFragment()),
            arrayOf("設備", "計劃", "我的")
        )
        viewPager.adapter = fragmentPagerAdapter

之後頁面的填充就要分別到DeviceFragment(), PlanFragment(), MineFragment()這三個fragment裏去實現了。

第三步:通過點擊底部按鈕來切換ViewPager到對應頁面

對BottomNavigationView設置OnNavigationItemSelectedListener來監聽導航按鈕的點擊事件,通過對當前點擊按鈕的itemId來判斷點擊了哪個按鈕,然後直接將ViewPager切換到對應位置的頁面:

navView.setOnNavigationItemSelectedListener {
            viewPager.currentItem = when (menuItem.itemId) {
                R.id.navigation_device -> 0
                R.id.navigation_plan -> 1
                R.id.navigation_mine -> 2
                else -> 0
            }
            true
        }

第四步:通過滑動ViewPager來同步更新導航按鈕的狀態

ViewPager可以通過左右滑動來切換頁面,但是當我們滑動切換頁面以後,會發現底部導航欄的按鈕狀態並沒有更新:當前頁面的導航按鈕並不處於選中狀態。我們需要導航欄按鈕跟隨當前的頁面變化而變化,需要爲ViewPager添加OnPageChangeListener監聽,在頁面發生改變的時候,更新導航欄按鈕的狀態,使之產生聯動:

viewPager.addOnPageChangeListener(object : OnPageChangeListener {
            override fun onPageScrollStateChanged(state: Int) {
            }

            override fun onPageScrolled(
                position: Int,
                positionOffset: Float,
                positionOffsetPixels: Int
            ) {
            }

            override fun onPageSelected(position: Int) {
                menuItem.isChecked = false
                menuItem = navView.menu[position]
                menuItem.isChecked = true
            }

        })

menuItem是一個用於保存已選導航按鈕的MenuItem對象,每次點擊的按鈕都會保留在此,當有新的導航按鈕點擊時,會先將剛纔點擊的按鈕設置成非點擊狀態,再保存當前點擊的按鈕,並將其狀態設置爲點擊狀態。到這就實現了BottomNavigationView+ViewPager的雙向聯動了。

第五步:導航欄圖片的替換

雖然到這首頁已經可以正常使用了,但是,默認的非選中灰色、選中主題色的變化太單調了,我們還是希望導航欄圖標可以在選中的狀態變成一個彩色圖片,這樣更醒目也更美觀。
我們需要先把系統的默認顏色變化列表置空,讓其不發揮作用:

navView.itemIconTintList = null

緊接着在一開始就將第一個按鈕設置爲選中狀態,對應的viewpager默認也會顯示第一個頁面:

menuItem = navView.menu[0]
menuItem.setIcon(R.drawable.ic_item_device_s)

我們寫一個方法來替換按鈕圖片

fun changeItemIcon(menuItem: MenuItem, isChecked: Boolean) {
        when (menuItem.itemId) {
            R.id.navigation_device -> menuItem.setIcon(if (isChecked) R.drawable.ic_item_device_s else R.drawable.ic_item_device)
            R.id.navigation_plan -> menuItem.setIcon(if (isChecked) R.drawable.ic_item_need_s else R.drawable.ic_item_need)
            R.id.navigation_mine -> menuItem.setIcon(if (isChecked) R.drawable.ic_item_my_s else R.drawable.ic_item_my)
        }
    }

然後在導航按鈕被點擊時:1、將剛纔點擊的按鈕恢復爲未點擊的對應圖片。2、將當前點擊的按鈕替換爲選中的圖片。這些操作要在導航按鈕被點擊和viewpager被滑動兩個改變頁面的入口處都添加:

navView.setOnNavigationItemSelectedListener {
            changeItemIcon(menuItem, false)
            menuItem = it
            changeItemIcon(menuItem, true)
            viewPager.currentItem = when (menuItem.itemId) {
                R.id.navigation_device -> 0
                R.id.navigation_plan -> 1
                R.id.navigation_mine -> 2
                else -> 0
            }
            true
        }
viewPager.addOnPageChangeListener(object : OnPageChangeListener {
            override fun onPageScrollStateChanged(state: Int) {
            }

            override fun onPageScrolled(
                position: Int,
                positionOffset: Float,
                positionOffsetPixels: Int
            ) {
            }

            override fun onPageSelected(position: Int) {
                menuItem.isChecked = false
                changeItemIcon(menuItem, false)
                menuItem = navView.menu[position]
                menuItem.isChecked = true
                changeItemIcon(menuItem, true)
            }

        })

到這,舊機寶的首頁就開發完畢了。
大家有更好的想法歡迎討論。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章