当前位置:网站首页>Navigation — 这么好用的导航框架你确定不来看看?

Navigation — 这么好用的导航框架你确定不来看看?

2022-07-07 12:40:00 51CTO

前言

什么是Navigation?官方文档的话有点不容易让人理解。所以,这里用我自己的话来总结一下,我们在处理Fragment是需要通过写Fragment的事务去操作Fragment的,而Navigation的出现是为了解决我们之前开发的一些痛点。Navigation主要用于实现Fragment代替Activity的页面导航功能,让Fragment能够轻松的实现跳转与传递参数,我们可以通过使用Navigation,让Fragment代替android项目中绝大多数的Activity。但需要注意的是在使用Navigation切换页面生命周期的变化情况,避免开发过程中踩坑。

官方文档:<​ ​https://developer.android.google.cn/jetpack/androidx/releases/navigation​​>

navigation项目地址:<​ ​https://github.com/googlecodelabs/android-navigation​​>

本文Demo地址:<​ ​https://github.com/taxze6/Jetpack_learn/tree/main/Jetpack_basic_learn/navigation​​>

使用Navigation具有什么优势?

  • 处理Fragment事务
  • 默认情况下,能正确处理往返操作
  • 为动画和转换提供标准化资源
  • 实现和处理深层链接
  • 包括导航界面模式,例如抽屉式导航栏和底部导航,我们只需要完成少量的代码编写
  • Safe Args - 可在目标之间导航和传递数据时提供类型安全的Gradle插件
  • ViewModel支持 - 您可以将ViewModel的范围限定为导航图,以在图标的目标之间共享与界面相关的数据

如何使用Navigation呢?

Navigation目前仅AndroidStudio 3.2以上版本支持,如果您的版本不足3.2,请​ ​点此下载​​最新版AndroidStudio(2202年了应该没有人还在用3.2以下的版本吧!)

在开始学习​​Navigation​​组件之前,我们需要先对​​Navigation​​主要组成部分有个简单的了解,Navigation由三部分组成:

  • Navigation graph:一个包含所有导航相关信息的​​XML​​ 资源
  • NavHostFragment:一种特殊的​​Fragment​​,用于承载导航内容的容器
  • NavController:管理应用导航的对象,实现​​Fragment​​之间的跳转等操作

下面我们正式开始学习Navigation啦

第一步:添加依赖
      
      
//project的Navigation依赖设置
dependencies {
//文章发布时的最新稳定版本:
def nav_version = "2.4.2"

// 使用java作为开发语言添加下面两行:
implementation "androidx.navigation:navigation-fragment:$nav_version"
implementation "androidx.navigation:navigation-ui:$nav_version"

// Kotlin:
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
}

//Compose版本:
implementation "androidx.navigation:navigation-compose:$nav_version"
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
第二步:创建导航图

右键点击​​res​​目录,然后依次选择​​New​​→ ​​Android Resource Directory​​。此时系统会显示 ​​New Resource Directory​​对话框。​​Directory name​​输入你的文件夹名(一般为​​navigation​​),​​Resource type​​选择​​navigation​

右键​​navigation​​文件夹,然后​​new​​ →​​Navigation Resource File​​在​​File name​​中输入名称(常用nav_graph_main或nav_graph

Navigation — 这么好用的导航框架你确定不来看看?_android

第三步:创建Fragment

为了让跳转更加的丰富,我们这里创建三个​​Fragment​

我们可以自己手动创建:

FirstFragment:

      
      
<?xml version="1.0" encoding="utf-8"?>
< FrameLayout xmlns:android= "http://schemas.android.com/apk/res/android"
xmlns:tools= "http://schemas.android.com/tools"
android:layout_width= "match_parent"
android:layout_height= "match_parent"
tools:context= ".FirstFragment" >

< TextView
android:layout_width= "match_parent"
android:layout_height= "match_parent"
android:gravity= "center"
android:text= "hello world" />
</ FrameLayout >
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
      
      
class FirstFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup ?,
savedInstanceState: Bundle ?
): View ? {
return inflater. inflate( R. layout. fragment_first, container, false)
}
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.

