美文网首页android开发技巧
BottomNavigationView + ViewPager

BottomNavigationView + ViewPager

作者: 禄眠 | 来源:发表于2020-02-29 10:23 被阅读0次

    介绍

    如果未使用过BottomNavigationView,请先看之前的文章:BottomNavigationView

    这次例子将使用AS自带的模板创建一个带底部导航栏的Demo

    确保能完美运行以后就准备开始修改

    本篇文章部分代码参考了这个Demo:ArchNavViewPagerImpl

    这个算是找了好久,因为网上大把文章都说了BottomNavigationView + ViewPager的使用,但唯独没说如何在此基础上使用导航,困扰了我好几天,以下解决办法也不是完美的,如果有什么错误或者意见欢迎评论!

    使用

    1. 引入ViewPager2

      implementation "androidx.viewpager2:viewpager2:1.0.0"
      
    1. 去除BottomNavigationView的导航图

      先来看看MainActivity中的代码:

      class MainActivity : AppCompatActivity() {
      
          override fun onCreate(savedInstanceState: Bundle?) {
              super.onCreate(savedInstanceState)
              setContentView(R.layout.activity_main)
              val navView: BottomNavigationView = findViewById(R.id.nav_view)
      
              val navController = findNavController(R.id.nav_host_fragment)
              // Passing each menu ID as a set of Ids because each
              // menu should be considered as top level destinations.
              val appBarConfiguration = AppBarConfiguration(
                  setOf(
                      R.id.navigation_home, R.id.navigation_dashboard, R.id.navigation_notifications
                  )
              )
      
              setupActionBarWithNavController(navController, appBarConfiguration)
              navView.setupWithNavController(navController)
          }
      }
      

      因为要用ViewPager2控件代替NavHostFragment,所以去除这部分的代码,那么就需要使用ViewPager2的监听和BottomNavigationView的监听来控制页面切换

    1. 编写监听器

      val viewPager2: ViewPager2 = findViewById(R.id.viewPager2)
              val bottomNavigationView: BottomNavigationView = findViewById(R.id.nav_view)
      
              viewPager2.adapter = object : FragmentStateAdapter(this) {
                  override fun getItemCount(): Int {
                      return 3
                  }
      
                  override fun createFragment(position: Int): Fragment {
                      return when (position) {
                          0 -> HomeFragment()
                          1 -> DashboardFragment()
                          else -> NotificationsFragment()
                      }
                  }
              }
      
              // 当ViewPager切换页面时,改变底部导航栏的状态
              viewPager2.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
                  override fun onPageSelected(position: Int) {
                      super.onPageSelected(position)
                      bottomNavigationView.menu.getItem(position).isChecked = true
                  }
              })
      
              // 当ViewPager切换页面时,改变ViewPager的显示
              bottomNavigationView.setOnNavigationItemSelectedListener {
                  when (it.itemId) {
                      R.id.navigation_home -> viewPager2.setCurrentItem(0, true)
                      R.id.navigation_dashboard -> viewPager2.setCurrentItem(1, true)
                      R.id.navigation_notifications -> viewPager2.setCurrentItem(2, true)
                  }
                  true
              }
      

      到这就已经能够实现滑动切换和导航栏切换了

      但是!我还想在这基础上再增加页面跳转

    跳转

    1. 修改需要跳转的页面(以HomeFragment为例):

      假设我们需要在从HomeFragment跳转到另一个页面,如果要使用Navigation进行导航,就必须要用到导航图,那么NavHostFragment就只能放在HomeFragment中,所以我们需要再新建一个Fragment,复制HomeFragment中的全部内容,这里取名为HomeFirstFragment:

      fragment_home.xml
      
      <?xml version="1.0" encoding="utf-8"?>
      <androidx.constraintlayout.widget.ConstraintLayout 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:layout_width="match_parent"
          android:layout_height="match_parent">
      
      
          <fragment
              android:id="@+id/fragment"
              android:name="androidx.navigation.fragment.NavHostFragment"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              app:defaultNavHost="true"
              app:layout_constraintBottom_toBottomOf="parent"
              app:layout_constraintEnd_toEndOf="parent"
              app:layout_constraintStart_toStartOf="parent"
              app:layout_constraintTop_toTopOf="parent"
              app:navGraph="@navigation/home_navigation" />
      </androidx.constraintlayout.widget.ConstraintLayout>
      
      home_first_fragment.xml
      
      <?xml version="1.0" encoding="utf-8"?>
      <androidx.constraintlayout.widget.ConstraintLayout 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:layout_width="match_parent"
          android:layout_height="match_parent"
          tools:context=".ui.HomeFirstFragment">
      
          <TextView
              android:id="@+id/text_home"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:layout_marginStart="8dp"
              android:layout_marginTop="8dp"
              android:layout_marginEnd="8dp"
              android:textAlignment="center"
              android:textSize="20sp"
              app:layout_constraintEnd_toEndOf="parent"
              app:layout_constraintStart_toStartOf="parent"
              app:layout_constraintTop_toTopOf="parent" />
      
          <Button
              android:id="@+id/button"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:text="GoToHomeSecondFragment"
              app:layout_constraintBottom_toBottomOf="parent"
              app:layout_constraintEnd_toEndOf="parent"
              app:layout_constraintStart_toStartOf="parent"
              app:layout_constraintTop_toTopOf="parent" />
      
      </androidx.constraintlayout.widget.ConstraintLayout>
      

      简单来说就是拷贝一份HomeFragment,然后把原来的HomeFragment作为导航图的载体

    2. 再写一个跳转页面(以HomeSecondFragment为例):

      代码就不贴了,就是个空的Fragment,这是导航图的xml:

      <?xml version="1.0" encoding="utf-8"?>
         <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/home_navigation"
             app:startDestination="@id/homeFirstFragment">
         
             <fragment
                 android:id="@+id/homeFirstFragment"
                 android:name="com.wzl.bottomnavigationdemo.ui.HomeFirstFragment"
                 android:label="fragment_home_first"
                 tools:layout="@layout/fragment_home_first" >
                 <action
                     android:id="@+id/action_homeFirstFragment_to_homeSecondFragment"
                     app:destination="@id/homeSecondFragment" />
             </fragment>
             <fragment
                 android:id="@+id/homeSecondFragment"
                 android:name="com.wzl.bottomnavigationdemo.ui.HomeSecondFragment"
                 android:label="fragment_home_second"
                 tools:layout="@layout/fragment_home_second" />
         </navigation>
      

      还是看图来的直观点:

      导航图.png

    到这就已经能实现跳转了,就是没有返回键

    GIF展示:

    录屏时被我覆盖了。。。后面反正还有GIF

    1. 添加ToolBar

      因为原先虽然是实现了跳转,但是标题栏还是MainActivity的,也没有返回键

      activity_main.xml中添加 ToolBar,并修改主题

      这里为了方便直接在原主题上进行修改,隐藏自带的ActionBar

      <!-- Base application theme. -->
          <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
              <!-- Customize your theme here. -->
              <item name="windowNoTitle">true</item>
              <item name="windowActionBar">false</item>
              <item name="colorPrimary">@color/colorPrimary</item>
              <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
              <item name="colorAccent">@color/colorAccent</item>
          </style>
      
    1. 设置标题栏

      根据ViewPager和BottomNavigationView的监听进行设置

      // 当ViewPager切换页面时,改变底部导航栏的状态
      viewPager2.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
       override fun onPageSelected(position: Int) {
           super.onPageSelected(position)
           bottomNavigationView.menu.getItem(position).isChecked = true
           when (position) {
               0 -> toolbar.title = "HomeFragment"
               1 -> toolbar.title = "DashboardFragment"
               2 -> toolbar.title = "NotificationFragment"
           }
       }
      })
      
      // 当ViewPager切换页面时,改变ViewPager的显示
      bottomNavigationView.setOnNavigationItemSelectedListener {
       when (it.itemId) {
           R.id.navigation_home -> {
               viewPager2.setCurrentItem(0, true)
               toolbar.title = "HomeFragment"
           }
           R.id.navigation_dashboard -> {
               viewPager2.setCurrentItem(1, true)
               toolbar.title = "DashboardFragment"
           }
           R.id.navigation_notifications -> {
               viewPager2.setCurrentItem(2, true)
               toolbar.title = "NotificationFragment"
           }
       }
       true
      }
      
      setSupportActionBar(toolbar)
      

      记得修改导航图中每个视图对应的Label,对应标题栏的的标题

    1. HomeFragment中设置NavController:

      override fun onActivityCreated(savedInstanceState: Bundle?) {
              super.onActivityCreated(savedInstanceState)
      
              val navHostFragment = childFragmentManager.findFragmentById(R.id.home_container)
                  as NavHostFragment
              val navController = navHostFragment.navController
              NavigationUI.setupWithNavController((activity as MainActivity).toolbar, navController)
          }
      
    1. 设置返回监听

      因为Fragment中并没有onBackPressedonKeyDown方法,所以处理方式也比较暴力,就是在需要返回的Fragment的onResume方法中进行监听:

      override fun onResume() {
       super.onResume()
       if (view == null) {
           return
       }
      
          // 在触摸模式下是否能够获得焦点
       view!!.isFocusableInTouchMode = true
          // 获得焦点
       view!!.requestFocus()
       view!!.setOnKeyListener(object : View.OnKeyListener {
           override fun onKey(v: View?, keyCode: Int, event: KeyEvent?): Boolean {
               return if (event!!.action == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) {
                   Navigation.findNavController(v!!).navigateUp()
                   true
               } else false
           }
      
       })
      }
      

      这样就能够实现按下返回键返回上一级页面而不会直接退出程序,但是经测试如果页面含有输入框,在使用输入框后,这个还是会失效,毕竟失去了焦点

      GIF展示:

      GIF.gif

    结尾

    其实还是有个Bug:比如点击跳转到第二个页面时,我点击底部导航栏或者滑动都会切换页面,且左上角的返回键会一直存在,不过想想也合理,毕竟ToolBar是写在Activity中的,我的想法就是当跳转到其他页面时,禁用ViewPager的滑动以及隐藏底部导航栏。当然这只是猜想,没实践过,看到这篇文章的可以试试。或者如果各位有什么更好的解决办法欢迎评论!

    我把源码放到Github上了:BottomNavigationDemo

    相关文章

      网友评论

        本文标题:BottomNavigationView + ViewPager

        本文链接:https://www.haomeiwen.com/subject/mjyuhhtx.html