前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android Jetpack架构组件(五)之Navigation

Android Jetpack架构组件(五)之Navigation

原创
作者头像
xiangzhihong
修改2020-12-22 10:14:46
1.2K0
修改2020-12-22 10:14:46
举报
文章被收录于专栏:向治洪向治洪

一、 Navigation简介

1.1 Navigation诞生背景

采用单个Activity嵌套多个Fragment的UI架构模式,已经被大多数的Android工程师所接受。但是,对于Fragment的管理一直是一件比较麻烦的事情,工程师需要通过FragmentManager和FragmentTransaction来管理Fragment之间的切换。

在Android中,页面的切换和管理包括应用程序Appbar的管理、Fragment的动画切换以及Fragment之间的参数传递等内容。并且,纯代码的方式使用起来不是特别友好,并且Appbar在管理和使用的过程中显得很混乱。因此,Jetpack提供了一个名为Navigation的组件,旨在方便开发者管理Fragment页面和Appbar。

相比之前Fragment的管理需要借助FragmentManager和FragmentTransaction,使用Navigation组件有如下一些优点:

  • 可视化的页面导航图,方便我们理清页面之间的关系
  • 通过destination和action完成页面间的导航
  • 方便添加页面切换动画
  • 页面间类型安全的参数传递
  • 通过Navigation UI类,对菜单/底部导航/抽屉蓝菜单导航进行统一的管理
  • 支持深层链接DeepLink

1.2 Navigation元素

在正式学习Navigation组件之前,我们需要对Navigation的主要元素有一个简单的了解,Navigation主要由三部分组成。

  • Navigation Graph:一个包含所有导航和页面关系相关的 XML资源。
  • NavHostFragment:一种特殊的Fragment,用于承载导航内容的容器。
  • NavController:管理应用导航的对象,实现Fragment之间的跳转等操作。

二、Navigation使用

2.1 添加依赖

首先,新建一个Android项目,然后在build.gradle中引入如下依赖脚本。

代码语言:txt
复制
dependencies {

  def nav\_version = "2.3.2"

  // Java language implementation

  implementation "androidx.navigation:navigation-fragment:$nav\_version"

  implementation "androidx.navigation:navigation-ui:$nav\_version"



  // Kotlin

  implementation "androidx.navigation:navigation-fragment-ktx:$nav\_version"

  implementation "androidx.navigation:navigation-ui-ktx:$nav\_version"

}

2.2 创建导航图

在【Project】窗口中,res 目录下右键然后依次选择 【New】->【Android Resource File】创建 New Resource File 对话框,如下图所示。

在这里插入图片描述
在这里插入图片描述

在弹出的界面中,File name可随意输入,Resource type选择Navigation,然后点击确定即可。

在这里插入图片描述
在这里插入图片描述

点击确定后,就会在res目录下创建navigation目录,以及导航文件nav_graph.xml。

2.3 Navigation graph

打开新建的nav_graph.xml文件,在Design界面可以看到目前还没有内容,可以【依次点击 New Destination】 图标,然后点击【 Create new destination】,即可快速创建新的Fragment,这里分别新建了FragmentA、FragmentB、FragmentC三个fragment。

在这里插入图片描述
在这里插入图片描述

然后,可通过手动配置页面之间的跳转关系,点击某个页面,右边会出现一个小圆点,拖曳小圆点指向跳转的页面,比如设置跳转的关系为FragmentA -> FragmentB -> FragmentC。

在这里插入图片描述
在这里插入图片描述

然后,我们再切换到Code面板,可以看到生成的代码如下所示。

代码语言:txt
复制
<?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/fragmentA">



    <fragment

        android:id="@+id/fragmentA"

        android:name="com.example.testnavigation.FragmentA"

        android:label="fragment\_a"

        tools:layout="@layout/fragment\_a" >

        <action

            android:id="@+id/action\_fragmentA\_to\_fragmentB"

            app:destination="@id/fragmentB" />

    </fragment>

    <fragment

        android:id="@+id/fragmentB"

        android:name="com.example.testnavigation.FragmentB"

        android:label="fragment\_b"

        tools:layout="@layout/fragment\_b" >

        <action

            android:id="@+id/action\_fragmentB\_to\_fragmentC"

            app:destination="@id/fragmentC" />

    </fragment>

    <fragment

        android:id="@+id/fragmentC"

        android:name="com.example.testnavigation.FragmentC"

        android:label="fragment\_c"

        tools:layout="@layout/fragment\_c" />

</navigation>

上面的生成的代码中用到了几个标签,含义如下。

  • navigation:根标签,通过startDestination配置指定默认的启动页面。
  • fragment: fragment标签代表一个fragment视图。
  • action:action标签定义了页面跳转的行为,destination标签定义跳转的目标页,跳转时还可以定义跳转动画。