另外创建两个和​​FirstFragement​​一样的:​​SecondFragment​​,​​ThirdFragment​

我们也可以通过Navigation graph​创建

我们在新建好的​​nav_graph_main.xml​​下,在右上角切换到​​Design​​模式,然后在​​Navigation Editor​​中,点击​​Create new destination​​,选择所需要的​​Fragment​​后,点击​​Finish​​,你就会发现​​Fragment​​已经出现在我们可以拖动的面板中了。

Navigation — 这么好用的导航框架你确定不来看看?_android_02

第四步:将Fragment拖入面板并进行跳转配置

只需要在​​Navigation Editor​​中双击想要的​​Fragment​​就会被加入到面板中啦。

点击其中一个​​Fragment​​,你会发现,在他的右边会有一个小圆点,拖曳小圆点指向想要跳转的那个​​Fragment​​,我们这里设置​​FirstFragment​​ → ​​SecondFragment​​ → ​​ThirdFragment​​ → ​​FirstFragment​

Navigation — 这么好用的导航框架你确定不来看看?_框架学习_03

我们将​​nav_graph_main.xml​​切换到​​Code​​下,我们来解读一下​​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/nav_graph_main"
app:startDestination= "@id/firstFragment" >
< fragment
android:id= "@+id/firstFragment"
android:name= "com.taxze.jetpack.navigation.FirstFragment"
android:label= "fragment_first"
tools:layout= "@layout/fragment_first" >
< action
android:id= "@+id/action_firstFragment_to_secondFragment2"
app:destination= "@id/secondFragment" />
</ fragment >
< fragment
android:id= "@+id/secondFragment"
android:name= "com.taxze.jetpack.navigation.SecondFragment"
android:label= "fragment_second"
tools:layout= "@layout/fragment_second" >
< action
android:id= "@+id/action_secondFragment_to_thirdFragment2"
app:destination= "@id/thirdFragment" />
</ fragment >
< fragment
android:id= "@+id/thirdFragment"
android:name= "com.taxze.jetpack.navigation.ThirdFragment"
android:label= "fragment_third"
tools:layout= "@layout/fragment_third" >
< action
android:id= "@+id/action_thirdFragment_to_firstFragment"
app:destination= "@id/firstFragment" />
</ fragment >
</ navigation >
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • ​navigation​​是根标签,通过​​startDestination​​配置默认启动的第一个页面,这里配置的是​​firstFragment​​,我们可以在代码中手动改​​mainFragment​​(启动时的第一个Fragment),也可以在可视化面板中点击​​Fragment​​,再点击​​Assign Start Destination​​,同样可以修改​​mainFragment​

Navigation — 这么好用的导航框架你确定不来看看?_android_04

  • ​fragment​​标签就代表这是一个Fragment
  • ​action​​标签定义了页面跳转的行为,就是上图中的每条线,​​destination​​定义跳转的目标页,还可以加入跳转时的动画

注意:在fragment标签下的android:name属性,其中的包名的是否正确声明

第五步:处理MainActivity

编辑​​MainActivity​​的布局文件,在布局文件中添加​​NavHostFragment​​。我们需要告诉​​Navigation​​和​​Activity​​,我们的​​Fragment​​展示在哪里,所以​​NavHostFragment​​其实就是导航界面的容器

      
      
<?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= ".MainActivity" >

