【译】Android Jetpack 架构导航组件之Navigation

示例代码:android-navigation
介绍:Navigation Codelab
原文:navigation-implementing

导航架构组件简化了App内不同目标的导航实现。一个目标即是App内一个特定的屏幕。导航架构组件默认包含了对fragments和Activities的支持,但是你也可以添加对新类型目标的支持。一系列的目标集合组成了app的导航图。

另外,导航图内不同目标的连接被称作"actions"。图1展示了示例app内一个假设的导航图表现,包含了6个目标,由5个actions连接。

图1 navigation-graph.png

导航架构组件是基于Principles of nagivation实现的。

Set up nagivation in a project

在你创建一个导航图之前,你必须在你的工程内配置导航架构组件。以下是具体步骤:

  1. 在你的app或者模块的build.gradle文件内天井Navigation Architecture Component。点击Adding components to your project查看更多。
  2. 在工程窗口,右击 res 目录,选择 New > Android resource file。显示New Resource对话框。
  3. 输入资源名称在File name栏目,比如“nav_graph”。
  4. 选择NavigationResource type下拉列表。
  5. 点击 OK。出现以下项目:
    a. 一个 nagivation 资源目录在res目录下创建。
    b. 一个nav_graph.xml文件在navigation目录下创建。
    c. nav_graph.xml文件将在Navigation Editor中打开。这个文件包含了你的导航图。
  6. 点击Texttab可以打开或者关闭text View。空导航图如下:

<?xml version="1.0" encoding="utf-8"?><navigation xmlns:android="http://schemas.android.com/apk/res/android"></navigation>

  1. 点击 Design返回导航编辑器。

Tour The Navigation Editor

Note: The Navigation Editor是在Android Studio的Canary Build中默认开启的。为了在Beta,Release Candidate和Stable Builds 中启用,在Mac上点击File > Settings(Android Studio > Preferences),在左侧面板选择Experimentalcategory,选择Enable Navigation Editor,最后重启Android Studio。

图2 navigation-editor.png

在导航编辑器,你可以快速地构建导航图,不需要手动构建graph’s xml文件。按照图2显示的,导航编辑器分为三个部分。
1. 目标列表-罗列当前导航编辑器内所有目标。
2. 图编辑器-包含导航图内所有虚拟目标。
3. 属性编辑器-包含目标属性和导航图内的actions。

Identify destinations

为你的app验证目标是创建导航图的第一部。你可以创建一个空目标或者在一个已经存在的工程内为fragments和Acitvities创建目标。

Note:导航架构组件是为一个activity内包含多个fragment的场景而设计的。主activity拥有导航图。在一个拥有众多activity目标的app内,每一个附加的activity都拥有自己的导航图。修改一个activity到主导航将在之后的文档进行讨论。

请参照一下步骤验证app内目标:

  1. 在图编辑器,点击** New DestinationNew Destination** 显示。
  2. 点击Create blank destination或者点击一个fragment或者activity。会出现新的Android Component 对话框。
  3. Fragment Name字段输入名字。这个名字是fragment的类的名字。
  4. Fragment layout Name字段输入一个名字。这个名字是fragment布局文件的名字。
  5. 点击完成,在图标编辑器的目标列表出现一个目标,出现以下现象:
  • 如果你创建一个空白的目标,在图标编辑器出现一个空白的一个“Hello blank fragment” 的信息的目标,如果你点击了一个Fragment或者Activity,图标编辑器会显示对应的布局。
  • 一个fragment子类将会被创建,名字在第三步被指定。
  • 一个资源文件会被创建,名字在第四步被指定。

图3 展示了一个空白的已经存在的目标。


