BottomNavigation | 青训营

60 阅读2分钟

这是我参与「第四届青训营 」笔记创作活动的第4天

jetpack 提供了 navigation 方案,使我们可以快速实现底部导航栏、Drawer等,下面介绍怎么用其实现底部导航栏

引入依赖

dependencies {
    ...
    // navigation
    implementation 'androidx.navigation:navigation-fragment:2.4.2'
    implementation "androidx.navigation:navigation-ui:2.4.2"
}

准备好 Fragment

创建 fragment 页面用于切换

页面一

<?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.dashboard.DashboardFragment">

    <TextView
        android:id="@+id/text_dashboard"
        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_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

页面二

<?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.home.HomeFragment">

    <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_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

页面三

<?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.notifications.NotificationsFragment">

    <TextView
        android:id="@+id/text_notifications"
        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_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

创建一个 menu 作为 BottomNavigation 的显示item

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">

    <item
        android:id="@+id/navigation_home"
        android:icon="@drawable/ic_home_black_24dp"
        android:title="@string/title_home" />

    <item
        android:id="@+id/navigation_dashboard"
        android:icon="@drawable/ic_dashboard_black_24dp"
        android:title="@string/title_dashboard" />

    <item
        android:id="@+id/navigation_notifications"
        android:icon="@drawable/ic_notifications_black_24dp"
        android:title="@string/title_notifications" />

</menu>

创建 Navigation 作为导航容器

<?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/mobile_navigation"
    app:startDestination="@+id/navigation_home">

    <fragment
        android:id="@+id/navigation_home"
        android:name="com.example.bottomnavigationtest.ui.home.HomeFragment"
        android:label="@string/title_home"
        tools:layout="@layout/fragment_home" />

    <fragment
        android:id="@+id/navigation_dashboard"
        android:name="com.example.bottomnavigationtest.ui.dashboard.DashboardFragment"
        android:label="@string/title_dashboard"
        tools:layout="@layout/fragment_dashboard" />

    <fragment
        android:id="@+id/navigation_notifications"
        android:name="com.example.bottomnavigationtest.ui.notifications.NotificationsFragment"
        android:label="@string/title_notifications"
        tools:layout="@layout/fragment_notifications" />
</navigation>

在主页面中添加 BottomNavigationView

<?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"
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingTop="?attr/actionBarSize">

    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/nav_view"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="0dp"
        android:layout_marginEnd="0dp"
        android:background="?android:attr/windowBackground"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:menu="@menu/bottom_nav_menu" />

    <fragment
        android:id="@+id/nav_host_fragment_activity_main"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:defaultNavHost="true"
        app:layout_constraintBottom_toTopOf="@id/nav_view"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:navGraph="@navigation/mobile_navigation" />

</androidx.constraintlayout.widget.ConstraintLayout>

在主页面中指定 NavigationUI

class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
        val navView: BottomNavigationView = binding.navView
        val navController = findNavController(R.id.nav_host_fragment_activity_main)
        val appBarConfiguration = AppBarConfiguration(
            setOf(
                R.id.navigation_home, R.id.navigation_dashboard, R.id.navigation_notifications
            )
        )
        setupActionBarWithNavController(navController, appBarConfiguration)
        navView.setupWithNavController(navController)
    }
}

自定义部分

上面只是介绍了关于 BottomNavigation 的最基本的用法,下面记录一下在开发中使用的自定义过程

改变字体大小

  • 为了使得 BottomNavigation 的 item 在被选中和未被选中时显示不同的字体大小,需要自定义 style:

        <!-- 底部导航栏 选中字体-->
        <style name="bottom_navigation_title_active">
            <item name="android:textSize">15sp</item>
        </style>
    
        <!-- 底部导航栏 未选中字体-->
        <style name="bottom_navigation_title_inactive">
            <item name="android:textSize">13sp</item>
        </style>
    
  • 定义好了样式,下面将其设置到 BottomNavigation 中

app:itemTextAppearanceActive="@style/bottom_navigation_title_active"
app:itemTextAppearanceInactive="@style/bottom_navigation_title_inactive"
  • 到此,就实现了被选中的 item 与未被选中的 item 的字体大小不一样的效果了

  • 还可以进行其他的设置,比如可以让颜色也显示出不同的颜色

添加选中时动画

  • 选中时先让图标放大再缩小回原样

  • 具体步骤:

    • 先拿到 BottomNavigation 的实例
    • 对每个 item 单独进行设置
    val navView: BottomNavigationView = binding.navView
    navView.menu.forEach {
        val itemView = findViewById<BottomNavigationItemView>(it.itemId)
        // 设置点击动画,因为自己设置了点击事件,navigation提供的导航功能就被拦截掉了,所以需要自己进行页面跳转
        itemView.setOnClickListener { item->
                                     val animatorSet = AnimatorSet()
                                     animatorSet
                                     .play(ObjectAnimator.ofFloat(item,"scaleX",1f,1.1f,0.9f,1f) )
                                     .with(ObjectAnimator.ofFloat(item,"scaleY",1f,1.1f,0.9f,1f))
                                     animatorSet.duration = 500L
                                     animatorSet.start()
                                     // 根据 bottomItemView, 手动设置被选中
                                     it.isChecked = true
                                    }
            when(item.id){
            R.id.homeFragment -> {
                navController.navigate(R.id.homeFragment,null,navOptions)
            }
            R.id.hotListFragment -> {
                navController.navigate(R.id.hotListFragment,null,navOptions)
            }
            R.id.plusFragment -> {
                navController.navigate(R.id.plusFragment,null,navOptions)
            }
            R.id.messageFragment -> {
                navController.navigate(R.id.messageFragment,null,navOptions)
            }
            R.id.profileFragment -> {
                navController.navigate(R.id.profileFragment,null,navOptions)
            }
        }
        // 根据 bottomItemView, 手动设置被选中
        it.isChecked = true
    }
    
  • 注意:

    • 由于这里对每个 BottomNavigation 的 item 设置了点击事件,NavigationUI 就不会帮我们处理点击事件了,也就是页面切换以及选中显示功能也会失效了,所以需要手动进行导航了

总结

  • Navigation 只是提供了一种方便的方案,让我们能够快速实现需要的效果,如果需要跟多的自定义的功能,可以考虑其他实现方案
  • 使用步骤:
    • 创建好要用的 Fragment
    • 创建一个 menu 用于作为 BottomNavigation 的显示
    • 创建 navigation 将需要管理的 Fragment 添加进去
    • 在主页面中添加 BottomNavigation,并添加 fragment 到其中
    • 在 代码中指定 NavigationUI
  • 注意:
    • menu中 item 的 id,需要与 Fragment 的 id 一致,否则没法正常跳转