Jetpack navigation组件使用

类似于ios开发中的storyboard,android studio 3.2中引入了可视化导航。之前Activity和Fragment的跳转代码样式化且易出错,通过使用Jetpack中navigation组件,开发人员可以更直观的维护界面跳转。

使用时需要引入依赖

    implementation "androidx.navigation:navigation-fragment-ktx:$rootProject.navigationVersion"
    implementation "androidx.navigation:navigation-ui-ktx:$rootProject.navigationVersion"

如果需要在xml中定义跳转出入参,需要引入safe args gradle插件,在项目的build.gradle文件中引入依赖

dependencies {
        classpath "android.arch.navigation:navigation-safe-args-gradle-plugin:$navigationVersion"
    //...
    }

然后在模块的build.gradle中应用插件

apply plugin: 'androidx.navigation.safeargs'

navigation组件主要包含3个部分

  • Navigation Graph: 新的xml资源文件,位于res/navigation文件夹中,描述应用中的界面及界面间的跳转关系。
  • NavHostFragment: 用于处理Navigation graph的Fragment,一般直接写在布局文件中。
  • NavController:与NavHostFragment关联的控制器,开发者使用该控制器控制界面的跳转。

Navigation Graph

直接在res/navigation文件夹中新建根标签为navigation的xml文件,在as中打开后就可以进行可视化编辑,对应的编辑会反映在xml文件中。
navigation文件根标签为navigation,其子标签均为destination,比如fragment或者activity,或者子navigation。
根标签声明中应该包含一个初始destination。

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

    <!-- ...tags for fragments and activities here -->

</navigation>

在design栏可以看到,每个子标签比如fragment有如下属性

  • id: 供跳转时使用,代码中可以直接调用**NavController.navigate(destId: Int)**跳转到该fragment
  • name: 对应代码中的fragment或者activity名称
  • label: 界面标题,设置ActionBar与NavController关联时ActionBar中的标题会自动显示该label
  • action: 定义该fragment中的跳转行为,可以在其中设置转场动画
  • argument: 定义跳转到该fragment时可接收的参数
  • deep-link:用于外部跳转到导航图的非初始页面,比如点击通知栏直接进入应用的详情页
    完整的示例如下
    <fragment
        android:id="@+id/flow_step_two_dest"
        android:name="com.example.android.codelabs.navigation.FlowStepFragment"
        android:label="Flow step two"
        tools:layout="@layout/flow_step_two_fragment">

        <argument
            android:name="flowStepNumber"
            app:argType="integer"
            android:defaultValue="2"/>

        <action
            android:id="@+id/next_action"
            app:popUpTo="@id/home_dest">
        </action>

       <deepLink app:uri="www.example.com/{myarg}" />
    </fragment>

定义了argument标签后,safe args插件会自动生成FlowStepFragmentArgs类,跳转时按照如下方式传入参数

    val args = FlowStepFragmentArgs(1)
    findNavController().navigate(R.id.flow_step_two_dest, args.toBundle())

针对xml中定义的action,safe args插件会自动生成对应的Directions类,该类有提供便捷的传参方式

    view.findViewById<Button>(R.id.navigate_action_button)?.setOnClickListener{
        val action = HomeFragmentDirections.nextAction(1)
        findNavController().navigate(action)
    }

接收方则按照如下方式接收参数

    val safeArgs = FlowStepFragmentArgs.fromBundle(arguments)
    val flowStepNumber = safeArgs.flowStepNumber

NavHostFragment

在应用中使用NavGraph时一般搭配NavHostFragment使用,在布局文件中包含该fragment。

<LinearLayout
    .../>
    <androidx.appcompat.widget.Toolbar
        .../>
    <fragment
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:id="@+id/my_nav_host_fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        app:navGraph="@navigation/mobile_navigation"
        app:defaultNavHost="true"
        />
    <com.google.android.material.bottomnavigation.BottomNavigationView
        .../>
</LinearLayout>

其中app:navGraph定义导航资源文件,视图在加载时会自动加载导航资源文件中的startDestination。app:defaultNavHost关联系统的back事件到NavHostFragment。

NavController

定义好导航图后,开发者需要在代码中通过NavController控制界面的跳转,NavHostFragment实现了NavHost,可以通过如下方式获取关联的NavController

  • Fragment.findNavController()
  • View.findNavController()
  • Activity.findNavController(viewId: Int)

需要注意的是,上述方法中的fragment和view必须是位于NavHostFragment下,不然没有对应的NavController,调用会报错。

在代码中获取到NavController后就可以调用**navigate()或者popBackStack()**方法进行相应的跳转。
跳转有几种方式,第一种就是直接跳转到graph中定义的destination,比如点击界面中按钮后需要跳转到另外一个fragment

	val options = navOptions {
	    anim {
	        enter = R.anim.slide_in_right
	        exit = R.anim.slide_out_left
	        popEnter = R.anim.slide_in_left
	        popExit = R.anim.slide_out_right
	    }
	}
	view.findViewById<Button>(R.id.navigate_destination_button)?.setOnClickListener {
	    findNavController().navigate(R.id.flow_step_one_dest, null, options)
	}

