Android Navigation 组件(进阶篇)

1、使用 NavigationUI 更新界面组件

Navigation 组件包含 NavigationUI 类。此类包含使用顶部应用栏、抽屉式导航栏和底部导航栏管理导航的静态方法。

可参阅:
Android Material 常用组件详解(七)—— BottomNavigationView 使用详解
Android Material 常用组件详解(九)—— NavigationView 使用详解
Android Material 常用组件详解(十)—— ToolBar、AppBarLayout、CoordinatorLayout、CollapsingToolbarLayout 使用详解

1.1 顶部应用栏

顶部应用栏在应用顶部提供了一个固定位置,用于显示当前屏幕的信息和操作。如需详细了解应用栏,请参阅设置应用栏。
Android Navigation 组件(进阶篇)_第1张图片

NavigationUI 包含在用户浏览您的应用时自动更新顶部应用栏中内容的方法。例如,NavigationUI 使用导航图中的目的地标签android:label及时更新顶部应用栏的标题,即应用栏的标题会显示与之对应的android:label内容。

<navigation>
    <fragment ...
              android:label="Page title">
      ...
    fragment>
navigation>

NavigationUI 支持以下顶部应用栏类型:

  • Toolbar
  • CollapsingToolbarLayout
  • ActionBar

Toolbar

NavigationUI 使用 AppBarConfiguration 对象管理在应用显示区域左上角的导航按钮行为。导航按钮的行为会根据用户是否位于顶级目的地而变化。
当用户位于顶级目的地时,如果目的地使用 DrawerLayout,导航按钮会变为抽屉式导航栏图标Android Navigation 组件(进阶篇)_第2张图片 。如果目的地没有使用 DrawerLayout,导航按钮处于隐藏状态。当用户位于任何其他目的地上时,导航按钮会显示为向上按钮 Android Navigation 组件(进阶篇)_第3张图片

<LinearLayout>
    <androidx.appcompat.widget.Toolbar
        android:id="@+id/toolbar" />
     <fragment
        android:id="@+id/nav_host_fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        ... />
    ...
LinearLayout>
        val navController = findNavController(R.id.nav_host_fragment)
        val appBarConfiguration = AppBarConfiguration(navController.graph)
//        val appBarConfiguration = AppBarConfiguration(setOf( R.id.fragmentA, R.id.fragmentB, R.id.fragmentC))
        toolbar.setupWithNavController(navController, appBarConfiguration)

CollapsingToolbarLayout

<LinearLayout>
    <com.google.android.material.appbar.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="@dimen/tall_toolbar_height">

        <com.google.android.material.appbar.CollapsingToolbarLayout
            android:id="@+id/collapsing_toolbar_layout"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:contentScrim="?attr/colorPrimary"
            app:expandedTitleGravity="top"
            app:layout_scrollFlags="scroll|exitUntilCollapsed|snap">

            <androidx.appcompat.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                app:layout_collapseMode="pin"/>
        com.google.android.material.appbar.CollapsingToolbarLayout>
    com.google.android.material.appbar.AppBarLayout>

    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/nav_host_fragment"
        ... />
    ...
LinearLayout>
override fun onCreate(savedInstanceState: Bundle?) {
    setContentView(R.layout.activity_main)
    
    val layout = findViewById<CollapsingToolbarLayout>(R.id.collapsing_toolbar_layout)
    val toolbar = findViewById<Toolbar>(R.id.toolbar)
    
    val navController = findNavController(R.id.nav_host_fragment)
    val appBarConfiguration = AppBarConfiguration(navController.graph)
    layout.setupWithNavController(toolbar, navController, appBarConfiguration)
}

默认应用栏

如需向默认操作栏添加导航支持,请通过主 Activity 的 onCreate() 方法调用 setupActionBarWithNavController(),如下所示。请注意,您需要在 onCreate() 之外声明 AppBarConfiguration,因为您在替换 onSupportNavigateUp() 时也使用该方法:

private lateinit var appBarConfiguration: AppBarConfiguration

