当前位置:网站首页>Navigation — 这么好用的导航框架你确定不来看看?
Navigation — 这么好用的导航框架你确定不来看看?
2022-07-07 12:33:00 【InfoQ】
前言
使用Navigation具有什么优势?
- 处理Fragment事务
- 默认情况下,能正确处理往返操作
- 为动画和转换提供标准化资源
- 实现和处理深层链接
- 包括导航界面模式,例如抽屉式导航栏和底部导航,我们只需要完成少量的代码编写
- Safe Args - 可在目标之间导航和传递数据时提供类型安全的Gradle插件
- ViewModel支持 - 您可以将ViewModel的范围限定为导航图,以在图标的目标之间共享与界面相关的数据
如何使用Navigation呢?
NavigationNavigation- 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"
第二步:创建导航图
resNewAndroid Resource DirectoryNew Resource DirectoryDirectory namenavigationResource typenavigationnavigationnewNavigation Resource FileFile 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)
}
}
FirstFragementSecondFragmentThirdFragmentNavigation graphnav_graph_main.xmlDesignNavigation EditorCreate new destinationFragmentFinishFragment
第四步:将Fragment拖入面板并进行跳转配置
Navigation EditorFragmentFragmentFragmentFirstFragmentSecondFragmentThirdFragmentFirstFragment
nav_graph_main.xmlCodexml<?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
MainActivityNavHostFragmentNavigationActivityFragmentNavHostFragment<?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。
MainActivityonSupportNavigateUpActivitybackclass 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的对应跳转事件
FragmentTextViewFragment<?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_secondFragment2nav_graph_main.xmlaction<action
android:id="@+id/action_firstFragment_to_secondFragment2"
app:destination="@id/secondFragment" />

跳转动画&自定义动画
nav_graph_main.xmlDesignFragmentAnimationsenterAnimPick a Resourenav_default_enter_animexitAnimnav_default_exit_animaction<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:回退时的原页面动画
resNewAndroid Resource FileNew Resource FileFile nameslide_from_leftResource typeAnimationresanim//左滑效果
<?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.xmlDesignFragmentAttributesArgumentsActionArgument Default ValuesuserId//伪代码,请勿直接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
navigationsetGraphNavigationactivity_mainfragmentapp: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"/>
setGraphapp:navGraphclass 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]()
}
}
NavigationNavController
NavigationFragmentfindNavControllerNavigationNavControllerapiNavControllerNavigationFragmentFragmentFragmentTabLayoutNavigation如何获取
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
oneFragmentsecondFragmentthirdFragmentthirdFragmentsecondFragmentpopBackStackoverride 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()
navigateUpFragmentpopBackStacknavigateUppopBackStacknavigateUpNavigationFragmentpopBackStack()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 Argsbuild.gradle//将其放在plugins{}之前
buildscript {
repositories {
google()
}
dependencies {
def nav_version = "2.4.2"
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
}
}
appmodulebuild.gradleJavaJavaKotlinJavaapply plugin: "androidx.navigation.safeargs"
KotlinKotlinapply 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>
BlankFragmentBlankFragment2var action = BlankFragment.actionJump("111")
action.setParam("222")
通过Navigation模仿WeChat底部跳转

resNewAndroid Resource FileNew Resource FileFile namemenuResource typeMenuresmenuitem
menu.xmlDesignItemidtitleicon
FragmentNavigation<?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.xmlFragmentidmenu.xmlid
activity_mainBottomNavigationViewdrawableselector_menu_text_color.xmlItem<?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尾述
关于我
边栏推荐
- 多商户商城系统功能拆解01讲-产品架构
- JS get the current time, month, day, year, and the uniapp location applet opens the map to select the location
- Cargo placement problem
- The difference between memory overflow and memory leak
- UML 顺序图(时序图)
- IP address home location query full version
- SAKT方法部分介绍
- mysql导入文件出现Data truncated for column ‘xxx’ at row 1的原因
- OAuth 2.0 + JWT 保护API安全
- How to check the ram and ROM usage of MCU through Keil
猜你喜欢

用例图

数据流图,数据字典

Vmware 与主机之间传输文件

带你掌握三层架构(建议收藏)

Wired network IP address of VMware shared host

Selenium库

Data flow diagram, data dictionary

Horizontal of libsgm_ path_ Interpretation of aggregation program

Use day JS let time (displayed as minutes, hours, days, months, and so on)

Social responsibility · value co creation, Zhongguancun network security and Information Industry Alliance dialogue, wechat entrepreneur Haitai Fangyuan, chairman Mr. Jiang Haizhou
随机推荐
Introduction to sakt method
交换机和路由器的异同
UML sequence diagram (sequence diagram)
Lavarel之环境配置 .env
Excuse me, why is it that there are no consumption messages in redis and they are all piled up in redis? Cerely is used.
Excuse me, does PTS have a good plan for database pressure measurement?
Csma/cd carrier monitoring multipoint access / collision detection protocol
Cesium 已知一点经纬度和距离求另一个点的经纬度
IP address home location query
gvim【三】【_vimrc配置】
请问,在使用flink sql sink数据到kafka的时候出现执行成功,但是kafka里面没有数
Reverse non return to zero code, Manchester code and differential Manchester code of common digital signal coding
Social responsibility · value co creation, Zhongguancun network security and Information Industry Alliance dialogue, wechat entrepreneur Haitai Fangyuan, chairman Mr. Jiang Haizhou
UML 顺序图(时序图)
oracle 非自动提交解决
The longest ascending subsequence model acwing 1014 Mountaineering
AutoCAD - how to input angle dimensions and CAD diameter symbols greater than 180 degrees?
Use case diagram
Common response status codes
请问,如图,pyhon云函数提示使用了 pymysql模块,这个是怎么回事?