当前位置:网站首页>Navigation — 这么好用的导航框架你确定不来看看?
Navigation — 这么好用的导航框架你确定不来看看?
2022-07-07 12:33:00 【InfoQ】
前言
使用Navigation具有什么优势?
- 处理Fragment事务
- 默认情况下,能正确处理往返操作
- 为动画和转换提供标准化资源
- 实现和处理深层链接
- 包括导航界面模式,例如抽屉式导航栏和底部导航,我们只需要完成少量的代码编写
- Safe Args - 可在目标之间导航和传递数据时提供类型安全的Gradle插件
- ViewModel支持 - 您可以将ViewModel的范围限定为导航图,以在图标的目标之间共享与界面相关的数据
如何使用Navigation呢?
Navigation
Navigation
- Navigation graph:一个包含所有导航相关信息的
XML
资源
- NavHostFragment:一种特殊的
Fragment
,用于承载导航内容的容器
- NavController:管理应用导航的对象,实现
Fragment
之间的跳转等操作
第一步:添加依赖
//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"
第二步:创建导航图
res
New
Android Resource Directory
New Resource Directory
Directory name
navigation
Resource type
navigation
navigation
new
Navigation Resource File
File name
第三步:创建Fragment
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">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="hello world" />
</FrameLayout>
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>
navigation
是根标签,通过startDestination
配置默认启动的第一个页面,这里配置的是firstFragment
,我们可以在代码中手动改mainFragment
(启动时的第一个Fragment),也可以在可视化面板中点击Fragment
,再点击Assign Start Destination
,同样可以修改mainFragment
fragment
标签就代表这是一个Fragment
action
标签定义了页面跳转的行为,就是上图中的每条线,destination
定义跳转的目标页,还可以加入跳转时的动画
第五步:处理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>
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()
}
}
第六步:处理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" />
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)
}
}
}
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>
enterAnim
: 跳转时的目标页面动画
exitAnim
: 跳转时的原页面动画
popEnterAnim
: 回退时的目标页面动画
popExitAnim
:回退时的原页面动画
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>
//右滑效果
<?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>
//旋转效果
<?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>
如何传递数据?
nav_graph_main.xml
Design
Fragment
Attributes
Arguments
Action
Argument Default Values
userId
//伪代码,请勿直接cv
<fragment
...
>
...
<argument
android:name="userId"
android:defaultValue="1"
app:argType="integer" />
</fragment>
//默认将 箭头 Action 中设置的参数传递过去
it.findNavController().navigate(R.id.action_firstFragment_to_secondFragment2)
动态传递数据
//伪代码,请勿直接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)
}
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"/>
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([email protected], R.id.nav_host_fragment)
if (id == 1) {
//设置对应的app:navGraph
controller.setGraph(R.navigation.first)
}
if (id == 2) {
controller.setGraph(R.navigation.second)
}
[email protected]()
}
}
Navigation
NavController
Navigation
Fragment
findNavController
Navigation
NavController
api
NavController
Navigation
Fragment
Fragment
Fragment
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)
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()
}
}
②弹出到指定的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)
⑤获取当前导航目的地
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
}
使用 Safe Args 确保类型安全
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"
}
}
app
module
build.gradle
Java
Java
Kotlin
Java
apply plugin: "androidx.navigation.safeargs"
Kotlin
Kotlin
apply plugin: "androidx.navigation.safeargs.kotlin"
android.useAndroidX=true
android.enableJetifier=true
//伪代码,请勿直接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>
BlankFragment
BlankFragment2
var action = BlankFragment.actionJump("111")
action.setParam("222")
通过Navigation模仿WeChat底部跳转
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
<?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>
class HomeFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_home, container, false)
}
}
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>
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)
}
}
Git
尾述
关于我
边栏推荐
- 数据流图,数据字典
- 内存溢出和内存泄漏的区别
- Realization of search box effect [daily question]
- First choice for stock account opening, lowest Commission for stock trading account opening, is online account opening safe
- PERT图(工程网络图)
- 接口自动化测试-接口间数据依赖问题解决
- C # use TCP protocol to establish connection
- Vmware共享主机的有线网络IP地址
- JS get the current time, month, day, year, and the uniapp location applet opens the map to select the location
- 带你掌握三层架构(建议收藏)
猜你喜欢
最长上升子序列模型 AcWing 1014. 登山
JS get the current time, month, day, year, and the uniapp location applet opens the map to select the location
常用数字信号编码之反向不归零码码、曼彻斯特编码、差分曼彻斯特编码
Data flow diagram, data dictionary
Mathématiques avancées - - chapitre 8 différenciation des fonctions multivariables 1
Equipment failure prediction machine failure early warning mechanical equipment vibration monitoring machine failure early warning CNC vibration wireless monitoring equipment abnormal early warning
Details of redis core data structure & new features of redis 6
VSCode 配置使用 PyLint 语法检查器
Did login metamask
UML state diagram
随机推荐
AI人才培育新思路,这场直播有你关心的
带你掌握三层架构(建议收藏)
Parsing of XML files
Equipment failure prediction machine failure early warning mechanical equipment vibration monitoring machine failure early warning CNC vibration wireless monitoring equipment abnormal early warning
请问,我kafka 3个分区,flinksql 任务中 写了 join操作,,我怎么单独给join
call undefined function openssl_ cipher_ iv_ length
Is the compass stock software reliable? Is it safe to trade stocks?
ES日志报错赏析-Limit of total fields
Excellent open source system recommendation of ThinkPHP framework
用例图
Verilog implementation of a simple legv8 processor [4] [explanation of basic knowledge and module design of single cycle implementation]
請問,在使用flink sql sink數據到kafka的時候出現執行成功,但是kafka裏面沒有數
How can the PC page call QQ for online chat?
Is the spare money in your hand better to fry stocks or buy financial products?
What are the principles for distinguishing the security objectives and implementation methods that cloud computing security expansion requires to focus on?
Pert diagram (engineering network diagram)
Vscode configuration uses pylint syntax checker
First choice for stock account opening, lowest Commission for stock trading account opening, is online account opening safe
数据流图,数据字典
PHP中用下划线开头的变量含义