图3 navigation-newexisting.png
  1. 点击高亮显示新插入的目标。在属性面板显示以下属性。
  • 在类型字段显示“fragment”或者“activity”,表示目标是否执行了fragment或者activity。
  • 标签字段表示了目标布局文件的名字;
  • ID字段表示了目标的ID,用来在代码中被引用;
  • 类字段表示了引用的目标的类名;
  1. 点击Text 进入xml视图,XML现在包含id,name(class name),label和layout属性,基于已经存在的类和布局文件的名字。
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:android="http://schemas.android.com/apk/res/android"
    app:startDestination="@id/blankFragment">
    <fragment
        android:id="@+id/blankFragment"
        android:name="com.example.cashdog.cashdog.BlankFragment"
        android:label="Blank"
        tools:layout="@layout/fragment_blank" />
</navigation>

Note:XML布局startDestination 表示一个空目标的Id,(app:startDestination="@+id/fragment")。查询更多关于startDestination信息请点击 Designate a screen as the start destination

Connect destinations

<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:android="http://schemas.android.com/apk/res/android"
    app:startDestination="@id/blankFragment">
    <fragment
        android:id="@+id/blankFragment"
        android:name="com.example.cashdog.cashdog.BlankFragment"
        android:label="fragment_blank"
        tools:layout="@layout/fragment_blank" />
    <fragment
        android:id="@+id/blankFragment2"
        android:name="com.example.cashdog.cashdog.BlankFragment2"
        android:label="Blank2"
        tools:layout="@layout/fragment_blank_fragment2" />
</navigation>

通过action连接不同的destination:
1、在Graph Editor,当鼠标浮动在destination的右边缘时会显示一个圈;
2、点击并且拖动圆圈到另一个destination,一条线将会关联起两个destination;


图5navigation-connected.png

3、点击高亮箭头,属性面板显示以下属性:

  • 类型字段包含“Action”;
  • ID字段表示了系统为action指定的ID;
  • Destination字段表示了目标Activity或者Fragment;
    4、代码展示:
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:android="http://schemas.android.com/apk/res/android"
    app:startDestination="@id/blankFragment">
    <fragment
        android:id="@+id/blankFragment"
        android:name="com.example.cashdog.cashdog.BlankFragment"
        android:label="fragment_blank"
        tools:layout="@layout/fragment_blank" >
        <action
            android:id="@+id/action_blankFragment_to_blankFragment2"
            app:destination="@id/blankFragment2" />
    </fragment>
    <fragment
        android:id="@+id/blankFragment2"
        android:name="com.example.cashdog.cashdog.BlankFragment2"
        android:label="fragment_blank_fragment2"
        tools:layout="@layout/fragment_blank_fragment2" />
</navigation>

Designate a screen as the start destination

Graph Editor中,进入app的第一个Destination会有一个house的icon。通过一下步骤可以将另外一个Destination指定为启动Destination。
1、点击高亮显示某个Destination;
2、点击属性面板的Set Start Destination即可完成设置;

Modify an activity to host navigation

一个Activity通过NavHost的实现类来持有app的导航。NavHost是一个空的View,因此Destinations会根据用户的导航被交换进或者退出。
Navigation Architecture Component 的默认的NavHost的实现是NavHostFragment。
在你包含了NavHost之后,你必须使用navGraph属性将NavHostFragment和Navigation graph关联起来。下面是代码示例:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.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:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/my_nav_host_fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        app:navGraph="@navigation/nav_graph"
        app:defaultNavHost="true"
        />

</android.support.constraint.ConstraintLayout>

上述例子的app:defaultNavHost="true"属性可以确保NavHostFragment可以拦截系统的回退事件。你也可以重写AppCompatActivity.onSupportNavigateUp()方法并且调用NavController.navigateUp方法:

@Override
public boolean onSupportNavigateUp() {
    return Navigation.findNavController(this, R.id.nav_host_fragment).navigateUp();
}

Create the NavHostFragment programmatically

你可以使用NavHostFragment.create()
编码创建NavHostFragment ,关联一个graph资源,如下所示:

NavHostFragment finalHost = NavHostFragment.create(R.navigation.example_graph);
getSupportFragmentManager().beginTransaction()
    .replace(R.id.nav_host, finalHost)
    .setPrimaryNavigationFragment(finalHost) // this is the equivalent to app:defaultNavHost="true"
    .commit();

