1、引入依赖
implementation 'androidx.navigation:navigation-fragment:2.3.0'
implementation 'androidx.navigation:navigation-ui:2.3.0'
3、navigation 资源文件夹下创建资源文件 navigation.xml,这个文件也叫做 Navigation Graph
navigation.xml 内容如下
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/navigation">
</navigation>
4、创建 RootFragment,布局为 fragment_root.xml
<LinearLayout 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"
android:background="@color/colorPrimary"
tools:context=".MainActivity"
android:gravity="center">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="RootFragment"
android:textColor="@android:color/white"/>
LinearLayout>
5、打开navigation.xml 切换布局模式,添加RootFragment
6、在项目的 MainActivity 布局 activity_main.xml 中,切换到布局模式,左侧 palette 搜索到 Navhoatfragment 拖拽到布局中,使其 宽高都 match_parent
现在 activity_main 中代码如下
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<fragment
android:id="@+id/fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="@navigation/navigation" />
</LinearLayout>
7、运行程序即可,需要注意 MainActivity 需要 extends AppCompatActivity ,否则会报错
可以看看刚才的 navigation.xml
跟之前相比,多了 fragment 节点,以及根节点上多了一个 startDestination 属性。在 Navigation 中我们使用 Destination(目标)来描述 Fragment 之间的跳转关系。这里的 startDestination 代表的就是这个是 Navigation 整个页面跳转管理栈的最根级页面
再来看看那个添加到 MainActivity 页面的 NavHostFragment 组件。它其实就是一个布局文件中的 fragmen 组件,跟我们正常使用的没什么不同,非要说不同,那就是其中的 name、defaultNavHost 以及 navGraph 这三个属性了
name 属性我们都知道,navGraph 属性里面的值是刚才创建 Navigation Graph,就是把 Navigation Graph 引用到了这个 NavHostFragment 中。最后一个 defaultNavHost 就是拦截系统返回按钮的点击事件的
我们再加一个页面(SettingsFragment)。尝试着从 RootFragment 页面点击按钮切换到 SettingsFragment 页面。然后在 SettingsFragment 页面点击按钮返回到 RootFragment
navigation.xml 布局模式跟之前一样的操作,添加 SettingFragment,多了一步就是点击 RootFragment 右边的小圆点然后牵引到右侧的 SettingsFragment。这样他们两个就建立一种关系
现在查看 navigation.xml 的代码,除了增加了一个 fragment 节点之外,原来的 RootFragment 的节点上还增加了一个子节点 action 。事实上,action 节点就是用来描述 Fragmen 之间的页面跳转的关系的,其中 destination 属性的值就是目标 fragment 的 id
我们给 RootFragment 布局中的 TextView 添加点击事件,通过 Navigation 找到一个叫 NavController 的东西,然后执行 navigate 方法,这个方法里面传的值就是刚才 RootFragment 子节点 action 的 id 的值。先运行一下看看效果
textView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Navigation.findNavController(textView).navigate(R.id.action_rootFragment_to_settingFragment);
}
});
点击 RootFragment 跳转 SettingFragment,此时点击系统的返回键,是能返回到 RootFragment。这就是 MainActivity 中 NavHostFragment 组件的属性 app:defaultNavHost=“true” 起到的作用
app:defaultNavHost=”true”
这个属性意味着你的Fragment将会 拦截系统Back键的点击事件(因为系统的back键会直接关闭Activity而非切换Fragment)。让 Navigation 处理返回事件,点返回按钮时并不是返回上一个 Activity,而是返回上一个「页面」,上一个「页面」有可能是 Activity,也可能是 Fragment
下面给 SettingFragment 的文字增加点击事件
tv_name.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Navigation.findNavController(tv_name).popBackStack();
}
});
在 navigation.xml 的视图模式中,选中箭头,在Animations中选中之前写好的动画即可
这样代码中的 action节点会自动增加代码
enterAnim:目标页进来的动画
exitAnim:当前页退出的动画
popEnterAnim:按下返回键,目标页进来的动画
popExitAnim:按下返回键,当前页退出的动画
也可以使用 java代码 实现
NavOptions options = new NavOptions.Builder()
.setEnterAnim(R.anim.slide_left_in)
.setExitAnim(R.anim.slide_left_out)
.setPopEnterAnim(R.anim.slide_left_in)
.setPopExitAnim(R.anim.slide_left_out)
.build();
textView = v.findViewById(R.id.tv_name);
textView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Navigation.findNavController(textView).navigate(R.id.action_rootFragment_to_settingFragment,null,options);
}
});
从上边的代码中可以看到,navigate 的第二个参数我们传了 null,
我们调用了 navigate 这个方法,其中第二个参数是 Bundle 类型,我们填入了 null,那如果正常填了值,Bundle 就是传递到 SettingsFragment 了
Bundle bundle = new Bundle();
bundle.putString("name","Errol");
Navigation.findNavController(textView).navigate(R.id.action_rootFragment_to_settingFragment,bundle,options);
如果想让 SettingFragment 有默认值可以在 navigation.xml 中设置 argument
获取数据时,SettingFragment中
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
String mParam1 = getArguments().getString("name");
Log.d("Budle",""+mParam1);
}
}
可在目标之间导航和传递数据时提供类型安全的 Gradle 插件
在项目根目录的 build.gradle 中加入
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.3.0"
如图:
然后在你项目app 或是 module 的目录下的 build.gradle 文件夹加入:
apply plugin: "androidx.navigation.safeargs"
如图:
现在我们就来从 RootFragmet 传递一个类型为 Int 的年龄字段到 SettingsFragmen 吧。navigation.xml 的图形界面中,选中 SettingsFragment,然后再右侧属性面板上找到 Argments 点击旁边的➕
然后 rebuild project,会在一下路径生成 RootFragmentDirections 和 SettingFragmentArgs 两个文件
RootFragment 中
String nickName = "master";
RootFragmentDirections.ActionRootFragmentToSettingFragment action =
RootFragmentDirections.actionRootFragmentToSettingFragment().setNickName(nickName);
textView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Navigation.findNavController(textView).navigate(action);
}
});
SettingFragment 中
String nickName = SettingFragmentArgs.fromBundle(getArguments()).getNickName();
tv_name.setText(nickName);
这比之前我们用 Bundle 传值要方便的多啦,而且再也不用担心 Key 写错的问题了
1、创建3个fragment:OneFragment、TwoFragment、ThreeFragment,布局中就一个 TextView仅仅为了展示,在 tab_navigation.xml 中把这三个 fragment 添加进去
<?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" android:id="@+id/tab_navigation"
app:startDestination="@id/oneFragment">
<fragment
android:id="@+id/oneFragment"
android:name="com.xx.xx.OneFragment"
android:label="OneFragment" />
<fragment
android:id="@+id/twoFragment"
android:name="com.xx.xx.TwoFragment"
android:label="TwoFragment" />
<fragment
android:id="@+id/threeFragment"
android:name="com.xx.xx.ThreeFragment"
android:label="ThreeFragment" />
</navigation>
2、新建 TabActivity ,布局为 activity_tab,在布局中拖动一个 BottomNavigationView 到底部,上边剩余部分放 NavHostFragment,navGrah 选择我们新建的 tab_navigation.xml
activity_tab 的代码如下:其中menu的设置看第3步
<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=".TabActivity">
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/nv_bottom_menu"
android:layout_width="match_parent"
android:layout_height="48dp"
app:itemHorizontalTranslationEnabled="false"
app:layout_constraintBottom_toBottomOf="parent"
app:menu="@menu/bottom_menu"
android:background="@android:color/white"/>
<fragment
android:id="@+id/fragment2"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
app:defaultNavHost="true"
app:layout_constraintBottom_toTopOf="@+id/nv_bottom_menu"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="@navigation/tab_navigation" />
</androidx.constraintlayout.widget.ConstraintLayout>
3、在 activity_tab 的 Design 模式中,选中 BottomNavigationView ,在右侧会出现 menu 属性,点击右侧按钮,来添加一个
bottom_menu内容:
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/oneFragment"
android:icon="@mipmap/btn_one"
android:title="One" />
<item
android:id="@+id/twoFragment"
android:icon="@mipmap/btn_two"
android:title="Two" />
<item
android:id="@+id/threeFragment"
android:icon="@mipmap/btn_three"
android:title="Three" />
</menu>
现在看 activity_tab 已经变成了这样
4、TabActivity中添加以下代码
NavHostFragment hostFragment = (NavHostFragment) getSupportFragmentManager()
.findFragmentById(R.id.fragment2);
BottomNavigationView navMenu = findViewById(R.id.nv_bottom_menu);
if (hostFragment != null) {
NavController navController = hostFragment.getNavController();
NavigationUI.setupWithNavController(navMenu, navController);
}
第一行,findFragmentById
里填写的 id 就是我们在 tab_activity.xml 中 name 属性是 NavHostFragment 节点的 id
然后再通过 NavigationUI.setupWithNavController()
将二者进行想管理,这样只要我们点击底部导航菜单就是自动实现 Fragment 的之间的切换
事实上,NavigationUI.setupWithNavController() 这个方法有很多重载方法,不仅仅只是用在BottomNavigationView,还有 NavigationView 等,在这里就不一一介绍了感兴趣的可以去试试
Deeplink,简单讲,就是你在手机上点击一个链接之后,可以直接链接到app内部的某个页面,而不是app正常打开时显示的首页。不似web,一个链接就可以直接打开web的内页,app的内页打开,必须用到deeplink技术
1、准备一个 DeepLinkFragment ,页面只展示名称,代码不写了
2、在我们之前的 navigation.xml 中把这个 DeeplinkFragment加进来,然后增加一个 deeplink
navigation.xml 的代码变成了
3、在 manifest 的承载 DeepLinkFragment 的 Activity 节点内引入你的 Navigation Graph
那里填的 url 后面大括号包裹着的是传入 DeepLinkFragment 的值,myarg 是 key,通过 Bundle 进行传递
这篇文章里有两个 Navigation Graph 文件,一个是用于 RootFragment 与 SettingFragment 进行跳转的 navigation.xml,一个是用于底部导航菜单栏的 tab_navigation.xml,我把 DeepLinkFragment 放在了 navigation.xml 中,所以下面的值是 @navigation/navigation
AndroidManifest.xml 中在 MainActivity 下增加
如果 DeeplinkFragment 写了接收参数的代码
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
String mParam1 = getArguments().getString("myarg");
Log.d("myarg",""+mParam1);
}
}
那么在打开 DeeplinkFragment 时还能接收到这个参数
一个App真的只需要一个Activity