JetPack-(2)-Navigation

189 阅读10分钟

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时才可以被导航),它包含了所有目的地和操作的声明。 创建步骤:

  1. 鼠标放在项目模块下的res目录下右击,-> new -> Android Resource File
  2. 在弹出的窗口中输入文件名
  3. Resource type中选择Navigation

35553fee0ea1c90efb55e480e641304.png

676b015bff7a530ec05fbb5c47affca.png

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)
  • 通过点击方式添加

b4905467adcb6c39a95869436205850.png

  • 通过代码添加
<?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,再松开鼠标

1710077783849.jpg

两个Fragment连接后:

1710078048105.jpg

  • 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). 导航到目的地的步骤如下:
  1. 获得导航控制器

获取导航宿主的导航控制器,可以通过以下方法:

  • NavHostFragment.findNavController(Fragment)
  • Navigation.findNavController(Activity,@IdRes int viewId)
  • Navigation.findNavController(View)
  1. 导航到目的地

获取到导航控制器之后,使用导航控制器类的 NavController.navigate()API 导航到指定的目的地,

  • 返回到指定目的地

返回到指定的目的地,是指返回到之前导航过的目的地,这些目的地必须是在任务栈内的,可以通过 NavController.popBackStack() 接口返回上一级,或者通过 NavController.popBackStack (int destinationId, boolean inclusive) 返回到指定的某个目的地

3.1.8 全部代码

mainActivity类中没有代码

activity_main.xml

b512f9fafa179adb55ffec95ee6aced.png

nav_graph.xml(导航图代码)

a8c476a117b6436ffc226d57f603f1a.png

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标签下存在。 首先创建全局操作:

  1. 在Graph Editor中,点击一个目的地,使其突出显示。
  2. 右键点击该目的地,以显示上下文菜单。
  3. 依次选择 Add Action > Global。此时系统会在该目的地左侧显示一个箭头
  4. 查看XML文本视图,即可看到在navigation标签下多了一个action

1710084589357.jpg

查看XML文本视图,即可看到在navigation标签下多了一个action

1710084678630.jpg

使用全局导航 全局导航可以在任意位置跳转到指定的目的地,但前提是这些目的地必须在同一个Navigation Graph.例如,多个不同目的地的按钮可以全部导航到home页面

使用: 需要确保当前页面与目标页面在同一个Navigation Graph中

写不下去,我写的全局Action一直报错暂未解决

3.2 动画

导航支持在目的地之间添加动画,以提高用户体验。导航动画在定义操作时添加,有以下四个类型:

  • enterAnim : 跳转时的目标页面动画
  • exitAnim : 跳转时的当前页面动画
  • popEnterAnim : 回退时的目标页面动画
  • popExitAnim : 回退时的当前页面动画

首先选中action连线,然后设置对应的动画。

1710086587402.jpg

创建自己的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 传递方式

1710088597871.jpg

接收:

1710088641887.jpg

3.3.2 使用safe args插件来传参数

safe ages与传统传参方法相比,好处在于安全的参数类型,Safe Args会生成类型安全的gettersetter方法,如果参数名称,类型或默认值不匹配,会在构建过程中报错。

1.导入依赖

首先,在模块的build.gradle中添加

1710089038542.jpg

然后在项目的build.gradle中添加

1710089120507.jpg

2.定义目的地参数
  1. Navigation Editor中,点击发送参数的Fragment,在Attributes面板中,点击Add(+)
  2. 在显示的 Add Argument Link窗口中,输入参数名称、参数类型、参数是否可为null,以及默认值

1710090177920.jpg

  1. 点击Add,该参数会显示在Attributes面板的Arguments列表中,同时可以看到该参数包含在目标Fragment中.并且使用了argument标签

1710090262843.jpg 注意:

  • android:name 是传递参数的名称
  • app:argType 是传递参数的类型
  • android:defaultValue: 是传递参数的默认值,需要根据app:argType进行添加
3.使用safe args传参

传递参数↓↓↓↓↓

1710095549175.jpg

接收参数:↓↓↓↓↓

1710095615172.jpg

4. 嵌套导航图

所谓嵌套导航图,就是在导航图中再嵌入一个导航图,外部的称为父导航图,内部的叫子导航图。嵌套的导航图封装着自己的目的地,且必须标识起始目的地,父导航图访问子导航图只能通过子导航图的起始目的地(不能直接访问子导航图中的目的地),因为子导航图拥有不一样的导航控制器(NavController)。使用嵌套导航图可以对导航目的地进行分类封装,防止错误的发生。

嵌套导航图有两种表现形式,一种是在导航图标签内部嵌套一个标签,另一种是使用include标签引入导航图资源。

注意:
两种表现形式的效果是一样的,如果导航图比较复杂,使用第二种会使得导航图资源显得更加简洁
父导航图中访问子导航图,不能直接访问子导航图中的目的地,只能通过子导航图ID访问子导航图的起始目的地
4.1 navigation标签内部嵌套一个navigation标签

1710174604896.jpg

在使用的时候,依然是先定义Action,然后用NavController执行跳转

4.2 使用include 标签引入导航图资源文件
  • 首先定义一个导航图资源文件(nav_include.xml)

1710174994724.jpg

  • 然后在主导航图中使用include引入导航图资源

1710175097428.jpg

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中 1710255833250.jpg

然后在发送通知的页面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 和 导航图的页面关联起来,减少重复工作。

假如有 MainFragmentSettingsFragment 两个页面,均属于 MainActivity,我们希望二者可通过 ActionBar 互相跳转,即 MainFragmentActionBar 右侧按钮点击可跳转到 SetttingsFragment,且希望 SettingsFragmentActionBar 左侧按钮点击可跳转到 MainFragment,效果如下图:

1710259933672.jpg

首先,新建res/menu/menu_settings.xml文件,示例如下:

其中menuitem的id 就是SettingsFragmentid,这样当该menuitem被点击时,就会跳转到SettingsFragment

1710260018331.jpg

然后,在MainActivity中实例化menu,

1710260157057.jpg 运行后,可以看到menu,效果如下:

4f381d3ecf0a998c0e9292ac098c91f.jpg

59c92ec088a5facdf003a6a41104375.jpg

我们不需要手动判断菜单项单击的效果,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中设置了itemid就是SettingsFragmentid,并且通过NavigationUI.setupActionBarWithNavController(this,navController,appBarConfiguration); 将AppBarNavigationUI绑定

所以当点击ActionBar右上角的按钮时会跳转到SettingsFragment

并且当在SettingsFragment点击ActionBar左上角的返回按钮时会跳转到MainFragment

接下来,需要在SettingsFragment也显示ActionBar右上角的按钮,并清除从MainFragment跳转过来时下拉已选的menu,代码如下:

1710260872436.jpg

展示结果:

1710261569100.jpg 1710261569100.jpg

另外NavigationUI还支持

  • Toolbar
  • CollapsingToolbarLayout
  • App bar 左侧的抽屉菜单(DrawLayout+NavigationView)
  • 底部菜单(BottomNavigation)