2.4 NavHostFragment

我们知道,Fragment需要有一个Activity容器才能正常运行,NavHostFragment就是承载导航内容的容器,并且它需要和Activity绑定。

代码语言:txt
复制
<?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/fragment"

        android:name="androidx.navigation.fragment.NavHostFragment"

        android:layout\_width="match\_parent"

        android:layout\_height="match\_parent"

        app:defaultNavHost="true"

        app:layout\_constraintBottom\_toBottomOf="parent"

        app:layout\_constraintLeft\_toLeftOf="parent"

        app:layout\_constraintRight\_toRightOf="parent"

        app:layout\_constraintTop\_toTopOf="parent"

        app:navGraph="@navigation/nav\_graph" />



</androidx.constraintlayout.widget.ConstraintLayout>

其中, android:name="androidx.navigation.fragment.NavHostFragment"这行代码的作用就是告诉Android系统这是一个特殊的Fragment。并且当app:defaultNavHost="true"属性为true时,该Fragment会自动处理系统返回。

  • android:name指定NavHostFragment
  • app:navGraph指定导航视图,即建好的nav_graph.xml
  • app:defaultNavHost=true,可以拦截系统的返回键,可以理解为默认给fragment实现了返回键的功能。

然后我们运行程序,可以看到默认展示的是FragmentA页面,这是因为MainActivity的布局文件中配置了NavHostFragment,并且给NavHostFragment指定了默认展示的页面为FragmentA。

2.5 NavControlle

NavController主要用来管理fragment之间的跳转,每个 NavHost 均有自己的相应 NavController。可以通过findNavController来获取NavController,然后使用NavController的navigate或者navigateUp方法来进行页面之间的路由操作。

打开FragmentA.java文件,然后在onViewCreated生命周期方法中添加如下代码。

代码语言:txt
复制
public View onCreateView(LayoutInflater inflater, ViewGroup container,

                             Bundle savedInstanceState) {

        View view = inflater.inflate(R.layout.fragment\_a, container, false);

        Button btnB = view.findViewById(R.id.btn\_b);

        btnB.setOnClickListener(new View.OnClickListener() {

            @Override

            public void onClick(View v) {

                Navigation.findNavController(v).navigate(R.id.action\_fragmentA\_to\_fragmentB);

            }

        });

        return view;

    }

运行上面的代码,然后点击FragmentA页面上的按钮,系统就会通过Navigation.findNavController(v).navigate()方法导航到FragmentB。

2.6 添加动画

在Fragment之间进行跳转时,还可以添加跳转的动画。打开nav_graph.xml文件的Design选项,然后在Attributes 面板的 Animations 部分中,点击要添加的动画旁边的下拉箭头,开发者可以从以下类型中进行选择,如下图所示。

在这里插入图片描述
在这里插入图片描述

其中,Attributes面板涉及的属性含义如下。

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

然后,打开Code面板,生成的代码如下。

代码语言:txt
复制
<fragment

        android:id="@+id/fragmentA"

        android:name="com.xzh.jetpack.FragmentA"

        android:label="fragment\_a"

        tools:layout="@layout/fragment\_a" >

        <action

            android:id="@+id/action\_fragmentA\_to\_fragmentB"

            app:destination="@id/fragmentB"

            app:enterAnim="@android:anim/slide\_in\_left"

            app:exitAnim="@android:anim/slide\_out\_right"

            app:popEnterAnim="@anim/fragment\_fade\_enter"

            app:popExitAnim="@anim/fragment\_fade\_exit" />

    </fragment>

三、参数传递

在Android中,页面之间如果要传递数据,建议传递最少量的数据,因为在 Android 上用于保存所有状态的总空间是有限的。如果您需要传递大量数据,可以使用 ViewModel。

Fragment的切换经常伴随着参数的传递,为了配合Navigation组件在切换Fragment时传递参数,Android Studio为开发者提供了Safe Args和Bundle两种参数传递方式。

3.1 使用Bundle传递数据

使用Bundle传递数据时,首先创建 Bundle 对象,然后使用 navigate() 将它传递给目的地,如下所示。

代码语言:txt
复制
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {

    super.onViewCreated(view, savedInstanceState)

    tv.setOnClickListener {

         Bundle bundle = new Bundle();

         bundle.putString("key", "from fragmentA");

         Navigation.findNavController(v).navigate(R.id.action\_fragmentA\_to\_fragmentB,bundle);

    }

}

然后,接受方使用getArguments() 方法来获取Bundle 传递的数据,如下所示。

代码语言:txt
复制
String keyStr = getArguments().getString("key");