override fun onCreate(savedInstanceState: Bundle?) {

	val navController = findNavController(R.id.nav_host_fragment)
    appBarConfiguration = AppBarConfiguration(navController.graph)
    setupActionBarWithNavController(navController, appBarConfiguration)
}

override fun onSupportNavigateUp(): Boolean {
    val navController = findNavController(R.id.nav_host_fragment)
    return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
}

2.2 抽屉式导航栏

抽屉式导航栏是显示应用主导航菜单的界面面板。当用户触摸应用栏中的抽屉式导航栏图标 Android Navigation 组件(进阶篇)_第4张图片或用户从屏幕的左边缘滑动手指时,就会显示抽屉式导航栏。
Android Navigation 组件(进阶篇)_第5张图片

XML布局


<androidx.drawerlayout.widget.DrawerLayout 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/drawer_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    tools:openDrawer="start">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <androidx.appcompat.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary" />

        <fragment
            android:id="@+id/nav_host_fragment"
            android:name="androidx.navigation.fragment.NavHostFragment"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:defaultNavHost="true"
            app:navGraph="@navigation/mobile_navigation" />

    LinearLayout>

    <com.google.android.material.navigation.NavigationView
        android:id="@+id/nav_view"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        android:fitsSystemWindows="true"
        app:headerLayout="@layout/nav_header_main"
        app:menu="@menu/activity_main_drawer" />
androidx.drawerlayout.widget.DrawerLayout>

NavigationView的菜单每个选项的id必须要和导航图中的每个fragment的id相同,这样才能当用户点击菜单项时,应用会自动使用相同的 id 导航到相应目的地。


<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/nav_home">

    <fragment
        android:id="@+id/nav_home"
        android:name="com.matt.myapplication3.ui.home.HomeFragment"
        android:label="@string/menu_home"
        tools:layout="@layout/fragment_home" />

    <fragment
        android:id="@+id/nav_gallery"
        android:name="com.matt.myapplication3.ui.gallery.GalleryFragment"
        android:label="@string/menu_gallery"
        tools:layout="@layout/fragment_gallery" />

    <fragment
        android:id="@+id/nav_slideshow"
        android:name="com.matt.myapplication3.ui.slideshow.SlideshowFragment"
        android:label="@string/menu_slideshow"
        tools:layout="@layout/fragment_slideshow" />
navigation>

<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    tools:showIn="navigation_view">

    <group android:checkableBehavior="single">
        <item
            android:id="@+id/nav_home"
            android:icon="@drawable/ic_menu_camera"
            android:title="@string/menu_home" />
        <item
            android:id="@+id/nav_gallery"
            android:icon="@drawable/ic_menu_gallery"
            android:title="@string/menu_gallery" />
        <item
            android:id="@+id/nav_slideshow"
            android:icon="@drawable/ic_menu_slideshow"
            android:title="@string/menu_slideshow" />
    group>
menu>

将抽屉菜单与导航组件、应用栏关联

class MainActivity : AppCompatActivity() {

    private lateinit var appBarConfiguration: AppBarConfiguration

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // 将自定义的toolbar取代原本的actionbar
        val toolbar: Toolbar = findViewById(R.id.toolbar)
        setSupportActionBar(toolbar)

        val drawerLayout: DrawerLayout = findViewById(R.id.drawer_layout)
        val navView: NavigationView = findViewById(R.id.nav_view)
        val navController = findNavController(R.id.nav_host_fragment)

        // 将导航组件与应用栏关联
//        appBarConfiguration = AppBarConfiguration(navController.graph, drawerLayout)
//        appBarConfiguration = AppBarConfiguration(navView.menu, drawerLayout)
       appBarConfiguration = AppBarConfiguration(setOf(
                R.id.nav_home, R.id.nav_gallery, R.id.nav_slideshow), drawerLayout)
        setupActionBarWithNavController(navController, appBarConfiguration)