< fragment
android:id= "@+id/nav_host_fragment"
android:layout_width= "0dp"
android:layout_height= "0dp"
android:name= "androidx.navigation.fragment.NavHostFragment"
app:layout_constraintBottom_toBottomOf= "parent"
app:layout_constraintLeft_toLeftOf= "parent"
app:layout_constraintRight_toRightOf= "parent"
app:layout_constraintTop_toTopOf= "parent"
app:defaultNavHost= "true"
app:navGraph= "@navigation/nav_graph_main" />
</ androidx.constraintlayout.widget.ConstraintLayout >
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • ​fragment​​标签下的​​android:name​​是用于指定​​NavHostFragment​
  • ​app:navGraph​​是用于指定导航视图的
  • ​app:defaultNavHost=true​​是在每一次​​Fragment​​切换时,将点击记录在堆栈中保存起来,在需要退出时,按下返回键后,会从堆栈拿到上一次的​​Fragment​​进行显示。但是在某些情况下,这样的操作不是很友好,不过好在我们只需要将​​app:defaultNavHost=true​​改为​​app:defaultNavHost=false​​或者删除这行即可。在其为​​false​​的情况下,无论怎么切换​​Fragment​​,再点击返回键就都直接退出​​app​​。当然我们也可以对其堆栈进行监听,从而来实现,点击一次返回键回到主页,再点击一次返回键退出​​app​​。

修改​​MainActivity​

我们重写了​​onSupportNavigateUp​​,表示我们将​​Activity​​的​​back​​点击事件委托出去

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

override fun onSupportNavigateUp(): Boolean {
return findNavController( R. id. nav_host_fragment). navigateUp()
}
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
第六步:处理Fragment的对应跳转事件

①​​Fragment​​布局:给一个按钮用于跳转,一个​​TextView​​用于标识,三个​​Fragment​​布局相同

      
      
<?xml version="1.0" encoding="utf-8"?>
< FrameLayout xmlns:android= "http://schemas.android.com/apk/res/android"
xmlns:tools= "http://schemas.android.com/tools"
android:layout_width= "match_parent"
android:layout_height= "match_parent"
tools:context= ".FirstFragment" >

< Button
android:id= "@+id/firstButton"
android:layout_width= "match_parent"
android:layout_height= "wrap_content"
android:layout_marginTop= "10dp"
android:text= "点击跳转到第二个fragment" />

< TextView
android:layout_width= "match_parent"
android:layout_height= "match_parent"
android:gravity= "center"
android:text= "@string/hello_first_fragment" />

</ FrameLayout >

//secondFragment中加入:
< Button
android:id= "@+id/firstButton"
android:layout_width= "match_parent"
android:layout_height= "wrap_content"
android:layout_marginTop= "10dp"
android:text= "点击跳转到第三个fragment" />

//thirdFragment中加入:
< Button
android:id= "@+id/firstButton"
android:layout_width= "match_parent"
android:layout_height= "wrap_content"
android:layout_marginTop= "10dp"
android:text= "返回第一个fragment" />
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.

②配置Fragment对应的跳转事件

      
      
class FirstFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup ?,
savedInstanceState: Bundle ?
): View ? {
return inflater. inflate( R. layout. fragment_first, container, false)
}

override fun onViewCreated( view: View, savedInstanceState: Bundle ?) {
super. onViewCreated( view, savedInstanceState)
view. findViewById < Button >( R. id. firstButton). apply {
setOnClickListener {
it. findNavController(). navigate( R. id. action_firstFragment_to_secondFragment2)
}
}
}
}

//secondFragment中加入:
override fun onViewCreated( view: View, savedInstanceState: Bundle ?) {
super. onViewCreated( view, savedInstanceState)
view. findViewById < Button >( R. id. firstButton). apply {
setOnClickListener {
it. findNavController(). navigate( R. id. action_secondFragment_to_thirdFragment2)
}
}
}

