介绍
如果未使用过BottomNavigationView,请先看之前的文章:BottomNavigationView
这次例子将使用AS自带的模板创建一个带底部导航栏的Demo
确保能完美运行以后就准备开始修改
参考链接:ArchNavViewPagerImpl
这个算是找了好久,因为网上大把文章都说了BottomNavigationView + ViewPager的使用,但唯独没说如何在此基础上使用导航,困扰了我好几天,以下解决办法也不是完美的,如果有什么错误或者意见欢迎评论!
使用
-
引入ViewPager2
implementation "androidx.viewpager2:viewpager2:1.0.0"
-
去除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的监听来控制页面切换
-
编写监听器
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 }
到这就已经能够实现滑动切换和导航栏切换了
但是!我还想在这基础上再增加页面跳转
跳转
-
修改需要跳转的页面(以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作为导航图的载体
-
再写一个跳转页面(以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>
还是看图来的直观点:
到这就已经能实现跳转了,就是没有返回键
GIF展示:
录屏时被我覆盖了。。。后面反正还有GIF
-
添加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>
-
设置标题栏
根据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,对应标题栏的的标题
-
在
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) }
-
设置返回监听
因为Fragment中并没有
onBackPressed
、onKeyDown
方法,所以处理方式也比较暴力,就是在需要返回的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展示:
结尾
其实还是有个Bug:比如点击跳转到第二个页面时,我点击底部导航栏或者滑动都会切换页面,且左上角的返回键会一直存在,不过想想也合理,毕竟ToolBar是写在Activity中的,我的想法就是当跳转到其他页面时,禁用ViewPager的滑动以及隐藏底部导航栏。当然这只是猜想,没实践过,看到这篇文章的可以试试。或者如果各位有什么更好的解决办法欢迎评论!
我把源码放到Github上了:BottomNavigationDemo