Android---Jetpack之Navigation

目录

为此,Jetpack 提供了 Navigation 组件,旨在方便我们管理页面和 App Bar。 

 Navigation 的优势

添加页面切换动画效果 

普通方式与 safe args  插件方式参数传递

完整 Demo


Navigation 的诞生

Activity 嵌套多个 Fragment 的 UI 架构模式已经非常普遍,但是对 Fragment 的管理一直是一件比较麻烦的事情。我们需要通过 FragmentManager 和 FragmentTransaction 来管理 Fragment 之间的切换。页面的切换通常还包括对应用程序 App bar 的管理、Fragment 间的切换动画,以及 Fragment 间的参数传递。纯代码的方式使用起来不是特别友好,并且 Fragment 和 App bar 在管理和使用的过程中显得混乱。

为此,Jetpack 提供了 Navigation 组件,旨在方便我们管理页面和 App Bar。 

 Navigation 的优势

  \bullet 可视化的页面导航图,类似于 Apple Xcode 中的 StoryBoard,便于我们理清页面关系。

  \bullet 通过 destination 和 action 完成页面间的导航。

  \bullet 方便添加页面切换动画。

  \bullet 页面间类型安全的参数传递。

  \bullet 通过 NavigationUI,对菜单底部导航抽屉菜单导航进行统一的管理。

  \bullet 支持深层链接 DeepLink。

Navigation 的主要元素

  \bullet Navigation Graph,一种新的 xml 资源文件,包含应用程序所有的页面,以及页面间的关系。

  \bullet NavHostFragment,一种特殊的 Fragment,可以将它看作是其他 Fragment 的容器,                    Navigation Grapth 中的 Fragment 正是通过 NavHostFragment 进行展示的。

  \bullet NavController,用于在代码中完成 Navigation Graph 中具体的页面切换工作。

它们三者之间的关系:

当你想切换 Fragment 时,使用 NavController 对象,告诉它你想要去 Navigation Graph 中的哪个 Fragment,NavController 会将你想去的 Fragment 展示在 NavHostFragment 中。

Navigation 应用

当点击fragment_home 里 Button 时,可以由 fragment_home 跳转到 fragment_detail。同理,当点击fragment_detail 里 Button 时,可以由 fragment_detail 跳转到 fragment_home。这个功能由navigation 实现。

步骤1:创建 HomeFragment/DetailFragment,并修改布局

fragment_home.xml / fragment_detail.xml

<?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=".HomeFragment">

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

步骤2:创建一个 Navigation 目录

在 Navigation 目录创建完毕后,会创建一个 my_nav_graph.xml(名字可以自取) 的文件。

 my_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/my_nav_graph"
    app:startDestination="@id/homeFragment">
    <fragment
        android:id="@+id/homeFragment"
        android:name="com.example.navigation.HomeFragment"
        android:label="fragment_home"
        tools:layout="@layout/fragment_home" >
        <action
            android:id="@+id/action_homeFragment_to_detailFragment"
            app:destination="@id/detailFragment" />
    </fragment>
    <fragment
        android:id="@+id/detailFragment"
        android:name="com.example.navigation.DetailFragment"
        android:label="fragment_detail"
        tools:layout="@layout/fragment_detail" >
        <action
            android:id="@+id/action_detailFragment_to_homeFragment"
            app:destination="@id/homeFragment" />
    </fragment>
</navigation>

 注意action属性,即 homeFragment 到 detailFragment 和 detailFragment 到 homeFragment

<action
            android:id="@+id/action_homeFragment_to_detailFragment"
            app:destination="@id/detailFragment" />
<action
            android:id="@+id/action_detailFragment_to_homeFragment"
            app:destination="@id/homeFragment" />

步骤3: 在 activity_main.xml 里创建NavHostFragment

activity_main.xml

<?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/fragmentContainerView"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="409dp"
        android:layout_height="729dp"
        app:defaultNavHost="true"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:navGraph="@navigation/my_nav_graph" />

</androidx.constraintlayout.widget.ConstraintLayout>

 步骤4:MainActivity.java。设置 NavController

package com.example.navigation;

import androidx.appcompat.app.AppCompatActivity;
import androidx.navigation.NavController;
import androidx.navigation.Navigation;
import androidx.navigation.ui.NavigationUI;

import android.os.Bundle;

public class MainActivity extends AppCompatActivity {
    private NavController navController;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //TODO NavController 管理 NavHostFragment 里的 Fragment 的切换
        navController = Navigation.findNavController(this, R.id.fragmentContainerView);
        NavigationUI.setupActionBarWithNavController(this, navController);
    }

    /**
     * 支持顶部的返回键起作用
     */
    @Override
    public boolean onSupportNavigateUp() {

        return navController.navigateUp();
    }
}

步骤5:HomeFragment.java。点击 Button 实现跳转

package com.example.navigation;

import android.os.Bundle;

import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.navigation.NavController;
import androidx.navigation.Navigation;

import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;

public class HomeFragment extends Fragment {