3.2 使用 Safe Args传递数据

首先,在项目的 build.gradle 中添加classpath配置,如下所示。

代码语言:txt
复制
dependencies {

    def nav\_version = "2.3.2"

    classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav\_version"

}

然后,在app的 build.gradle添加 apply plugin脚本,如下所示。

代码语言:txt
复制
apply plugin: 'androidx.navigation.safeargs'

配置完成后记重新rebuild下项目,会生成{module}/build/generated/source/navigation-args/{debug}/{packaged}/{Fragment}Dircetions,如下所示。

在这里插入图片描述
在这里插入图片描述

如果需要往目的页面传递数据,首先请按照以下步骤将参数添加到接收它的目的页面中。Navigation提供了一个子标签argument可以用来传递参数。

首先,在 Navigation Editor 中,点击接收参数的目的页面,在 Attributes 面板中,点击 Add (+)。然后,在显示的 Add Argument Link 窗口中,输入参数名称、参数类型、参数是否可为 null,以及默认值(如果需要)点击 【Add】按钮,如下所示。

在这里插入图片描述
在这里插入图片描述

点击 Text 标签页切换到 XML 视图,会发现生成的代码如下。

代码语言:txt
复制
<argument

     android:name="key"

     app:argType="string"

     app:nullable="false"

     android:defaultValue="navigation参数传递" />

然后,我们在FragmentA.java中使用如下代码传递数据,如下所示。

代码语言:txt
复制
@Override

    public View onCreateView(LayoutInflater inflater, ViewGroup container,

                             Bundle savedInstanceState) {

        View view = inflater.inflate(R.layout.fragment\_a, container, false);

        Button btnB = view.findViewById(R.id.btn\_b);

        btnB.setOnClickListener(new View.OnClickListener() {

            @Override

            public void onClick(View v) {

              FragmentADirections.ActionFragmentAToFragmentB action=FragmentADirections.actionFragmentAToFragmentB().setKey("通过safeArgs进行参数传递");

              Navigation.findNavController(v).navigate(action);

            }

        });

        return view;

    }

在接受的页面的onCreate方法中使用如下的方法进行接受。

代码语言:txt
复制
public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        if (getArguments() != null) {

            String name=  FragmentBArgs.fromBundle(getArguments()).getKey();

        }

    }

四、深层链接DeepLink

当应用程序接受到某个通知推送,希望用户在点击该通知时,能够直接跳转到展示该通知内容的页面,这就是深层链接DeepLink最常见的场景,Navigation组件提供了对深层链接(DeepLink)的支持。

DeepLink有两种应用场景,一种是PendingIntent,另一种是真实的URL链接,利用这两种方式都可以跳转到程序中指定的页面。

4.1 PendingIntent

PendingIntent方式一般用在消息通知中,当应用程序接收到某个通知时,并且希望用户在单击该通知时直接跳转到到指定的页面,那么就可以通过PendingIntent来完成。

例如,下面的代码实现功能是,在MainActivity中单击按钮弹出通知栏,点击通知栏跳转到指定NotificationActivity页面中,代码如下。

代码语言:txt
复制
public class MainActivity extends AppCompatActivity {



    NotificationManager manager=null;



    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity\_main);

        init();

    }



    private void init() {

        manager = (NotificationManager) getSystemService(Context.NOTIFICATION\_SERVICE);

        if (Build.VERSION.SDK\_INT>=Build.VERSION\_CODES.O){

            //注意通知渠道一旦建立就无权修改

            NotificationChannel channel =new NotificationChannel("normal","Normal",NotificationManager.IMPORTANCE\_DEFAULT);

            manager.createNotificationChannel(channel);

            NotificationChannel channel2 =new NotificationChannel("important","Important",NotificationManager.IMPORTANCE\_HIGH);

            manager.createNotificationChannel(channel2);

        }



        Button pending=findViewById(R.id.btn\_pendingintent);

        pending.setOnClickListener(new View.OnClickListener() {

            @Override

            public void onClick(View v) {

                sendNotification();

            }

        });

    }



    void sendNotification(){

        Intent intent =new Intent(this,NotificationActivity.class);

        PendingIntent pi = PendingIntent.getActivity(this,0,intent,0);

        //使用NotificationCompat兼容8.0以前的系统

//        val notification = NotificationCompat.Builder(this,"normal")

        Notification notification = new NotificationCompat.Builder(this,"important")

                .setContentTitle("This is content title")

                .setContentText("This is content text")

                .setStyle(new NotificationCompat.BigTextStyle().bigText("Learn how to build notification,send any sync data,and use voice actions.Get the " +

                        "official Android IDE and developer tools to build apps for Android."))

                .setStyle(new NotificationCompat.BigPictureStyle().bigPicture(BitmapFactory.decodeResource(getResources(),R.drawable.ic\_launcher\_background)))

                .setSmallIcon(R.drawable.ic\_launcher\_foreground)

                .setLargeIcon(BitmapFactory.decodeResource(getResources(),R.drawable.ic\_launcher\_background))

                .setContentIntent(pi)

                .setAutoCancel(true)

                .build();

        manager.notify(1,notification);

    }

}

