乐趣区

关于android:Android-Jetpack架构组件五之Navigation

一、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 中引入如下依赖脚本。

    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 面板,能够看到生成的代码如下所示。

    <?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 绑定。

    <?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 NavController

    NavController 次要用来治理 fragment 之间的跳转,每个 NavHost 均有本人的相应 NavController。能够通过 findNavController 来获取 NavController,而后应用 NavController 的 navigate 或者 navigateUp 办法来进行页面之间的路由操作。

    关上 FragmentA.java 文件,而后在 onViewCreated 生命周期办法中增加如下代码。

    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 面板,生成的代码如下。

    <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() 将它传递给目的地,如下所示。

    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 传递的数据,如下所示。

    String keyStr = getArguments().getString("key");

    3.2 应用 Safe Args 传递数据

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

    dependencies {
        def nav_version = "2.3.2"
        classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
    }

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

    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 视图,会发现生成的代码如下。

    <argument
         android:name="key"
         app:argType="string"
         app:nullable="false"
         android:defaultValue="navigation 参数传递" />

    而后,咱们在 FragmentA.java 中应用如下代码传递数据,如下所示。

    @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 办法中应用如下的办法进行承受。

    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 页面中,代码如下。

    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 组件,如下所示。

    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 页面地址,如下所示。

    <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 中拜访到链接时,你的应用程序便能监听到,如下所示。

    <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 工具,应用命令行来实现测试操作。

    adb shell am start -a android.intent.action.VIEW -d "http://www.YourWebsite.com/fromWeb"

    执行完命令,手机便能间接关上 deepLinkSettingsFragment。在该 Fragment 中,咱们能够通过 Bundle 对象获取相应的参数(fromWeb),从而实现后续的操作。

    @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;
    }
    退出移动版