    public HomeFragment() {
        // Required empty public constructor
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_home, container, false);
    }

    /**
     * 事件监听的设置
     */
    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        Button btn1 = getView().findViewById(R.id.button);
        btn1.setOnClickListener(v -> {//TODO 点击按钮后跳转
            // v 就是我们的 button,它是在HomeFragment上, 我们在 MainActivity 里设置的NavController,所以就能找到
            NavController navController = Navigation.findNavController(v);
            // navigate-->导航:从 homeFragment --> detailFragment
            navController.navigate(R.id.action_homeFragment_to_detailFragment);
        });
    }
}

步骤6:DetailFragment.java。点击 Button 跳转回来

 package com.example.navigation;

import android.os.Bundle;

import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.navigation.NavController;
import androidx.navigation.Navigation;
import androidx.navigation.ui.NavigationUI;

import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;

 public class DetailFragment extends Fragment {

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_detail, container, false);
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        Button btn1 = getView().findViewById(R.id.button2);
        btn1.setOnClickListener(v -> {
            NavController navController = Navigation.findNavController(v);
            navController.navigate(R.id.action_detailFragment_to_homeFragment);
        });
    }
}

添加页面切换动画效果 

添加完后,可以看到 xml 布局里也添加了响应的代码。当然也可以自己设置动画效果 

 

普通方式与 safe args  插件方式参数传递

1. 普通方式,通过 Bundle 实现。

在 HomeFragment 里传递参数到 DetailFragment。

 在 DetailFragment 里接收参数,并打印

 查看结果

 2. safe args  插件方式。普通方式存在一些问题,所以就需要更安全的方式来完成。

首先给整个工程添加插件

根目录的 build.gradle 里添加如下代码

buildscript {
    repositories {
        google()
    }
    dependencies {
        def nav_version = "2.4.2"
        classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
    }
}

 在 app 的 build.gradle 里添加对插件的引用

id 'androidx.navigation.safeargs'

 在 my_nav_graph 里添加要传递的参数。如果是 HomeFragment 传参到 DetailFragment ,那么就在 action 为 "action_homeFragment_to_detailFragment" 里设置参数。同理,如果是由 DetailFragment 传参到 HomeFragment

在 HomeFragment 里通过 HomeFragmentArgs 来完成参数的传递

DetailFragment 里获取参数

 查看打印结果

NavigationUI 作用

Fragment 的切换,除了 Fragment 页面本身的切换,通常还伴有 App bar 的变化。为了方便统一管理,Navigation 组件引入了 NavigationUI 类。

深层链接 DeepLink

方式1:PendingIntent

\bullet 当 App 受到某个通知推送,我们希望用户在点击该通知时,能够直接跳转到展示该通知内容的页面,可以通过 PendingIntent 来完成。

应用实现

布局和前面的案例是一样的,也可以根据后面提供的完整 demo(navigation3 module 里) 进行查看。

HomeFragment.java 里点击按钮---> 发送通知 

 sendNotification() 

private void sendNotification() {
        // 通知渠道,当是用一个应用的通知,会折叠到一起 (O == 26)
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            // 定义一个 channel
            NotificationChannel channel = new NotificationChannel(getActivity().getPackageName(),
                    "MyChannel", NotificationManager.IMPORTANCE_DEFAULT);
            channel.setDescription("My NotificationChannel");
            //TODO NotificationManager 来创建我们的 NotificationChannel
            NotificationManager notificationManager = getActivity().getSystemService(NotificationManager.class);
            notificationManager.createNotificationChannel(channel);
        }

        // 创建一个通知
        Notification notification = null;
        notification = new NotificationCompat.Builder(getActivity(), getActivity().getPackageName())
                .setSmallIcon(R.drawable.ic_launcher_background)
                .setContentTitle("Deep Link")
                .setContentText("点击我试试...")
                .setPriority(NotificationCompat.PRIORITY_DEFAULT) // 设置优先级
                .setContentIntent(getPendingIntent()) //TODO 使用 PendingIntent
                .build();


        // 发送通知
        NotificationManagerCompat notificationManagerCompat = NotificationManagerCompat.from(getActivity());
        notificationManagerCompat.notify(notificationId++, notification);

    }

getPendingIntent() 方法会返回一个 PendingIntent 对象

 /**
     * 返回一个 PendingIntent
     */
    private PendingIntent getPendingIntent() {

        return Navigation.findNavController(requireActivity(), R.id.button)
                .createDeepLink()
                .setGraph(R.navigation.my_nav_graph)
                .setDestination(R.id.detailFragment)
                .createTaskStackBuilder().getPendingIntent(0, PendingIntent.FLAG_IMMUTABLE);

    }

方式2:URL 方式

\bullet 当用户通过手机浏览器浏览网站上某个页面时,可以在网页上放置一个类似于“在应用内打开”的按钮,如果用户的手机安装有我们的 App,那么通过 DeepLink 就能打开相应的页面;如果没有安装,那么网站可以导航到应用程序的下载页面,引导用户安装应用程序。

完整 Demo

提供上述两个应用的完整代码

链接:https://pan.baidu.com/s/1JLres5nSXqBZkW0IuClbbA 
提取码:4opc

猜你喜欢

转载自blog.csdn.net/qq_44950283/article/details/129944797