//thirdFragment中加入:
override fun onViewCreated( view: View, savedInstanceState: Bundle ?) {
super. onViewCreated( view, savedInstanceState)
view. findViewById < Button >( R. id. firstButton). apply {
setOnClickListener {
it. findNavController(). navigate( R. id. action_thirdFragment_to_firstFragment)
}
}
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.

其中的​​R.id.action_firstFragment_to_secondFragment2​​这样的标识,是在​​nav_graph_main.xml​​的​​action​​标签下配置的。

      
      
< action
android:id= "@+id/action_firstFragment_to_secondFragment2"
app:destination= "@id/secondFragment" />
  • 1.
  • 2.
  • 3.

③最后的效果图:

Navigation — 这么好用的导航框架你确定不来看看?_android_05

跳转动画&自定义动画

我们会发现,刚刚的例子中,我们在跳转界面时,没有左滑右滑进入界面的动画,显得很生硬,所以我们要给跳转过程中添加上动画。

添加默认动画:

在​​nav_graph_main.xml​​文件中的​​Design​​模式下,点击连接两个​​Fragment​​的线。然后你会发现在右侧会出现一个​​Animations​​的面板,然后我们点击​​enterAnim​​这些选项最右侧的椭圆点,然后就会弹出​​Pick a Resoure​​的面板,我们可以在这里选择需要的动画,这里我们就设置​​nav_default_enter_anim​​。同理​​exitAnim​​我们也设置一个动画,​​nav_default_exit_anim​​。然后运行代码你就发现一个渐变的跳转动画。然后配置动画后会发现​​action​​多了动画相关的属性

      
      
< fragment
android:id= "@+id/firstFragment"
android:name= "com.taxze.jetpack.navigation.FirstFragment"
android:label= "fragment_first"
tools:layout= "@layout/fragment_first" >
< action
android:id= "@+id/action_firstFragment_to_secondFragment2"
app:destination= "@id/secondFragment"
app:enterAnim= "@anim/nav_default_enter_anim"
app:exitAnim= "@anim/nav_default_exit_anim"
/>
</ fragment >
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.

Navigation — 这么好用的导航框架你确定不来看看?_架构_06

  • ​enterAnim​​: 跳转时的目标页面动画
  • ​exitAnim​​: 跳转时的原页面动画
  • ​popEnterAnim​​: 回退时的目标页面动画
  • ​popExitAnim​​:回退时的原页面动画

自定义动画

在真正的业务需求中是会有很多不同的跳转动画的,官方提供的默认动画是不够的,所以我们要学会自定义动画。

⑴创建Animation资源文件

右键点击​​res​​目录,然后依次选择​​New​​→ ​​Android Resource File​​。此时系统会显示 ​​New Resource File​​对话框。​​File name​​输入你的文件夹名(这里设置为​​slide_from_left​​),​​Resource type​​选择​​Animation​​,然后我们就可以发下在​​res​​目录下多了一个​​anim​​目录,里面存放着我们自定义的动画。

⑵编写我们的动画代码

这里举几个常用的例子:

      
      
//左滑效果
<?xml version="1.0" encoding="utf-8"?>
< set xmlns:android= "http://schemas.android.com/apk/res/android" >
< translate
android:duration= "300"
android:fromXDelta= "-100%"
android:toXDelta= "0%" >
</ translate >
</ set >
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
      
      
//右滑效果
<?xml version="1.0" encoding="utf-8"?>
< set xmlns:android= "http://schemas.android.com/apk/res/android" >
< translate
android:duration= "300"
android:fromXDelta= "0%"
android:toXDelta= "100%" >
</ translate >
</ set >
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
      
      
//旋转效果
<?xml version="1.0" encoding="utf-8"?>
< set xmlns:android= "http://schemas.android.com/apk/res/android" >
< scale
android:duration= "1000"
android:fromXScale= "0.0"
android:fromYScale= "0.0"
android:pivotX= "50%"
android:pivotY= "50%"
android:toXScale= "1.0"
android:toYScale= "1.0" />

< rotate
android:duration= "1000"
android:fromDegrees= "0"
android:pivotX= "50%"
android:pivotY= "50%"
android:toDegrees= "360" />
</ set >
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.

常用的属性:

属性

介绍

alpha

透明度设置效果

scale

大小缩放效果

rotate

旋转效果

translate

位移效果

常用设置:

属性

介绍

android:duration

动画时长

fromXX

开始状态

toXX

结束状态

⑶使用自定义动画文件

使用自定义动画也是和使用默认动画是一样的。

旋转效果:

Navigation — 这么好用的导航框架你确定不来看看?_架构_07

如何传递数据?

Navigation 支持您通过定义目的地参数将数据附加到导航操作。一般情况下,建议传递少量数据。例如传递userId,而不是具体用户的信息。如果需要传递大量的数据,还是推荐使用ViewModel。

在​​nav_graph_main.xml​​文件中的​​Design​​模式下。点击选中其中的​​Fragment​​。在右侧的​​Attributes​​ 面板中,点击​​Arguments​​选项右侧的加号。添加需要的参数。添加参数后,在箭头 ​​Action​​ 上点击,会在右边的 ​​Argument Default Values​​中显示这个​​userId​​变量,在xml中也可以看到

      
      
//伪代码,请勿直接cv
< fragment
...
>
...
< argument
android:name= "userId"
android:defaultValue= "1"
app:argType= "integer" />
</ fragment >
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.

代码处理:

      
      
//默认将 箭头 Action 中设置的参数传递过去
it. findNavController(). navigate( R. id. action_firstFragment_to_secondFragment2)
  • 1.
  • 2.

动态传递数据

①我们可以使用Bundle在目的地之间传递参数

      
      
//伪代码,请勿直接cv
view. findViewById < Button >( R. id. firstButton). setOnClickListener {
val bundle = Bundle()
bundle. putString( "userId", "1")
val navController = it. findNavController()
navController. navigate( R. id. action_firstFragment_to_secondFragment2, bundle)
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

②然后我们就可以接受参数啦

在对应的Fragment:

      
      
val tv = view. findViewById < TextView >( R. id. textView)
tv. text = arguments ?. getString( "userId")
  • 1.
  • 2.

在Activity使用setGraph切换不同的Navigation

通常情况下,我们不止一个​​navigation​​的文件,我们需要根据业务情况去判断使用哪个,这里就可以用到我们的​​setGraph​​去切换不同的​​Navigation​​了。

①把​​activity_main​​中​​fragment​​标签下的​​app:navGraph​​这个属性去除

      
      
< fragment
android:id= "@+id/nav_host_fragment"
android:name= "androidx.navigation.fragment.NavHostFragment"
android:layout_width= "0dp"
android:layout_height= "0dp"
app:layout_constraintBottom_toBottomOf= "parent"
app:layout_constraintLeft_toLeftOf= "parent"
app:layout_constraintRight_toRightOf= "parent"
app:layout_constraintTop_toTopOf= "parent"
app:defaultNavHost= "true" />
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.

②在代码中通过​​setGraph​​设置​​app:navGraph​

      
      
class MainActivity : AppCompatActivity() {
override fun onCreate( savedInstanceState: Bundle ?) {
super. onCreate( savedInstanceState)
setContentView( R. layout. activity_main)
initNav( 1)
}
private fun initNav( id: Int) {
var controller = Navigation. findNavController( this @MainActivity, R. id. nav_host_fragment)
if ( id == 1) {
//设置对应的app:navGraph
controller. setGraph( R. navigation. first)
}
if ( id == 2) {
controller. setGraph( R. navigation. second)
}
this @MainActivity. finish()
}
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.

到此,我们已经讲完了​​Navigation​​大部分的使用情况,下面来讲解几个重要的点,并通过一个案例来学习总结这篇文章吧。

NavController

我们在前面已经讲了​​Navigation​​的使用,在处理​​Fragment​​的对应跳转事件时,我们用到了​​findNavController​​这个方法,其实呢,这个就是​​Navigation​​的三部分中的​​NavController​​中的一个​​api​​。那么什么是​​NavController​​呢?它是负责操作​​Navigation​​框架下的​​Fragment​​的跳转与退出、动画、监听当前​​Fragment​​信息,当然这些是基本操作。因为除了在​​Fragment​​中调用,在实际情况中它也可以在Activity中调用。如果能灵活的使用它,它可以帮你实现任何形式的页面跳转,也可以使用​​TabLayout​​配合​​Navigation​​在主页进行分页设计。

如何获取​​NavController​​实例呢?

在前面的基础使用中我们也用到了。

      
      
//伪代码,请勿直接cv
//activity:
//Activity.findNavController(viewId: Int)
findNavController( R. id. nav_host_fragment). navigateUp()

//Fragment:
//Fragment.findNavController()
//View.findNavController()
findNavController(). navigate( R. id. action_thirdFragment_to_firstFragment)
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.

Navigation常用操作:

①popBackStack弹出Fragment

现在我们从​​oneFragment​​跳转到​​secondFragment​​在到​​thirdFragment​​,然后我们想从​​thirdFragment​​回到​​secondFragment​​那儿就可以使用​​popBackStack​

      
      
override fun onCreate( savedInstanceState: Bundle ?) {
super. onCreate( savedInstanceState)
setContentView( R. layout. activity_main)
.....
btn. setOnClickListener{
//弹出Fragment
controller. popBackStack()
}
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
②弹出到指定的Fragment

也是使用​​popBackStack​​,这个方法可以实现清空中间导航栈堆的需求

      
      
//xxxFragment弹出到指定的Fragment。
//第二个参数的布尔值如果为true则表示我们参数一的Fragment一起弹出,意思就是如果是false就popBackStack到
//xxxFragment,如果是true,就在xxxFragment在popBackStack()一次
controller. popBackStack( xxxFragment, true)
  • 1.
  • 2.
  • 3.
  • 4.
③navigateUp() 向上导航
      
      
findNavController( R. id. nav_host_fragment). navigateUp()
  • 1.

​navigateUp​​也是执行返回上一级​​Fragment​​的功能。和​​popBackStack​​功能一样。那么既然它存在肯定是有它特殊的地方的。​​navigateUp​​向上返回的功能其实也是调用​​popBackStack​​的。 但是,​​navigateUp​​的源码里多了一层判断,判断这个​​Navigation​​是否是最后一个​​Fragment​​。使用​​popBackStack()​​如果当前的返回栈是空的就会报错,因为栈是空的了,​​navigateUp()​​则不会,还是停留在当前界面。

④添加导航监听
      
      
val listener: NavController. OnDestinationChangedListener =
object : OnDestinationChangedListener() {
fun onDestinationChanged(
controller: NavController,
destination: NavDestination,
@Nullable arguments: Bundle ?
) {
Log. d( TAG, "onDestinationChanged: id = ${destination.getId()}")
}
}
//添加监听
controller. addOnDestinationChangedListener( listener)
//移除监听
controller. removeOnDestinationChangedListener( listener)
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
⑤获取当前导航目的地

使用​​getCurrentDestination​​可以获取当前导航的目的地

      
      
//获取
val destination = controller. getCurrentDestination()
Log. d( TAG, "onCreate: NavigatorName = ${destination.getNavigatorName()}")
Log. d( TAG, "onCreate: id = ${destination.getId()}")
Log. d( TAG, "onCreate: Parent = ${destination.getParent()}")
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
⑥判断当前页面显示的Fragment是不是目标Fragment
      
      
//可直接cv
fun < F : Fragment > isActiveFragment( fragmentClass: Class < F >): Boolean {
val navHostFragment = this. supportFragmentManager. fragments. first() as NavHostFragment
navHostFragment. childFragmentManager. fragments. forEach {
if ( fragmentClass. isAssignableFrom( it. javaClass)) {
return true
}
}
return false
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.

使用 Safe Args 确保类型安全

通过上面的NavController我们就可以实现fragment之间的跳转,但是Google建议使用 Safe Args Gradle 插件实现。这个插件可以生成简单的对象和构建器类,这些类就可以在目的地之间进行安全的导航和参数的传递啦。

那么,该如何使用 ​​Safe Args​​呢。

①在项目最外面的​​build.gradle​​中加入

      
      
//将其放在plugins{}之前
buildscript {
repositories {
google()
}
dependencies {
def nav_version = "2.4.2"
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
}
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.

②在​​app​​、​​module​​下的​​build.gradle​​加入

如需生成适用于 ​​Java​​ 模块或​​Java​​ 和 ​​Kotlin​​混合模块的​​Java​​语言代码

      
      
apply plugin: "androidx.navigation.safeargs"
  • 1.

如需生成适用于 ​​Kotlin​​独有的模块的​​Kotlin​​代码,请添加以下行:

      
      
apply plugin: "androidx.navigation.safeargs.kotlin"
  • 1.

要使用Safe Args,必须在gradle.properties这个文件中加入

android.useAndroidX=true android.enableJetifier=true

为了避免生成的类名和方法名过于复杂,需要对导航图进行调整,修改了action的id,因为自动生成的id太长了,会导致插件生成的类名和方法名也很长。

      
      
//伪代码,请勿直接cv
< fragment
android:id= "@+id/blankFragment"
android:name= "com.taxze.jetpack.navigation.BlankFragment"
android:label= "fragment_blank"
tools:layout= "@layout/fragment_blank" >
< action
android:id= "@+id/toSecond"
修改此处id
app:destination= "@id/blankFragment2" />
</ fragment >
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.

然后就会为我们生成​​BlankFragment​​和​​BlankFragment2​​的类啦,然后就可以和上面一样使用,传递参数啦。

      
      
var action = BlankFragment. actionJump( "111")
  • 1.

也可以使用set方法对对应的参数进行赋值

      
      
action. setParam( "222")
  • 1.

通过Navigation模仿WeChat底部跳转

通过Navigation实现开发中超级常用的底部跳转功能

话不多说,先上效果图:

Navigation — 这么好用的导航框架你确定不来看看?_架构_08

①右键点击​​res​​目录,然后依次选择​​New​​→ ​​Android Resource File​​。此时系统会显示 ​​New Resource File​​对话框。​​File name​​输入你的文件夹名(这里设置为​​menu​​),​​Resource type​​选择​​Menu​​,然后我们就可以发下在​​res​​目录下多了一个​​menu​​目录,里面存放着我们底部跳转的​​item​​。

Navigation — 这么好用的导航框架你确定不来看看?_android_09

②进入​​menu.xml​​的​​Design​​下

填入四个​​Item​​,并分别设置​​id​​,​​title​​,​​icon​

Navigation — 这么好用的导航框架你确定不来看看?_框架学习_10

③创建四个​​Fragment​​,并在​​Navigation​​中建立链接

Fragment的布局十分简单,这里就放一个的代码。

      
      
<?xml version="1.0" encoding="utf-8"?>
< FrameLayout xmlns:android= "http://schemas.android.com/apk/res/android"
xmlns:tools= "http://schemas.android.com/tools"
android:layout_width= "match_parent"
android:layout_height= "match_parent"
tools:context= ".HomeFragment" >

< TextView
android:layout_width= "match_parent"
android:layout_height= "match_parent"
android:gravity= "center"
android:text= "Home" />
</ FrameLayout >
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
      
      
class HomeFragment : Fragment() {

override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup ?,
savedInstanceState: Bundle ?
): View ? {
return inflater. inflate( R. layout. fragment_home, container, false)
}

}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.

在​​nav_graph_main.xml​​建立连接,注意这里每个​​Fragment​​的​​id​​要和​​menu.xml​​中的​​id​​一一对应

Navigation — 这么好用的导航框架你确定不来看看?_框架学习_11

④在​​activity_main​​中使用​​BottomNavigationView​​建立底部跳转

这里在​​drawable​​下创建一个​​selector_menu_text_color.xml​​用于区分当前的​​Item​

      
      
<?xml version="1.0" encoding="utf-8"?>
< selector xmlns:android= "http://schemas.android.com/apk/res/android" >
< item android:color= "#818181" android:state_checked= "false" />
< item android:color= "#45C01A" android:state_checked= "true" />
</ selector >
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
      
      
<?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= ".MainActivity" >

< fragment
android:id= "@+id/nav_host_fragment"
android:layout_width= "0dp"
android:layout_height= "0dp"
android:name= "androidx.navigation.fragment.NavHostFragment"
app:layout_constraintBottom_toBottomOf= "parent"
app:layout_constraintLeft_toLeftOf= "parent"
app:layout_constraintRight_toRightOf= "parent"
app:layout_constraintTop_toTopOf= "parent"
app:defaultNavHost= "true"
app:navGraph= "@navigation/nav_graph_main" />

< com.google.android.material.bottomnavigation.BottomNavigationView
android:id= "@+id/nav_view"
android:layout_width= "match_parent"
android:layout_height= "0dp"
app:layout_constraintBottom_toBottomOf= "parent"
app:layout_constraintEnd_toEndOf= "parent"
app:layout_constraintStart_toStartOf= "parent"
app:itemIconTint= "@drawable/selector_menu_text_color"
app:labelVisibilityMode= "labeled"
app:itemTextColor= "@drawable/selector_menu_text_color"
app:menu= "@menu/menu" />
</ androidx.constraintlayout.widget.ConstraintLayout >
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.

⑤在MainActivty中设置切换逻辑

      
      
class MainActivity : AppCompatActivity() {
override fun onCreate( savedInstanceState: Bundle ?) {
super. onCreate( savedInstanceState)
//去除标题栏
supportRequestWindowFeature( Window. FEATURE_NO_TITLE)
setContentView( R. layout. activity_main)

val navController: NavController = Navigation. findNavController( this, R. id. nav_host_fragment)
val navigationView = findViewById < BottomNavigationView >( R. id. nav_view)
NavigationUI. setupWithNavController( navigationView, navController)
}
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.

这样我们就可以实现效果图中的效果啦,具体代码可以查看​​Git​​仓库

尾述

这篇文章已经很详细的讲了Jetpack Navigation的大部分用法,不过在看完文章后,你仍需多多实践,相信你很快就可以掌握Navigation啦 因为我本人能力也有限,文章有不对的地方欢迎指出,有问题欢迎在评论区留言讨论~

关于我

Hello,我是Taxze,如果您觉得文章对您有价值,希望您能给我的文章点个️,也欢迎关注我的​ ​博客​​。

如果您觉得文章还差了那么点东西,也请通过关注督促我写出更好的文章——万一哪天我进步了呢?

基础系列:

 ​2022 · 让我带你Jetpack架构组件从入门到精通 — Lifecycle​

 ​学会使用LiveData和ViewModel,我相信会让你在写业务时变得轻松​

 ​当你真的学会DataBinding后,你会发现“这玩意真香”!​

Navigation — 这么好用的跳转管理框架你确定不来看看?(本文)

以下部分还在码字,赶紧点个收藏吧

2022 · 让我带你Jetpack架构组件从入门到精通 — Room

2022 · 让我带你Jetpack架构组件从入门到精通 — Paging3

2022 · 让我带你Jetpack架构组件从入门到精通 — WorkManager

2022 · 让我带你Jetpack架构组件从入门到精通 — ViewPager2

2022 · 让我带你Jetpack架构组件从入门到精通 — 登录注册页面实战(MVVM)

进阶系列:

协程 + Retrofit网络请求状态封装

Room 缓存封装

.....

原网站

版权声明
本文为[51CTO]所创,转载请带上原文链接,感谢
https://blog.51cto.com/u_15688050/5450644