Navigation基本使用
-
概述:
-
JetPack组件库共有差不多60个库
-
Navigation的初心:Android应该只有一个Activity
- 一个Activity作为控制层,有许多Fragment显示页面(对比前端的脚手架)
-
-
Navigation概述:
-
官网描述:用于在Android应用的目标之间导航,提供了一致的API,无论目标是Fragment还是Activity还是其他组件
-
未引入Navigation:
- 采用TableLayout或者Fragment手动管理
-
主要功能:
- 完成Fragment的导航,特别是在单个Activity配合多个Fragment实现
-
项目架构:
-
整体示意图
-
细节:
-
整体采用单个MainActivity+多个fragment(配合ViewModel),其中Activity只做控制,fragment展示页面
-
nav_graph_main.xml:导航图而已
- 位置:res/navigation
- 作用:指定所导航的fragment信息(id,name,label,layout,action)
-
多个fragment实例:
- 显示页面的具体UI
-
fragmenti :fragmentiViewModel = 1 :1
- ViewModel(维护fragment的数据)
-
在JetPack标准架构模型中仓库只有一个
-
-
-
代码实现:
-
实现思路:
-
构建MainActivity:做控制
- onCreate之外:声明布局中的BottomNavigationView
- onCreate之内:实例化布局中的BottomNavigationView
- 获取navHostFragment
- 获取navController
- 绑定BottomNavigationView与navController
-
构建activity_main.xml
-
编写FragmentContainerView
- id
- name:NavHostFragment
- 屏蔽系统返回键,启用fragment任务栈:defaultNavHost=”true”
- 指定导航图:navGraph=”@navigation/nav_graph_main”
-
编写BottomNavigationView
- 内嵌menu
-
编写menu:完善item细节
- id
- icon
- title
-
-
编写导航图:
-
新建navigation包:res/navigation
-
新建导航图文件:nav_graph_main
-
向导航图填充fragment标签:真正用来展示界面的
-
完善fragment标签细节:
-
指定id:作为跳转地址
android:id="@+id/page1Fragment" 复制代码
-
指定name:反射执行
android:name="com.derry.navigation.MainPage1Fragment" 复制代码
-
指定label:fragment的标签
-
指定layout:fragment布局文件
-
新建action标签:指定跳转顺序(可以使用图形化界面玩)
<action android:id="@+id/action_page2" app:destination="@id/page2Fragment" /> 复制代码
-
-
-
构建MainActivity与Fragment
-
工程结构:
-
代码实现:
-
MainPage1Fragment
-
点击事件:跳转到fragment1
val btn = view.findViewById<Button>(R.id.btn) btn.setOnClickListener { view -> 配合导航图进行跳转 Navigation.findNavController(view).navigate(R.id.action_page2) } 复制代码
-
-
MainPage2Fragment
-
点击事件:跳转到fragment1、fragment2
val btn = view.findViewById<Button>(R.id.btn) btn.setOnClickListener { view -> //配合导航图进行页面跳转 Navigation.findNavController(view).navigate(R.id.action_page1) // Navigation.findNavController(view).navigateUp(); 返回上一个Fragment } val btn2 = view.findViewById<Button>(R.id.btn2) btn2.setOnClickListener { view -> Navigation.findNavController(view).navigate(R.id.action_page3) } 复制代码
-
navigateUp():回退到上一个fragment(上次跳转的起点)
-
navigate(R.id.action_page1):指定id,进行跳转
-
这两句话,效果一样
-
-
MainPage3Fragment
-
点击事件:跳转到fragment2
btn.setOnClickListener { view -> Navigation.findNavController(view).navigate(R.id.action_page2) // 回退上一步 // Navigation.findNavController(view).navigateUp(); } 复制代码
-
-
-
构建Navgation导航图:nav_gragh_main.xml
-
原始res目录
-
选中res—>右键—>New—>Android Resource File
-
type选择Navigation(会在res文件夹下自动新建一个navigation文件夹,存放新建 .xml)
-
新建后的res资源目录
-
-
建立导航图与Fragment之间的链接:导航图去寻找实例并且实例是在导航图之前建立的
-
先点击res/navigation下的nav_graph_main.xml再点击屏幕右边的Design选项(切换到以下视图)
-
细节:
- 当点击上图红色按钮后,可以在下拉列表中选择fragment实例并且第一个选择的实例作为导航图的默认启动页面(就是最上角有个小房子的哪个)
-
修改导航图默认启动页面:在Design视图中,默认启动页面左上角是有一个小房子的
修改nav_graph_main.xml:app:startDestination=”@id/page1Fragment
<navigation …… <--! 此时导航图以page1Fragment为默认启动页面--> app:startDestination="@id/page1Fragment"> 复制代码
-
-
通过拖动建立Fragment之间的跳转关系(指定action)
-
效果图:
-
实现细节:
-
如何建立视图之间的跳转关系?
-
将鼠标悬停至跳转起点页面右侧,出现一个蓝色的圆圈,点一下(拉出一根线),将其连给在本次跳转的目标页面上即可
-
代码描述:由page1Fragment跳转到page2Fragment
<fragment …… > <!-- action:程序中使用id跳到destination对应的类 --> <action android:id="@+id/action_page2" app:destination="@id/page2Fragment" /> </fragment> 复制代码
-
-
为什么在单个fragment布局文件中name属性为此fragment的全类名?
-
为了通过反射进行激活
<fragment android:id="@+id/page1Fragment" android:name="com.wasbry.navigation.MainPage1Fragment" android:label="fragment_page1" tools:layout="@layout/fragment_main_page1"> </fragment> 复制代码
-
-
-
-
nav_graph_main.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.xml" app:startDestination="@id/page1Fragment"> <!-- TODO 这个是第一个 Fragment --> <fragment android:id="@+id/page1Fragment" android:name="com.derry.navigation.MainPage1Fragment"反射实例化 android:label="fragment_page1" tools:layout="@layout/fragment_main_page1"> <!-- action:程序中使用id跳到destination对应的类 --> <action android:id="@+id/action_page2" app:destination="@id/page2Fragment" /> </fragment> <!-- TODO 这个是第二个 Fragment --> <fragment android:id="@+id/page2Fragment" android:name="com.derry.navigation.MainPage2Fragment" android:label="fragment_page2" tools:layout="@layout/fragment_main_page2"> <action android:id="@+id/action_page1" app:destination="@id/page1Fragment" /> <action android:id="@+id/action_page3" app:destination="@id/page3Fragment" /> </fragment> <!-- TODO 这个是第三个 Fragment --> <!-- <navigation--> <!-- android:id="@+id/nav_graph_page3"--> <!-- app:startDestination="@id/page3Fragment">--> <fragment android:id="@+id/page3Fragment" android:name="com.derry.navigation.MainPage3Fragment" android:label="fragment_page3" tools:layout="@layout/fragment_main_page3"> <action android:id="@+id/action_page2" app:destination="@id/page2Fragment" /> </fragment> </navigation> 复制代码
-
细节:
- 导航图(nav_graph_main.xml),只是一个xml,不能去激活三个fragment实例;
- 需要在Activity布局引入此导航图,在Activity中指挥导航图
-
-
-
编写Activity布局文件:控制导航图实现对Fragment的控制(这个导航图是很流行的)
-
引入fragment
<androidx.fragment.app.FragmentContainerView android:id="@+id/my_nav_host_fragment" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_weight="9" android:name="androidx.navigation.fragment.NavHostFragment" app:defaultNavHost="true" app:navGraph="@navigation/nav_graph_main"/> 复制代码
- 可以将外部标签androidx.fragment.app.FragmentContainerView替换为fragment:功能会减少许多,这个是次要的
- NavHostFragment:这个才是主导航,在Activity的布局文件中被引入并且指向导航图,这个是主要的
- app:defaultNavHost:拦截系统返回键,激活fragment的任务栈
- app:navGraph:在activity_main.xml中激活导航图
-
-
实现功能:模仿微信,点击底部导航栏跳转页面
-
实现思路:
-
在activity_main.xml中添加菜单
<com.google.android.material.bottomnavigation.BottomNavigationView android:id="@+id/nav_view" android:layout_width="match_parent" android:layout_height="wrap_content" app:itemTextColor="#ff0000" app:menu="@menu/menu" /> 复制代码
-
引入菜单布局
-
菜单存储位置:
-
实现效果:
-
代码实现:
<?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@+id/page1Fragment" android:icon="@drawable/ic_launcher_foreground" android:title="第一页"/> <item android:id="@+id/page2Fragment" android:icon="@drawable/ic_launcher_foreground" android:title="第二页"/> <item android:id="@+id/page3Fragment" android:icon="@drawable/ic_launcher_foreground" android:title="第三页"/> </menu> 复制代码
-
-
在MainActivity中绑定navigation
-
代码展示:
val navHostFragment = supportFragmentManager.findFragmentById(R.id.my_nav_host_fragment) as NavHostFragment? 复制代码
-
实现细节:来自于Google官网代码
-
获取底部导航栏实例
bottomNavigationView = findViewById(R.id.nav_view) 复制代码
-
需要去拿到fragment
public FragmentManager getSupportFragmentManager() { return mFragments.getSupportFragmentManager(); } 复制代码
-
根据id拿到主导航页面
findFragmentById(R.id.my_nav_host_fragment) 复制代码
-
此时拿到的fragment需要将其强转成fragment
as NavHostFragment 复制代码
-
有可能强转失败,所以添加?,失败时将其赋值为空
as NavHostFragment? 复制代码
-
获得navigation的控制者
val controller = navHostFragment!!.navController 复制代码
-
拿到UI:由于这句话,底部的导航栏才能实现点击
NavigationUI.setupWithNavController(bottomNavigationView!!, controller) 复制代码
-
-
-
实现效果
-
-
-
修改MainActivity细节:
-
别名问题:action也有自己的id,它包含的page又有单独的id;可以实现调用action的id拿到指定的Page的id(destination),再跳转到指定的page
-
根据id与destination进行页面跳转
-
MainFragment1
val btn = view.findViewById<Button>(R.id.btn) btn.setOnClickListener { view -> Navigation.findNavController(view).navigate(R.id.action_page2) } 复制代码
-
MainFragment2:存在弹栈机制(按下返回键会默认调用这个up)(也可以直接指定id来玩)
val btn = view.findViewById<Button>(R.id.btn) btn.setOnClickListener { view -> Navigation.findNavController(view).navigate(R.id.action_page1) // Navigation.findNavController(view).navigateUp(); 返回上一个Fragment,内部存在fragment任务栈 } val btn2 = view.findViewById<Button>(R.id.btn2) btn2.setOnClickListener { view -> Navigation.findNavController(view).navigate(R.id.action_page3) } 复制代码
-
MainFragment3
val btn = view.findViewById<Button>(R.id.btn) btn.setOnClickListener { view -> Navigation.findNavController(view).navigate(R.id.action_page2) // 回退上一步 // Navigation.findNavController(view).navigateUp(); } 复制代码
-
-
-
实际上在新建project的时候,就可以选择ButtomNavigationActivity
-
示意图:
-
项目架构:
-
src下有三个包,每个包对应一个fragment+ViewModel
-
res/layout:有对应的fragment的布局文件
-
res/navigation:有对应的导航图
-
res/menu:有对应的菜单
-
-
使用这种方法与自己写的相同点:
- 项目架构与实现思路基本一致
- 也在activity_main.xml中引入了navHostFragment
-
使用这种方法与自己写的不同点:
-
它的内部是没有连线的,采用fragment直接来玩的
-
这个更加专业:
- ViewModel配合LiveData(MutableLiveData)
- 分层更加规范
-
-
使用这种方法的问题
-
组件在高版本中会遇到很多问题:
-
将SDK版本改成30
-
31在Windows与mac上面都是有点问题的
-
-
修改注册清单文件:修改内置的label为 BootomNavigationActivity的Activity
- 增加属性修饰exported
-
-