持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第7天,点击查看活动详情
概述
在上一篇笔记中对导航组件有了初步的认识,简单来说,导航组件更多的适用于单Activity多Fragment的场景。通过创建导航图,可以更加简单直观地查看页面的连接和流转。同时,使用NavController对页面的跳转进行控制,可以简化我们对Fragment页面状态的控制,我们只需要重点关注页面如何跳转即可。
本篇学习笔记会在上一篇笔记的基础上,主要学习导航组件的简单使用。
创建目的地
在上一篇笔记中我们已经学习了如何创建一个以Fragment为目标的目的地,具体的创建步骤如下:
- 创建一个
Fragment,这是需要跳转到的目的地 - 在导航资源文件中添加对当前
Fragment的描述信息:(类型为fragment,id指定跳转的时候使用的id,name为fragment的路径和名称,label为fragment的描述信息,layout用于在视图中显示fragment) - 在需要执行跳转的目的地添加
action标签,其中需要指定action自身的id,还需要指定destination,这个属性的值为上一步设置的fragment的id - 在需要指定跳转的地方获取
NavController并调用navigate(actionId)执行跳转。
使用DialogFragment创建目的地
很多时候我们都需要显示一个Dialog来提醒用户做一些操作,一般我们会给用户提供一些按钮,当用户点击这些按钮的时候会跳转到新的页面。我们使用DialogFragment来显示一个提示框,点击其中的按钮跳转到另一个Fragment,对于DialogFragment创建目的地的操作如下:
<dialog
android:id="@+id/show_alert_dialog"
android:name="com.zyf.navstudy.nav_dialog.NavContentDialog"
android:label="@string/nav_label_alert_dialog"
>
<argument
android:name="remindMessage"
android:defaultValue="@null"
app:nullable="true"
/>
<action
android:id="@+id/to_second_fragment"
app:destination="@id/nav_second_fragment"
/>
</dialog>
和之前创建Fragment作为目的地的操作是一样的,只不过这一次我们将type类型设置为了dialog,同时我们添加了argument标签,这个标签用于指定在dialog中可以接收的数据。由于我们这里的Dialog其实是DialogFragment,所以对于数据的传递我们按照普通的Fragment那种数据传递方式即可。
之所以这里要将Dialog单独设置,主要是因为dialog类型的目的地不能加入到回退栈中,比如我们从第一个Fragment中打开了这个Dialog,然后又从这个弹框中跳转到了第二个Fragment,那么在第二个Fragment点击返回键的时候不能重新把弹框显示出来,应该直接回退到第一个Fragment上。下面的代码演示了这个操作:
- 首先我们定义两个
Fragment,然后将这两个Fragment加入到导航资源文件中,如下所示:
<fragment
android:id="@+id/nav_first_fragment"
android:name="com.zyf.navstudy.nav_dialog.NavFirstFragment"
android:label="first_fragment"
tools:layout="@layout/fragment_nav_first"
>
<action
android:id="@+id/show_dialog"
app:destination="@id/show_alert_dialog"
/>
</fragment>
<fragment
android:id="@+id/nav_second_fragment"
android:name="com.zyf.navstudy.nav_dialog.NavSecondFragment"
android:label="second_fragment"
tools:layout="@layout/fragment_nav_second"
>
</fragment>
上面的代码设置完成之后,导航图的显示内容如下:
可以看到,如我们所言,第一个页面弹出一个弹框,然后跳转到第二个页面。
- 在第一个页面中使用
NavController打开弹框,同时向这个弹框传递数据,如下代码所示:
findNavController().navigate(R.id.show_dialog, bundleOf("remindMessage" to "设置的提示信息"))
注意我们设置数据的时候,key一定要和导航文件中设置的key是一样的,否则是无法获取到数据的。
- 弹出提示框之后,我们点击提示框上的按钮跳转到第二个
Fragment,代码如下所示:
findNavController().navigate(R.id.to_second_fragment)
简单执行上面三个操作之后我们就完成之前所说的流程,最终的效果如下图所示:
将Activity作为目的地
有时候我们需要从Fragment跳转到一个Activity,特别是对于维护老的项目来说,新的功能使用单Activity多Fragment实现,但是之前的功能可能仍然是使用多Activity实现,或者我们的应用需要跳转到别的应用的页面去实现一些功能,这个时候就需要将Activity作为目的地来使用。
虽然创建导航图中的activity节点和创建Fragment节点没有什么不同,但是两者的性质是完全不一样的。默认情况下,Navigation库会将NavController附加到Activity布局,并且处于活跃状态的导航图的作用域限定为处于活跃状态的Activity.如果用户导航到其它Activity,则当前的导航图不再位于作用域内,这意味着Activity目的地应被视为导航图中的一个端点。
下面的代码创建了一个Activity作为目的地:
<activity
android:id="@+id/nav_destination_activity"
android:name="com.zyf.navstudy.nav_activity.NavDestinationActivity"
android:label="activity_as_destination"
tools:layout="@layout/activity_nav_destination" />
可以看到,在创建导航图的过程中和创建Fragment作为目的地的过程是一样的。通过上面的方式定义好的Activity在使用NavController进行跳转的时候就相当于执行了startActivity(Intent(context,NavDestinationActivity::class.java))
上面这种是理想情况,但是有时候我们的Activity可能只支持隐式启动,比如我们修改Activity的声明:
<activity
android:name=".nav_activity.NavDestinationActivity"
android:exported="true">
<intent-filter>
<action android:name="com.zyf.project.nav_study.toNavDestanitionActivity" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
可以看到,我们定义的Activity支持通过action隐士启动,对于同一个应用中的Activity,我们知道它的类名当然可以继续使用上面的方式定义导航图,但是如果这个Activity是存在于别的应用中,我们并不知道它的类名,只知道它的action则不能继续使用上面的导航图中的定义方式,而是应该修改为下面的方式:
<activity
android:id="@+id/nav_destination_activity"
app:action="com.zyf.project.nav_study.toNavDestinationActivity"
android:label="activity_as_destination"
tools:layout="@layout/activity_nav_destination"
app:targetPackage="${applicationId}"
/>
在上面的目的地中,我们取消了之前的name属性,转而使用了action属性,这要和我们定义的Activity的action属性完全一致,否则无法打开页面。同时在测试的过程中也发现,在Manifest文件中必须添加<category android:name="android.intent.category.DEFAULT" />,否则即便成功设置了action也无法跳转页面。
同时在上面我们还指定了targetPakcage属性,将它的值设置为${applicationId}也就是我们当前应用的包名,如果我们要跳转的是别的应用,则此处应该设置别的应用的包名,设置包名可以更明确的执行页面的跳转,避免出现多个应用使用相同action的情况。如下所示:
<!--打开另一个应用的activity-->
<activity
android:id="@+id/nav_other_activity"
app:action="com.zyf.other.otherActivity"
android:label="activity_as_destination"
app:targetPackage="com.zyf.otherapplication">
</activity>
指定了上面的目的地之后我们就可以成功执行跳转了,如下图所示:
有时候我们的Activity不仅会设置action,还需要接收一些数据,此时我们可能会设置如下的参数:
<activity
android:name=".nav_activity.NavDestinationActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<data
android:host="nav_study.project.zyf.com"
android:scheme="http" />
<category android:name="android.intent.category.BROWSABLE" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
对于包含参数的配置,我们也需要在导航资源文件中设置对应的参数:
<activity
android:id="@+id/nav_destination_activity"
app:action="android.intent.action.VIEW"
app:data="http://nav_study.project.zyf.com"
android:label="activity_as_destination"
tools:layout="@layout/activity_nav_destination"
app:targetPackage="${applicationId}"
/>
有时候我们可能还需要携带动态参数,比如在网址中携带用户信息,如"nav_study.project.zyf.com?user=",这种情况下我们就不能使用data标签,而是应该使用dataPattern。如下所示:
<activity
android:id="@+id/nav_destination_activity"
app:action="android.intent.action.VIEW"
app:data="http://nav_study.project.zyf.com"
app:dataPattern="http://nav_study.project.zyf.com?user={userId}"
android:label="activity_as_destination"
tools:layout="@layout/activity_nav_destination"
app:targetPackage="${applicationId}">
<argument
android:name="userId"
app:argType="string" />
</activity>
在执行跳转的时候我们可以将用户信息携带上去:
findNavController().navigate(R.id.to_nav_destination_activity, bundleOf(
"userId" to "szyf"
))
这样最终携带的数据就是http://nav_study.project.zyf.com?user=szyf