然后,需要新建一个通知的目的页面,为了MainActivity做区分,我们在NotificationActivity页面仅放了一个TextView组件,如下所示。

代码语言:txt
复制
public class NotificationActivity extends AppCompatActivity {



    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity\_notification);

    }

}

然后运行上面的代码,最终的效果如下图所示。

在这里插入图片描述
在这里插入图片描述

4.2 URL

使用URL链接方式,当用户通过手机浏览器浏览网站上的某个页面时,可以通过网页浏览器的方式打开对应的应用页面。如果用户的手机安装有我们得应用程序,那么通过DeepLink就能打开相应的页面;如果没有安装,那么网站可以导航到应用程序的下载页面,从而引导用户安装应用程序。

首先,在导航图中为destination添加<deepLink/>标签,在app:uri属性中填入的是你的网站的相应web页面地址,如下所示。

代码语言:txt
复制
<fragment

    android:id="@+id/deepLinkSettingsFragment"

    android:name="com.michael.deeplinkdemo.DeepLinkSettingsFragment"

    android:label="fragment\_deep\_link\_settings"

    tools:layout="@layout/fragment\_deep\_link\_settings">



    <!-- 为destination添加<deepLink/>标签 -->

    <deepLink app:uri="www.YourWebsite.com/{params}" />



</fragment>

如上所示,app:uri里面填的是网站的相应Web页面地址,{params}里面的参数会通过Bundle对象传递到页面中。

然后,为相应的Activity设置<nav-graph/>标签,当用户在Web中访问到链接时,你的应用程序便能监听到,如下所示。

代码语言:txt
复制
<application

    android:allowBackup="true"

    android:icon="@mipmap/ic\_launcher"

    android:label="@string/app\_name"

    android:roundIcon="@mipmap/ic\_launcher\_round"

    android:supportsRtl="true"

    android:theme="@style/AppTheme">

    <activity android:name=".DeepLinkActivity">

        <intent-filter>

            <action android:name="android.intent.action.VIEW" />

            <action android:name="android.intent.action.MAIN"/>

            <category android:name="android.intent.category.LAUNCHER"/>

        </intent-filter>



        <!-- 为Activity设置<nav-graph/>标签 -->

       <nav-graph android:value="@navigation/nav\_graph"/>

    </activity>



</application>

经过上面的设置后,接下来就可以测试我们的功能了。我们可以在Google app中输入相应的Web地址,也可以通过adb工具,使用命令行来完成测试操作。

代码语言:txt
复制
adb shell am start -a android.intent.action.VIEW -d "http://www.YourWebsite.com/fromWeb"

执行完命令,手机便能直接打开deepLinkSettingsFragment。在该Fragment中,我们可以通过Bundle对象获取相应的参数(fromWeb),从而完成后续的操作。

代码语言:txt
复制
@Override

public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){

    View view = inflater.inflate(R.layout.fragment\_deep\_link\_settings, container, false);

    Bundle bundle = getArguments();

    if(bundle != null){

        String params = bundle.getString("params");

        TextView tvDesc = view.findViewById(R.id.tvDesc);

        if(!TextUtils.isEmpty(params)){

            tvDesc.setText(params);

        }

    }

    return view;

}

运行效果如下图所示。

在这里插入图片描述
在这里插入图片描述

参考:

Android Jetpack架构组件(四)之LiveData

Android Jetpack架构组件(三)之ViewModel

Android Jetpack架构组件(二)之Lifecycle

Android Jetpack架构组件(一)与AndroidX

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、 Navigation简介
    • 1.1 Navigation诞生背景
      • 1.2 Navigation元素
      • 二、Navigation使用
        • 2.1 添加依赖
          • 2.2 创建导航图
            • 2.3 Navigation graph
              • 2.4 NavHostFragment
                • 2.5 NavControlle
                  • 2.6 添加动画
                  • 三、参数传递
                    • 3.1 使用Bundle传递数据
                      • 3.2 使用 Safe Args传递数据
                      • 四、深层链接DeepLink
                        • 4.1 PendingIntent
                          • 4.2 URL
                          相关产品与服务
                          容器服务
                          腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
                          领券
                          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
                          http://www.vxiaotou.com