Navigation导航
1. Navigation基本介绍:
Navigation的Android Jetpack的成员之一,主要用途是实现用户导航,进入和退出应用中的不同片段的交互,方便我们管理页面和App bar,它具有以下优势:
- 可视化的页面导航图,便于理清页面间的关系
- 通过NavigationUI类,对菜单、底部导航、抽屉菜单导航进行统一的管理
- 方便添加页面切换动画
- 支持深层连接DeepLink
- 支持ViewModel
- 支持Safe Args
2. 组成三要素
Navigation graph(导航图):
这是包含所有导航相关信息的XML资源,这些信息包括应用内所有区域个体(称为目标,一般都是Fragment),以及用户可以通过应用跳转的可能路径。
NavHostFragment(导航宿主):
是一个特殊的Fragment,是其他Fragment的容器。Navigation graph中的Fragment正是通过navHostFragment进行展示的。
NavController
用来在Navigation Graph中切换页面。当你想切换Fragment时,告诉NavController对象你想要去Navigation Graph中的哪个Fragment,它就会将你要去的Fragment展示在NavHostFragment中。
3. 使用Navigation
3.1 基本使用
3.1.1 添加依赖
implementation 'androidx.navigation:navigation-fragment:2.3.1'
implementation 'androidx.navigation:navigation-ui:2.3.1'
3.1.2 向Activity中添加导航宿主(NavHostFragment)
导航宿主必须派生与NavHost,NavHostFragment是导航组件的默认导航宿主实现,负责处理Fragment目的地的交换。
注意:导航组件的设计理念是用于具有一个主 Activity 和多个 Fragment 目的地的应用,主 Activity 与导航图相关联,并且包含一个负责根据需要交换目的地的 NavHostFragment。如果您的应用需要在多个 Activity 上实现导航,就需要为每个 Activity 添加导航宿主,并在每个 Activity 关联其自己的导航图。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="@navigation/nav_graph" />
</LinearLayout>
详细说明:
- android:name="androidx.navigation.fragment.NavHostFragment" 属性包含NavHost实现类的名称,这里使用的是默认实现的NavHostFragment
- app:navGraph="@navigation/nav_graph" 将导航宿主(NavHostFragment)与导航图管理,指向包含所有导航图目的地的导航图资源文件
- app:defaultNavHost="true" 意思是可以拦截系统的返回键,可以理解为默认给fragment实现了返回键的功能,这样在fragment的跳转过程中,当我们按返回键时,就可以使得fragment跟activity一样可以回到上一个页面了
3.1.3 创建导航图资源文件(nav_graph.xml)
导航是发生在各个目的地之间的(只有nav_graph.xml中包含该Fragment时才可以被导航),它包含了所有目的地和操作的声明。 创建步骤:
- 鼠标放在项目模块下的res目录下右击,-> new -> Android Resource File
- 在弹出的窗口中输入文件名
- 在Resource type中选择Navigation
nav_graph.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">
</navigation>
3.1.4 向导航图(nav_graph.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"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/nav_graph"
app:startDestination="@id/loginFragment">
<fragment
android:id="@+id/loginFragment"
android:name="com.wzj.navigationTest.LoginFragment"
android:label="LoginFragment"
tools:layout="@layout/fragment_login">
<action
android:id="@+id/action_loginFragment_to_registerFragment"
app:destination="@id/registerFragment"
app:enterAnim="@anim/slide_in_right"
app:exitAnim="@anim/slide_out_left"
app:popEnterAnim="@anim/slide_in_left"
app:popExitAnim="@anim/slide_out_right"/>
</fragment>
<fragment
android:id="@+id/registerFragment"
android:name="com.wzj.navigationTest.RegisterFragment"
android:label="RegisterFragment"
tools:layout="@layout/fragment_register">
</fragment>
</navigation>
属性详解:
navigation:
- app:startDestination="loginFragment" 表示第一个显示到页面中的Fragment
fragment:
- android:id="" : 这个属性指定该目的地的ID,用于在代码中引用该目的地
- android:name="" : 指定当前Fragment关联的类
- android:label="" : 这个属性指定目的地的名称
- tools:layout="" : 这个Fragment的布局
action:
- android:id="" 一般是action_当前Fragment_to_目标Fragment
- app:destination="" : 指定操作跳转的目的地
- app:launchSingleTop="" 效果类似于Activity中是SingleTop,栈顶复用模式
- app:popUpTo="" 跳转到tag,并弹出tag之上的ragment
- app:popUpToInclusive="" 如果 =true 会弹出当前Fragment,false则不会弹出
3.1.5 通过action连接 各个 Fragment
- 拖动方式
点击跳转前的LoginFragment,鼠标选中其右侧圆圈,拖拽到registerFragment,再松开鼠标
两个Fragment连接后:
- 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"
app:startDestination="@id/loginFragment">
<fragment
android:id="@+id/loginFragment"
android:name="com.wzj.navigationtest.LoginFragment"
android:label="fragment_login"
tools:layout="@layout/fragment_login" >
<action
android:id="@+id/action_loginFragment_to_registerFragment"
app:destination="@id/registerFragment" />
</fragment>
<fragment
android:id="@+id/registerFragment"
android:name="com.wzj.navigationtest.RegisterFragment"
android:label="fragment_register"
tools:layout="@layout/fragment_register" />
</navigation>
3.1.6 用NavController 页面跳转
`
package com.wzj.navigationtest;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.navigation.Navigation;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
public class HomeFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_home, container, false);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
view.findViewById(R.id.tvNickname).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//核心代码 跳转
Navigation.findNavController(view).navigate(R.id.action_homeFragment_to_loginFragment);
}
});
view.findViewById(R.id.tvSettings).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
}
});
}
}
`
3.1.7 导航到目的地与返回指定目的地
- 导航到目的地是使用NavController完成的,这是在导航宿主中管理导航的对象,每个导航宿主都有自己的相应导航控制器(NavController). 导航到目的地的步骤如下:
- 获得导航控制器
获取导航宿主的导航控制器,可以通过以下方法:
- NavHostFragment.findNavController(Fragment)
- Navigation.findNavController(Activity,@IdRes int viewId)
- Navigation.findNavController(View)
- 导航到目的地
获取到导航控制器之后,使用导航控制器类的 NavController.navigate()API 导航到指定的目的地,
- 返回到指定目的地
返回到指定的目的地,是指返回到之前导航过的目的地,这些目的地必须是在任务栈内的,可以通过 NavController.popBackStack() 接口返回上一级,或者通过 NavController.popBackStack (int destinationId, boolean inclusive) 返回到指定的某个目的地
3.1.8 全部代码
mainActivity类中没有代码
activity_main.xml
nav_graph.xml(导航图代码)
fragment_home.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="15dp"
xmlns:app="http://schemas.android.com/apk/res-auto">
<ImageView
android:id="@+id/ivAvator"
android:layout_width="100dp"
android:layout_height="100dp"
android:scaleType="centerCrop"
android:src="@mipmap/ic_launcher"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"/>
<TextView
android:id="@+id/tvNickname"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="22sp"
android:textColor="@android:color/black"
android:text="欢迎您!"
android:clickable="true"
android:focusable="true"
app:layout_constraintTop_toBottomOf="@+id/ivAvator"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:padding="15dp"/>
<Button
android:id="@+id/btnStart"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp"
android:background="#00000000"
android:text="开始使用"
app:layout_constraintTop_toBottomOf="@+id/tvNickname"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
android:layout_marginTop="20dp"/>
<TextView
android:id="@+id/tvSettings"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="22sp"
android:textColor="@android:color/black"
android:text="设置"
android:clickable="true"
android:focusable="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:padding="15dp"/>
<TextView
android:id="@+id/tvDeepLink1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="22sp"
android:textColor="@android:color/black"
android:text="显示深层链接"
android:clickable="true"
android:focusable="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
android:padding="15dp"/>
</androidx.constraintlayout.widget.ConstraintLayout>
HomeFragment
package com.wzj.navigationtest;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.navigation.Navigation;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
public class HomeFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_home, container, false);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
view.findViewById(R.id.tvNickname).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//核心代码 跳转
Navigation.findNavController(view).navigate(R.id.action_homeFragment_to_loginFragment);
}
});
view.findViewById(R.id.tvSettings).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
}
});
}
}
fragment_login.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="20dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="15dp"
android:gravity="center"
android:text="用户登录"
android:textAllCaps="false"
android:textSize="24sp" />
<EditText
android:id="@+id/etAccount"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入账号/邮箱/手机号"
android:padding="15dp"
android:textAllCaps="false"
android:textSize="16sp" />
<EditText
android:id="@+id/etPwd"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入密码"
android:inputType="textPassword"
android:padding="15dp"
android:textAllCaps="false"
android:textSize="16sp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="35dp"
android:orientation="horizontal">
<Button
android:id="@+id/btnCancel"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="5dp"
android:layout_marginRight="5dp"
android:layout_weight="1"
android:text="取消" />
<Button
android:id="@+id/btnLogin"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_marginLeft="5dp"
android:layout_weight="1"
android:text="登录" />
</LinearLayout>
<Button
android:id="@+id/btnRegister"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_marginTop="15dp"
android:background="#00000000"
android:minEms="8"
android:padding="15dp"
android:text="还没有账号?现在注册"
android:textColor="#FF0000FF"
android:textSize="16sp" />
</LinearLayout>
LoginFragment.java
package com.wzj.navigationtest;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.navigation.Navigation;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
public class LoginFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_login, container, false);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
view.findViewById(R.id.btnRegister).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Navigation.findNavController(view).navigate(R.id.action_loginFragment_to_registerFragment);
}
});
}
}
fragment_register.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="20dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAllCaps="false"
android:textSize="24sp"
android:gravity="center"
android:layout_marginBottom="15dp"
android:text="用户注册" />
<EditText
android:id="@+id/etAccount"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="15dp"
android:textSize="16sp"
android:textAllCaps="false"
android:hint="请输入账号/邮箱/手机号"/>
<EditText
android:id="@+id/etPwd"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="15dp"
android:textSize="16sp"
android:textAllCaps="false"
android:inputType="textPassword"
android:hint="请输入密码"/>
<EditText
android:id="@+id/etPwdAgain"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="15dp"
android:textSize="16sp"
android:textAllCaps="false"
android:inputType="textPassword"
android:hint="请再次输入密码"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginTop="35dp">
<Button
android:id="@+id/btnCancel"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginRight="5dp"
android:layout_marginEnd="5dp"
android:text="取消" />
<Button
android:id="@+id/btnRegister"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginLeft="5dp"
android:layout_marginStart="5dp"
android:text="注册" />
</LinearLayout>
</LinearLayout>
RegisterFragment.java里面啥都没写
全局操作
全局操作指定的是无论你在哪个Fragment,只要在同一个导航图中,使用全局资源ID,就可以跳转到指定的Fragment。需要注意,该操作的Action不能被包含在Fragment中,而是在navigation标签下存在。 首先创建全局操作:
- 在Graph Editor中,点击一个目的地,使其突出显示。
- 右键点击该目的地,以显示上下文菜单。
- 依次选择 Add Action > Global。此时系统会在该目的地左侧显示一个箭头
- 查看XML文本视图,即可看到在navigation标签下多了一个action
查看XML文本视图,即可看到在navigation标签下多了一个action
使用全局导航 全局导航可以在任意位置跳转到指定的目的地,但前提是这些目的地必须在同一个Navigation Graph.例如,多个不同目的地的按钮可以全部导航到home页面
使用: 需要确保当前页面与目标页面在同一个Navigation Graph中
写不下去,我写的全局Action一直报错暂未解决
3.2 动画
导航支持在目的地之间添加动画,以提高用户体验。导航动画在定义操作时添加,有以下四个类型:
- enterAnim : 跳转时的目标页面动画
- exitAnim : 跳转时的当前页面动画
- popEnterAnim : 回退时的目标页面动画
- popExitAnim : 回退时的当前页面动画
首先选中action连线,然后设置对应的动画。
创建自己的Animations动画用于页面切换:
res/anim/slide_in_left.xml内容:↓↓↓↓↓↓
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" >
<translate
android:duration="3000"
android:fromXDelta="-50%p"
android:toXDelta="0" />
<alpha
android:duration="3000"
android:fromAlpha="0.0"
android:toAlpha="1.0" />
</set>
res/anim/slide_out_right.xml内容:↓↓↓↓↓
<translate
android:duration="3000"
android:fromXDelta="0"
android:toXDelta="50%p" />
<alpha
android:duration="3000"
android:fromAlpha="1.0"
android:toAlpha="0.0" />
使用方式还是和上面的一样,没有区别
3.3 传递参数
3.3.1 常见的 Bundle 传递方式
接收:
3.3.2 使用safe args插件来传参数
safe ages与传统传参方法相比,好处在于安全的参数类型,Safe Args会生成类型安全的getter和setter方法,如果参数名称,类型或默认值不匹配,会在构建过程中报错。
1.导入依赖
首先,在模块的build.gradle中添加
然后在项目的build.gradle中添加
2.定义目的地参数
- 在Navigation Editor中,点击发送参数的Fragment,在Attributes面板中,点击Add(+)
- 在显示的 Add Argument Link窗口中,输入参数名称、参数类型、参数是否可为null,以及默认值
- 点击Add,该参数会显示在Attributes面板的Arguments列表中,同时可以看到该参数包含在目标Fragment中.并且使用了argument标签
注意:
- android:name 是传递参数的名称
- app:argType 是传递参数的类型
- android:defaultValue: 是传递参数的默认值,需要根据app:argType进行添加
3.使用safe args传参
传递参数↓↓↓↓↓
接收参数:↓↓↓↓↓
4. 嵌套导航图
所谓嵌套导航图,就是在导航图中再嵌入一个导航图,外部的称为父导航图,内部的叫子导航图。嵌套的导航图封装着自己的目的地,且必须标识起始目的地,父导航图访问子导航图只能通过子导航图的起始目的地(不能直接访问子导航图中的目的地),因为子导航图拥有不一样的导航控制器(NavController)。使用嵌套导航图可以对导航目的地进行分类封装,防止错误的发生。
嵌套导航图有两种表现形式,一种是在导航图标签内部嵌套一个标签,另一种是使用include标签引入导航图资源。
注意:
两种表现形式的效果是一样的,如果导航图比较复杂,使用第二种会使得导航图资源显得更加简洁
父导航图中访问子导航图,不能直接访问子导航图中的目的地,只能通过子导航图ID访问子导航图的起始目的地
4.1 navigation标签内部嵌套一个navigation标签
在使用的时候,依然是先定义Action,然后用NavController执行跳转
4.2 使用include 标签引入导航图资源文件
- 首先定义一个导航图资源文件(nav_include.xml)
- 然后在主导航图中使用include引入导航图资源
5. DeepLink深层链接
5.1 PendingIntent(显示深层链接)
当用户通过显式深层链接打开应用时,任务返回堆栈会被清除,并被替换为相应的深层链接目的地。当嵌套图表时,每个嵌套级别的起始目的地(即层次结构中每个 ** < navigation > ** 元素的起始目的地)也会添加到相应堆栈中。也就是说,当用户从深层链接目的地按下返回按钮时,就像从应用入口一步步进入到指定目的地的返回一样的效果。
当 App 受到某个通知推送,我们希望用户在点击该通知时,能够直接跳转到展示该通知内容的页面,可以通过 PendingIntent 来完成。
创建显示深层链接,可以使用NavDeepLinkBuilder类构建PendingIntent,如下:
PendingIntent pendingIntent = new NavDeepLinkBuilder(requireContext())
.setGraph(R.navigation.nav_graph)
.setDestination(R.id.accountSettingFragment)
.createPendingIntent();
如果已有NavController,则还可以通过NavController.createDeepLink()API创建深层链接,如下:
PendingIntent pendingIntent = Navigation.findNavController(getActivity())
.createDeepLink()
.setGraph(R.navigation.nav_graph)
.setDestination(R.id.accountSettingFragment)
.createPendingIntent();
注意:
1.第一种创建显示深层链接的方式中,如果提供的上下文不是Activity,构造函数会使用
PackageManager.getLaunchIntentForPackage()作为默认Activity来启动(如果有);
2.显示深层链接生成的对象的PendingIntent,适合的场景有通知,快捷方式启动,桌面小部件
显示深层链接使用实例:
首先目标页面必须和起始页面在同一Navigation Graph中
然后在发送通知的页面coding
private String channelName = "安安安卓";
private String channelId = "channelId";
public void showNotification(Context context) {
createNotificationChannel(context);
Notification notification = new NotificationCompat.Builder(context, channelId)//此处channelId要与 setChannelId()参数一致
.setSmallIcon(R.drawable.ic_launcher_background)//设置状态栏展示的通知样式
.setContentText("呀呼")
.setContentTitle("正文")
.setChannelId(channelId)//设置通知渠道,这个渠道id必须是和我们创建渠道时候的id对应
.setPriority(NotificationCompat.PRIORITY_HIGH)//设置通知优先级
.setContentIntent(getPendingIntent())//使用PendingIntent
.build();
NotificationManagerCompat.from(context).notify(13, notification);
}
//创建渠道,android8.0系统要求必须创建渠道才能展示通知,同一个渠道只需要被创建一次即可,我们可以在即将展示通知的时候创建,可以在应用启动的时候创建
//也可以在activity中创建。
public NotificationChannel createNotificationChannel(Context context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
String descriptionText = "渠道描述";
int importance = NotificationManager.IMPORTANCE_HIGH;
NotificationChannel channel = new NotificationChannel(channelId, channelName, importance);
channel.setDescription(descriptionText);
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.createNotificationChannel(channel);
return channel;
}
return null;
}
/**
* 返回一个 PendingIntent
*/
private PendingIntent getPendingIntent() {
return Navigation.findNavController(requireActivity(), R.id.nav_host_fragment)
.createDeepLink()
.setGraph(R.navigation.nav_graph)
.setDestination(R.id.deepLinkFragment)
.createTaskStackBuilder().getPendingIntent(0, PendingIntent.FLAG_IMMUTABLE);
}
5.2 创建隐式深层链接(URL方式)
当用户通过手机浏览器浏览网站上某个页面时,可以在网页上放置一个类似于“在应用内打开”的按钮,如果用户的手机安装有我们的 App,那么通过 DeepLink 就能打开相应的页面;如果没有安装,那么网站可以导航到应用程序的下载页面,引导用户安装应用程序。
当用户触发隐式深层链接时,返回堆栈的状态取决于是否使用Intent.FLAG_ACTIVITY_NEW_TASK 标记启动隐式 Intent:
- 如果该标记已设置,则任务返回堆栈会被清除,并被替换为相应的深层链接指定的目的地。就像显式深层链接,当嵌套图表时,每个嵌套级别的起始目的地(即层次结构中每个 元素的起始目的地)也会添加到相应堆栈中。也就是说,当用户从深层链接目的地按下返回按钮时,就像从应用入口一步步进入到指定目的地的返回一样的效果。
- 如果该标记未设置,则仍然位于上一个应用的任务堆栈中,该应用中的隐式深层链接已触发。在这种情况下,如果按下返回按钮,则您会返回到上一个应用;如果按下向上按钮,则会在导航图中的层次父级目的地上启动应用的任务。
6.NavigationUI更新页面组件
因为AppBar的按钮,菜单也需要控制导航,所以使用NavigationUI组件可以将AppBar 和 导航图的页面关联起来,减少重复工作。
假如有 MainFragment 和 SettingsFragment 两个页面,均属于 MainActivity,我们希望二者可通过 ActionBar 互相跳转,即 MainFragment 的 ActionBar 右侧按钮点击可跳转到 SetttingsFragment,且希望 SettingsFragment 的 ActionBar 左侧按钮点击可跳转到 MainFragment,效果如下图:
首先,新建res/menu/menu_settings.xml文件,示例如下:
其中menu的item的id 就是SettingsFragment的id,这样当该menu的item被点击时,就会跳转到SettingsFragment
然后,在MainActivity中实例化menu,
运行后,可以看到menu,效果如下:
我们不需要手动判断菜单项单击的效果,NavigationUI库会自动替我们实现跳转逻辑,MainActivity的代码如下:
public class MainActivity extends AppCompatActivity {
private NavController navController;
private AppBarConfiguration appBarConfiguration;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.nav_host_fragment);
//获取navController
navController = navHostFragment.getNavController();
navController.addOnDestinationChangedListener(new NavController.OnDestinationChangedListener() {
@Override
public void onDestinationChanged(@NonNull NavController controller, @NonNull NavDestination destination, @Nullable Bundle arguments) {
//在onDestinationChanged()可以对页面切换事件进行监听
if(destination.getId() == R.id.settingsFragment){
Toast.makeText(MainActivity.this, "来到settingsFragment页面了", Toast.LENGTH_SHORT).show();
}else if(destination.getId() == R.id.homeFragment){
Toast.makeText(MainActivity.this, "来到home页面了", Toast.LENGTH_SHORT).show();
}
}
});
//绑定当前的ActionBar,除此之外NavigationUi还能绑定Toolbar和CollapsingToolbarLayout
//绑定后,系统会默认处理ActionBar左上角区域,为你添加返回按钮,将所切换到的Fragment在导航图里的name属性中的内容显示到Title
appBarConfiguration = new AppBarConfiguration.Builder(navController.getGraph()).build();
NavigationUI.setupActionBarWithNavController(this,navController,appBarConfiguration);
}
//加载菜单
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
getMenuInflater().inflate(R.menu.menu_settings, menu);
return true;
}
//ActionBar中的按钮被点击时,根据菜单中的ID,跳转到相应的页面
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
return NavigationUI.onNavDestinationSelected(item,navController) || super.onOptionsItemSelected(item);
}
//左上角的返回按钮被点击时调用到
@Override
public boolean onSupportNavigateUp() {
return NavigationUI.navigateUp(navController,appBarConfiguration)||super.onSupportNavigateUp();
}
}
因为上文已经在res/menu/menu_settings.xml中设置了item的id就是SettingsFragment的id,并且通过NavigationUI.setupActionBarWithNavController(this,navController,appBarConfiguration); 将AppBar和NavigationUI绑定
所以当点击ActionBar右上角的按钮时会跳转到SettingsFragment
并且当在SettingsFragment点击ActionBar左上角的返回按钮时会跳转到MainFragment
接下来,需要在SettingsFragment也显示ActionBar右上角的按钮,并清除从MainFragment跳转过来时下拉已选的menu,代码如下:
展示结果:
另外NavigationUI还支持
- Toolbar
- CollapsingToolbarLayout
- App bar 左侧的抽屉菜单(DrawLayout+NavigationView)
- 底部菜单(BottomNavigation)