其中flow_step_one_dest代表Navigation Graph中定义的destination id。 options可选用于转场动画的设置。

第二种跳转方式为通过Navigation Graph – destination – action来定义跳转行为。比如同样的点击按钮跳转
Navigation Graph中定义的Action如下,通过该方式可以实现在xml中定义跳转的属性,如转场动画等。

<fragment android:id="@+id/home_dest"
        ...>
        
        <action android:id="@+id/next_action"
            app:destination="@+id/flow_step_one"
            app:enterAnim="@anim/slide_in_right"
            app:exitAnim="@anim/slide_out_left"
            app:popEnterAnim="@anim/slide_in_left"
            app:popExitAnim="@anim/slide_out_right" />

在代码中只需要构造根据该Action构造行为即可

 view.findViewById<Button>(R.id.navigate_action_button)?.setOnClickListener(
        Navigation.createNavigateOnClickListener(R.id.next_action, null)
)

搭配导航视图控件

AndroidX库中提供了现成的视图控件与NavController搭配使用,包括ActionBar, BottomNavigationBar, DrawerLayout, NavigationView。两者的关联主要依靠NavigationUI类和navigation-ui-ktx扩展函数,扩展函数其实也是调用NavigationUI的。

菜单与destination关联的判断依据是两者的id需要相同。

关联ActionBar

点击ActionBar上菜单然后跳转到对应的destination,首先需要destination和res/menu/中菜单的id相同,然后在代码中建立关联

        val drawerLayout : DrawerLayout? = findViewById(R.id.drawer_layout)
        appBarConfiguration = AppBarConfiguration(
                setOf(R.id.home_dest, R.id.deeplink_dest),
                drawerLayout)
        // 调用AppCompatActivity中定义的扩展函数关联
        setupActionBarWithNavController(navController, appBarConfiguration)

处理options menu事件时,优先交给NavController处理

	override fun onOptionsItemSelected(item: MenuItem): Boolean {
	    return item.onNavDestinationSelected(findNavController(R.id.my_nav_host_fragment))
	            || super.onOptionsItemSelected(item)
	}
关联BottomNavigationView

要求底部导航栏类型为com.google.android.material.bottomnavigation.BottomNavigationView,与options menu类似,res/menu中的menu id与Navigation Graph中定义的destination的id需要保持一致,然后在AppCompatActivity中关联NavController即可

    val bottomNav = findViewById<BottomNavigationView>(R.id.bottom_nav_view)
    bottomNav?.setupWithNavController(navController)
关联DrawerLayout

DrawerLayout与NavController的关联包含两方面

  • NavigationView中菜单与Navigation Graph的关联
  • ActionBar中navigation button的显示以及up事件的处理

关联菜单同样类似于options menu,只需要保持id一致然后调用如下关联代码即可

        val sideNavView = findViewById<NavigationView>(R.id.nav_view)
        sideNavView?.setupWithNavController(navController)

而ActionBar中navigation button的显示则由AppBarConfiguration类控制,该类用于配置ActionBar是否需要支持DrawerLayout以及指定哪些destination为top level。
当支持DrawerLayout时,ActionBar中的Navigation Button位于top level界面时显示drawer图标,其它界面显示返回图标。

如下代码表示ActionBar需要支持DrawerLayout,并且home_dest和deeplink_dest为top level界面。

        val drawerLayout : DrawerLayout? = findViewById(R.id.drawer_layout)
        appBarConfiguration = AppBarConfiguration(
                setOf(R.id.home_dest, R.id.deeplink_dest),
                drawerLayout)
        // 调用AppCompatActivity中定义的扩展函数关联
        setupActionBarWithNavController(navController, appBarConfiguration)

而如下代码则表示ActionBar不需要支持DrawerLayout,当界面位于Navigation Graph的start destination时ActionBar不需要显示Navigation Button,跳转后的界面则显示返回图标。

        val drawerLayout : DrawerLayout? = findViewById(R.id.drawer_layout)
        appBarConfiguration = AppBarConfiguration(navController.graph)
        // 调用AppCompatActivity中定义的扩展函数关联
        setupActionBarWithNavController(navController, appBarConfiguration)

针对ActionBar上的返回,通常需要手动控制返回事件

    override fun onSupportNavigateUp(): Boolean {
        // Allows NavigationUI to support proper up navigation or the drawer layout
        // drawer menu, depending on the situation
        return findNavController(R.id.my_nav_host_fragment).navigateUp(appBarConfiguration)
    }

本文基于官方codelab整理https://codelabs.developers.google.com/codelabs/android-navigation

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值