前言
什么是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)
第三步:创建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)
}
}
另外创建两个和FirstFragement
一样的:SecondFragment
,ThirdFragment
我们也可以通过Navigation graph
创建
我们在新建好的nav_graph_main.xml
下,在右上角切换到Design
模式,然后在Navigation Editor
中,点击Create new destination
,选择所需要的Fragment
后,点击Finish
,你就会发现Fragment
已经出现在我们可以拖动的面板中了。
第四步:将Fragment拖入面板并进行跳转配置
只需要在Navigation Editor
中双击想要的Fragment
就会被加入到面板中啦。
点击其中一个Fragment
,你会发现,在他的右边会有一个小圆点,拖曳小圆点指向想要跳转的那个Fragment
,我们这里设置FirstFragment
→ SecondFragment
→ ThirdFragment
→ FirstFragment
我们将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
-
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"
/>
③最后的效果图:
跳转动画&自定义动画
我们会发现,刚刚的例子中,我们在跳转界面时,没有左滑右滑进入界面的动画,显得很生硬,所以我们要给跳转过程中添加上动画。
①添加默认动画:
在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.
-
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 支持您通过定义目的地参数将数据附加到导航操作。一般情况下,建议传递少量数据。例如传递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)
动态传递数据
①我们可以使用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)
}
②然后我们就可以接受参数啦
在对应的Fragment:
val
tv
=
view.
findViewById
<
TextView
>(
R.
id.
textView)
tv.
text
=
arguments
?.
getString(
"userId")
在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)
③navigateUp() 向上导航
findNavController(
R.
id.
nav_host_fragment).
navigateUp()
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()}")
⑥判断当前页面显示的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"
如需生成适用于 Kotlin
独有的模块的Kotlin
代码,请添加以下行:
apply plugin: "androidx.navigation.safeargs.kotlin"
要使用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")
也可以使用set方法对对应的参数进行赋值
通过Navigation模仿WeChat底部跳转
通过Navigation实现开发中超级常用的底部跳转功能
话不多说,先上效果图:
①右键点击res
目录,然后依次选择New
→ Android Resource File
。此时系统会显示 New Resource File
对话框。File name
输入你的文件夹名(这里设置为menu
),Resource type
选择Menu
,然后我们就可以发下在res
目录下多了一个menu
目录,里面存放着我们底部跳转的item
。
②进入menu.xml
的Design
下
填入四个Item
,并分别设置id
,title
,icon
③创建四个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
一一对应
④在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
>
<?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 缓存封装
.....