当前位置:网站首页>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尾述
关于我
边栏推荐
- Environment configuration of lavarel env
- [AI practice] Application xgboost Xgbregressor builds air quality prediction model (II)
- Excuse me, when using Flink SQL sink data to Kafka, the execution is successful, but there is no number in Kafka
- 搜索框效果的实现【每日一题】
- 【立体匹配论文阅读】【三】INTS
- The difference between memory overflow and memory leak
- Parameter keywords final, flags, internal, mapping keywords internal
- Similarities and differences between switches and routers
- Cesium 已知一点经纬度和距离求另一个点的经纬度
- PERT图(工程网络图)
猜你喜欢

The delivery efficiency is increased by 52 times, and the operation efficiency is increased by 10 times. See the compilation of practical cases of financial cloud native technology (with download)

Selenium Library

Assign a dynamic value to the background color of DataGrid through ivalueconverter

最长上升子序列模型 AcWing 1014. 登山

【立体匹配论文阅读】【三】INTS

Docker deploy Oracle

Social responsibility · value co creation, Zhongguancun network security and Information Industry Alliance dialogue, wechat entrepreneur Haitai Fangyuan, chairman Mr. Jiang Haizhou

数据流图,数据字典
![[fortress machine] what is the difference between cloud fortress machine and ordinary fortress machine?](/img/fb/17e029b1d955965d7e2e0f58701d91.png)
[fortress machine] what is the difference between cloud fortress machine and ordinary fortress machine?

手把手教会:XML建模
随机推荐
请问,PTS对数据库压测有好方案么?
Excusez - moi, l'exécution a été réussie lors de l'utilisation des données de puits SQL Flink à Kafka, mais il n'y a pas de nombre dans Kafka
Excerpt from "misogyny: female disgust in Japan"
Laravel Form-builder使用
Parsing of XML files
杭电oj2092 整数解
IP and long integer interchange
手把手教会:XML建模
Cargo placement problem
ES日志报错赏析-Limit of total fields
Best practice | using Tencent cloud AI willingness to audit as the escort of telephone compliance
Excuse me, as shown in the figure, the python cloud function prompt uses the pymysql module. What's the matter?
Es log error appreciation -limit of total fields
Dry goods | summarize the linkage use of those vulnerability tools
oracle 非自动提交解决
Seven propagation behaviors of transactions
Vmware共享主机的有线网络IP地址
Cascading update with Oracle trigger
Verilog implementation of a simple legv8 processor [4] [explanation of basic knowledge and module design of single cycle implementation]
【AI实战】应用xgboost.XGBRegressor搭建空气质量预测模型(二)