Tie destinations to UI widgets

通过NavController
实现对Destination的导航。通过以下静态方法获取NavController示例:

viewTransactionsButton.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        Navigation.findNavController(view).navigate(R.id.viewTransactionsAction);
    }
});

Android 系统维护了一个back stack来表示最近访问的destination。
在app启动得时候,第一个destination会被放入栈里面。每一次调用navigate()
都会放入另外一个destination在栈的顶部。相反的,调用NavController.navigateUp()
NavController.popBackStack()
方法,从栈里弹出顶部的destination。对于按钮,你可以使用Navigation
类的 createNavigateOnClickListener() 方法来很方便地去导航到destination。

button.setOnClickListener(Navigation.createNavigateOnClickListener(R.id.next_fragment, null));

Tie destinations to menu-driven UI components

通过为destination、navigation drawer和overflow menu设置相同的Id来将destination和navigation drawer或者overflow menu关联起来。以下代码片段显示了id为details_page_fragment的destination:

<fragment android:id="@+id/details_page_fragment"
     android:label="@string/details"
     android:name="com.example.android.myapp.DetailsFragment" />

以下代码片段表示了如何关联fragment destination 和navigation drawer中的menu item,比如( menu_nav_drawer.xml)。

<item
   android:id="@id/details_page_fragment"
   android:icon="@drawable/ic_details"
   android:title="@string/details" />

以下XML表示了如果把destination和overflow menu 关联的细节:

<item
    android:id="@id/details_page_fragment"
    android:icon="@drawable/ic_details"
    android:title="@string/details"
    android:menuCategory:"secondary" />

Navigation Architecture Component 包含了一个NavigationUI
类。这个类的多个静态方法都可以用来关联 menu item 和 navigation destination。以下代码表示了如果使用setupWithNavController()
方法去关联 menu item 到Navigation View。

NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view);
NavigationUI.setupWithNavController(navigationView, navController);

使用NavigationUI
去设置menu 驱动的导航组件是很有必要的,因为这样可以同步这些UI元素地改变到NavController。

Pass data between destinations

两种方式在不同的destination之间传递数据:使用Bundle
对象或者通过Gradle插件类型安全得传递数据。遵照以下步骤在destinations之间使用Bundle对象传递数据。如果你想使用Gradle,请遵照Pass data between destinations in a type-safe way
的介绍。
1、在Graph Editor,点击接受参数的destination。该destination会高亮。
2、在属性面板的参数部分点击 Add(+),会显示空名字和默认值字段。
3、在名字字段双击输入参数名字。
4、按压Tab,输入参数的默认值。
5、点击上述destination的action。参数的默认值应该包含你新增的参数。
6、点击Text去切换XML View。一个参数元素,包含名字和默认值属性,已经被添加到该destination。

<fragment
  android:id="@+id/confirmationFragment"
  android:name="com.example.buybuddy.buybuddy.ConfirmationFragment"
  android:label="fragment_confirmation"
  tools:layout="@layout/fragment_confirmation">
  <argument android:name="amount" android:defaultValue="1" app:type="integer"/>
</fragment>

当你使用safeargs plugin,会为action,destinations的接受者和发送者创建简单对象和构造类。这些类是:

  • 一个类为action源起的destination,后缀“Directions”。因此,如果起始的fragment叫做SpecifyAmountFragment,那么生成的类就叫做SpecifyAmountFragmentDirections。这个类有一个以传递参数的action来命名,去绑定参数,比如confirmationAction()。
  • 一个匿名内部类,名字基于传递参数的action。如果传递的action叫做confirmationAction,类就被命名为ConfirmationAciton。
  • 一个类用于接受destination,后缀Args,所以,如果destination fragment叫做ComfirmationFragment,生成的类就叫做ConfirmationFragmentArgs。使用这个类的fromBundle()方法获取参数。
    示例代码:
@Override
public void onClick(View view) {
  EditText amountTv = (EditText) getView().findViewById(R.id.editTextAmount);
  int amount = Integer.parseInt(amountTv.getText().toString());
  ConfirmationAction action =
          SpecifyAmountFragmentDirections.confirmationAction()
  action.setAmount(amount)
  Navigation.findNavController(view).navigate(action);
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
    TextView tv = view.findViewById(R.id.textViewAmount);
    int amount = ConfirmationFragmentArgs.fromBundle(getArguments()).getAmount();
    tv.setText(amount + "")
}

Group destinations into a nested navigation graph

在navigation graph内,可以把一些相关的destination是分组为sub-graph,如果包含sub-graph的是root-graph,那么这个sub-graph被称作“nestedgraph”,Nestedgraph在组织和重用App UI的模块上是很有用的,比如登录流程。
和root graph一样,nested graph必须也要有一个启动的destination作为唯一的标识。nested graph封装了它的destinations。nested graph外部,root graph之内的destination只能通过root graph的启动destination访问nested graph。图6 展示了一个simple money transfer app的navigation graph。这个graph有两个流程:转钱流程和余额流程。

图6 navigation-pre-nestedgraph.png

把destinations分组为nested graph:
1、在graph editor,按住shift点击需要加入nested graph的destination,该destination高亮。
2、打开菜单选择Move to Nested Graph > New Graph,如图7.
图7navigation-nestedgraph.png

3、点击高亮nested graph,属性面板显示如下:

  • Type字段显示“Nested Graph”。
  • Id字段是系统为nested graph指定的。Id用来在代码中引用这个nested graph。
    4、双击nested graph查看内部的destinations。
    5、在destinations列表,点击root返回root graph。
    6、点击Text进入XML视图。
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:app="http://schemas.android.com/apk/res-auto"
   xmlns:tools="http://schemas.android.com/tools"
   xmlns:android="http://schemas.android.com/apk/res/android"
   app:startDestination="@id/mainFragment">
   <fragment
       android:id="@+id/mainFragment"
       android:name="com.example.cashdog.cashdog.MainFragment"
       android:label="fragment_main"
       tools:layout="@layout/fragment_main" >
       <action
           android:id="@+id/action_mainFragment_to_chooseRecipient"
           app:destination="@id/sendMoneyGraph" />
       <action
           android:id="@+id/action_mainFragment_to_viewBalanceFragment"
           app:destination="@id/viewBalanceFragment" />
   </fragment>
   <fragment
       android:id="@+id/viewBalanceFragment"
       android:name="com.example.cashdog.cashdog.ViewBalanceFragment"
       android:label="fragment_view_balance"
       tools:layout="@layout/fragment_view_balance" />
   <navigation android:id="@+id/sendMoneyGraph" app:startDestination="@id/chooseRecipient">
       <fragment
           android:id="@+id/chooseRecipient"
           android:name="com.example.cashdog.cashdog.ChooseRecipient"
           android:label="fragment_choose_recipient"
           tools:layout="@layout/fragment_choose_recipient">
           <action
               android:id="@+id/action_chooseRecipient_to_chooseAmountFragment"
               app:destination="@id/chooseAmountFragment" />
       </fragment>
       <fragment
           android:id="@+id/chooseAmountFragment"
           android:name="com.example.cashdog.cashdog.ChooseAmountFragment"
           android:label="fragment_choose_amount"
           tools:layout="@layout/fragment_choose_amount" />
   </navigation>
</navigation>

7、在你的代码里,使用action id来关联root graph 和 nested graph。

Navigation.findNavController(view).navigate(R.id.action_mainFragment_to_sendMoneyGraph);

Reference other navigation graphs using <include>

在 navigation graph内,可以通过<include>引用其它navigation graph,其等价于使用nested graph。以此使用其它module或者library中的navigation。

<include app:graph="@navigation/included_graph"/>

<include>只有一个app:graph属性,且不允许其它任何属性。

Create a deep link for a destination

Add an intent filter for a deep link

Create a transition between destinations

AndroidX命名空间迁移
Verify Android App Links (Deep Links和Android App Links的区别)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容