Navigation跳转
1.环境配置
在app model下配置:
//build.gradle
implementation deps.navigation.fragment_ktx
implementation deps.navigation.ui_ktx
相关配置:
//versions.gradle
versions.navigation = "2.1.0"
def navigation = [:]
navigation.fragment_ktx = "androidx.navigation:navigation-fragment-ktx:$versions.navigation"
navigation.ui_ktx = "androidx.navigation:navigation-ui-ktx:$versions.navigation"
navigation.safe_args = "androidx.navigation:navigation-safe-args-gradle-plugin:$versions.navigation"
deps.navigation = navigation
2.NavHostFragment配置
Google提供的Navigation的使用模式是:Activity中包含一个宿主NavHostFragment,该fragment用来解析跳转树NavGragh,并在初始化完成后被替换为跳转树中startDestination指向的节点。下面看一下NavHostFragment怎么样被引入到Activity的布局文件的:
//activity_navigation.xml
<?xml version="1.0" encoding="utf-8"?>
<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" >
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorPrimary"
android:theme="@style/ThemeOverlay.MaterialComponents.Dark.ActionBar"
app:layout_constraintTop_toTopOf="parent"/>
<fragment
android:id="@+id/my_nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintTop_toBottomOf="@id/toolbar"
app:layout_constraintBottom_toBottomOf="parent"
app:defaultNavHost="true"
app:navGraph="@navigation/mobile_navigation" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.drawerlayout.widget.DrawerLayout>
主要是用<fragment>标签引入,name指向该NavHostFragment,app:navGraph指向跳转树的资源文件定义:
<?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/navHomeFragment">
<fragment
android:id="@+id/navHomeFragment"
android:name="com.yy.onepiece.jetpackpractice.ui.NavHomeFragment"
android:label="NavHomeFragment"
tools:layout="@layout/fragment_nav_home">
<action
android:id="@+id/action_navHomeFragment_to_navStepFragment"
app:destination="@id/navStepFragmentOne" />
</fragment>
<fragment
android:id="@+id/navStepFragmentOne"
android:name="com.yy.onepiece.jetpackpractice.ui.NavStepFragment"
android:label="NavStepFragment"
tools:layout="@layout/fragment_nav_step_one">
<action
android:id="@+id/action_navStepFragmentOne_to_navStepFragmentTwo"
app:destination="@id/navStepFragmentTwo" />
<argument
android:name="flowStepNumber"
app:argType="integer"
android:defaultValue="1" />
</fragment>
<fragment
android:id="@+id/navStepFragmentTwo"
android:name="com.yy.onepiece.jetpackpractice.ui.NavStepFragment"
android:label="NavStepFragment"
tools:layout="@layout/fragment_nav_step_two">
<argument
android:name="flowStepNumber"
app:argType="integer"
android:defaultValue="2" />
<action
android:id="@+id/action_navStepFragmentTwo_to_navHomeFragment"
app:popUpTo="@id/navHomeFragment" />
</fragment>
</navigation>
其相应的Destinations视图为:
根标签会被解析成NavGragh对象,其属性app:startDestination指明了第一个显示的Destination,即NavHomeFragment。<fragment>标签会被解析成Destination对象,以其id为key保存在NavGragh对象的mNodes中,<action>标签定义了跳转,会被解析成NavAction对象,以其id为key保存在Destination对象的mActions中。
3 通过Destination跳转
val navOptions = navOptions {
anim {
enter = R.anim.slide_in_right
exit = R.anim.slide_out_left
popEnter = R.anim.slide_in_right
popExit = R.anim.slide_out_left
}
}
binding.navigateDestinationButton.setOnClickListener {
findNavController().navigate(R.id.navStepFragmentOne, null, navOptions)
}
navOptions定义了进入和退出动画,这里的R.id.navStepFragmentOne即为第一个NavStepFragment的id,这种情况下即是没有定义<action>也是可以跳转的。
4 通过Action跳转
binding.navigateActionButton.setOnClickListener {
findNavController().navigate(
R.id.action_navHomeFragment_to_navStepFragment,
null,
navOptions
)
}
这里的 R.id.action_navHomeFragment_to_navStepFragment是<action>的id,这种情况必须要定义<action>
5 带参数跳转
Navigation组件提供了名为safe args的Gradle Plugin来产生简单的对象和编译生成一些类来安全地访问destinations和actions中定义的arguments。要实现带参数跳转,第一步必须要引入该Plugin:
//project build.gradle
dependencies {
classpath deps.navigation.safe_args
}
//project versions.gradle
navigation.safe_args = "androidx.navigation:navigation-safe-args-gradle-plugin:$versions.navigation"
// app build.gradle
apply plugin: 'androidx.navigation.safeargs.kotlin'
第二步是在<fragment>标签下定义<argument>标签:
<argument
android:name="flowStepNumber"
app:argType="integer"
android:defaultValue="1" />
第三步通过action跳转并传参:
//NavHomeFragment.kt
binding.navigateDestinationButton.setOnClickListener {
val actionNavHomeFragmentToNavStepFragment =
NavHomeFragmentDirections.actionNavHomeFragmentToNavStepFragment(3)
findNavController().navigate(actionNavHomeFragmentToNavStepFragment, navOptions)
}
NavHomeFragmentDirections类就是编译生成的,实现了接口NavDirections,看一下这个接口的注释: An interface that describes a navigation operation: action’s id and arguments。
第四步是获取该参数:
//NavStepFragment.kt
private val layoutId: Int
get() {
flowStepNumber = arguments?.get("flowStepNumber") as? Int ?: 1
Log.d(TAG, "flowStepNumber: $flowStepNumber")
return if (flowStepNumber == 2) R.layout.fragment_nav_step_two
else R.layout.fragment_nav_step_one
}
使用NavigationUI进行导航
1. Options menu在NavigationUI中的使用
1.1 定义资源文件
首先需要定义<menu>资源文件:
//overflow_menu.xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@id/nav_setting"
android:icon="@drawable/ic_settings"
android:menuCategory="secondary"
android:title="@string/settings" />
</menu>
这里的id指向的是定义在<navigaiton>中的<fragment>的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/navHomeFragment">
//...
<fragment
android:id="@+id/nav_setting"
android:name="com.yy.onepiece.jetpackpractice.ui.NavSettingFragment"
android:label="NavDeepLinkFragment"
tools:layout="@layout/fragment_nav_setting"/>
</navigation>
1.2 创建menu
//NavigationActivity.kt
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.overflow_menu, menu)
return super.onCreateOptionsMenu(menu)
}
此时ToolBar上会展示menu的入口按钮,点击该按钮会显示该menu,但是点击menu的item还不能跳转到@id/nav_setting,需要让NavigationUI来处理onOptionsItemSelected()回调,如果menu的item的目的并不是跳转,那就交给父类处理。
1.3 点击item实现跳转
//NavigationActivity.kt
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return item.onNavDestinationSelected(findNavController(R.id.my_nav_host_fragment))
|| super.onOptionsItemSelected(item)
}
2. 使用NavigationUI来实现底部导航
2.1 在布局中加上BottomNavigationView
//activity_navigation.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" >
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorPrimary"
android:theme="@style/ThemeOverlay.MaterialComponents.Dark.ActionBar"
app:layout_constraintTop_toTopOf="parent"/>
<fragment
android:id="@+id/my_nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintTop_toBottomOf="@id/toolbar"
app:layout_constraintBottom_toBottomOf="parent"
app:defaultNavHost="true"
app:navGraph="@navigation/mobile_navigation" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottom_nav_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorAccent"
app:layout_constraintBottom_toBottomOf="parent"
app:menu="@menu/bottom_nav_menu" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.drawerlayout.widget.DrawerLayout>
menu属性指向了底部导航栏的样式:
//bottom_nav_menu.xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@id/navHomeFragment"
android:icon="@drawable/ic_home"
android:title="@string/home" />
<item
android:id="@id/deeplink_dest"
android:icon="@drawable/ic_android"
android:title="@string/deeplink" />
</menu>
id属性指向定义在<navigation>中的<fragment>的id值,此时BottomNavigationView已经可以正确展示了,但是还不支持点击跳转。
2.2 NavigationUI处理底部导航跳转
private fun setupBottomNavMenu(navController: NavController) {
val bottomNav = findViewById<BottomNavigationView>(R.id.bottom_nav_view)
bottomNav?.setupWithNavController(navController)
}
3. 使用NavigationUI来实现Drawer跳转
3.1 在资源文件中加上NavigationView
<?xml version="1.0" encoding="utf-8"?>
<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" >
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
//...
</androidx.constraintlayout.widget.ConstraintLayout>
<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"
app:menu="@menu/nav_drawer_menu" />
</androidx.drawerlayout.widget.DrawerLayout>
配置完手指右滑已经可以打开Drawer了,但是还不能实现item点击跳转。
3.2 NavigationUI处理Drawer item点击跳转
private fun setupNavigationMenu(navController: NavController) {
val navigationView = findViewById<NavigationView>(R.id.nav_view)
navigationView.setupWithNavController(navController)
}
如上图,此时ActionBar上还没有左上角的图标,虽然实现了跳转,但是Drawer点击跳转到某个Fragment后并没有影响到ActionBar,要建立这一联系,要通过AppBarConfiguration。
3.3 创建包含一些列顶层destination的AppBarConfiguration
//NavigationActivity.kt
private fun initView() {
val toolbar = findViewById<Toolbar>(R.id.toolbar)
setSupportActionBar(toolbar)
val host: NavHostFragment = supportFragmentManager
.findFragmentById(R.id.my_nav_host_fragment) as NavHostFragment? ?: return
val navController = host.navController
val drawerLayout: DrawerLayout? = findViewById(R.id.drawer_layout)
appBarConfiguration = AppBarConfiguration(
setOf(R.id.navHomeFragment, R.id.deeplink_dest),
drawerLayout)
setupActionBar(navController, appBarConfiguration)
}
private fun setupActionBar(
navController: NavController,
appBarConfig: AppBarConfiguration
) {
setupActionBarWithNavController(navController, appBarConfig)
}
如上图所示,经过这一操作,在处于顶层destination时会在ActionBar左上角显示一个图标,在处于非顶层destination时会显示箭头图标,如下图所示:
此时左上角的箭头图标还不能实现点击返回。
3.4 处理点击返回事件
override fun onSupportNavigateUp(): Boolean {
return findNavController(R.id.my_nav_host_fragment).navigateUp()
}
处理Deeplink
1. 定义
在需要处理deepllink的destination中定义<deeplink>,如果需要接收数据,还需定义<argument>,这两处的参数名必须一样。
<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/navHomeFragment">
//...
<fragment
android:id="@+id/deeplink_dest"
android:name="com.yy.onepiece.jetpackpractice.ui.NavDeepLinkFragment"
android:label="NavDeepLinkFragment"
tools:layout="@layout/fragment_nav_deep_link">
<argument android:name="nav_arg"
android:defaultValue="hello navigation"/>
<deepLink app:uri="hello.navigation.com/{nav_arg}"/>
</fragment>
</navigation>
这里uri需要注意一下几点:
- uri可以省略scheme,即http://www.baidu.com和www.baidu.com是匹配的。
- 可以使用占位符:{placeholder}。
- 可以使用通配符.*,它可以匹配零个或多个字符。
2. 在manifest中配置<nav-graph>
<application>
//...
<activity android:name=".ui.NavigationActivity"
android:label="@string/navigation_activity_title"
android:theme="@style/AppTheme" >
<nav-graph android:value="@navigation/mobile_navigation" />
</activity>
</application>
3 接收参数
//NavDeepLinkFragment.kt
override fun initView() {
val navArg = arguments?.getString("nav_arg")
Log.d(TAG, "navArg: $navArg")
binding.deepLinkText.text = navArg
}
使用adb命令来启动app:
adb -s YourDeviceId shell am start -a android.intent.action.VIEW -d "http://hello.navigation.com/testUrl"
结果如下图所示,点击我们的App图标后会跳转到NavDeepLinkFragment,并且当前堆栈中没有MainActivity实例,只有一个NavigationActivity实例。