        // 将抽屉式导航栏与导航组件
        navView.setupWithNavController(navController)
    }


    override fun onSupportNavigateUp(): Boolean {
        val navController = findNavController(R.id.nav_host_fragment)
        return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
    }
}
  • setSupportActionBar
    setSupportActionBar是用自定义的Toolbar替换原本的actionbar,在替换之后才能使用actionbar的方法,例如,上面的示例中,在调用 setupActionBarWithNavController()方法自动修改Toolbar的标题为导航图中的目的地标签android:label

  • AppBarConfiguration
    AppBarConfiguration有3种创建方式

@Suppress("FunctionName") /* Acts like a constructor */
inline fun AppBarConfiguration(
    navGraph: NavGraph,
    drawerLayout: Openable? = null,
    noinline fallbackOnNavigateUpListener: () -> Boolean = { false }
) = AppBarConfiguration.Builder(navGraph)
    .setOpenableLayout(drawerLayout)
    .setFallbackOnNavigateUpListener(fallbackOnNavigateUpListener)
    .build()

@Suppress("FunctionName") /* Acts like a constructor */
inline fun AppBarConfiguration(
    topLevelMenu: Menu,
    drawerLayout: Openable? = null,
    noinline fallbackOnNavigateUpListener: () -> Boolean = { false }
) = AppBarConfiguration.Builder(topLevelMenu)
    .setOpenableLayout(drawerLayout)
    .setFallbackOnNavigateUpListener(fallbackOnNavigateUpListener)
    .build()


@Suppress("FunctionName") /* Acts like a constructor */
inline fun AppBarConfiguration(
    topLevelDestinationIds: Set<Int>,
    drawerLayout: Openable? = null,
    noinline fallbackOnNavigateUpListener: () -> Boolean = { false }
) = AppBarConfiguration.Builder(topLevelDestinationIds)
    .setOpenableLayout(drawerLayout)
    .setFallbackOnNavigateUpListener(fallbackOnNavigateUpListener)
    .build()

第一种和第二种方式的效果相同,在切换fragment之后,图标Android Navigation 组件(进阶篇)_第6张图片 自动变成 Android Navigation 组件(进阶篇)_第7张图片

Android Navigation 组件(进阶篇)_第8张图片
第三种方式图标不会改变
Android Navigation 组件(进阶篇)_第9张图片

2.3 底部导航栏

Android Navigation 组件(进阶篇)_第10张图片
XML布局


<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"
        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>

BottomNavigationView的菜单每个选项的id必须要和导航图中的每个fragment的id相同,这样才能当用户点击菜单项时,应用会自动使用相同的 id 导航到相应目的地。和抽屉式导航栏一致,这里不再说明

将底部导航与导航组件、应用栏关联

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)
    }
}

2、使用 ViewModel 在目标间共享界面相关数据

在 Kotlin 中,您可以让共享数据的Fragment使用 navGraphViewModels() 属性委托从 Fragment 目标中检索 ViewModel,创建一个范围限定于导航图的 ViewModel,从而使您能够在图表的目标间共享与界面相关的数据。可以以这种方式创建的任何 ViewModel 对象都会一直存在,直至关联的 NavHost 及其 ViewModelStore 遭到清除,或导航图从返回栈中弹出为止。

ShareDataModel .kt

class ShareDataModel : ViewModel() {
    var share: String? = null
}

BlankFragment.kt

class BlankFragment : Fragment() {

    private val shareDataModel by navGraphViewModels<ShareDataModel>(R.id.nav_graph)

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        shareDataModel.share = "share data from navGraphViewModels"

        val binding = FragmentBlankBinding.inflate(inflater, container, false)
        binding.button.setOnClickListener {
            findNavController().navigate(BlankFragmentDirections.actionBlankFragmentToBlankFragment2())
        }
        return binding.root
    }
}

BlankFragment2.kt

class BlankFragment2 : Fragment() {

    private val shareDataModel by navGraphViewModels<ShareDataModel>(R.id.nav_graph)

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val binding = FragmentBlank2Binding.inflate(inflater, container, false)
        binding.textview.text = shareDataModel.share
        return binding.root
    }

}

Android Navigation 组件(进阶篇)_第11张图片

你可能感兴趣的:(Android)