示例代码:android-navigation
介绍:Navigation Codelab
原文:navigation-implementing
导航架构组件简化了App内不同目标的导航实现。一个目标即是App内一个特定的屏幕。导航架构组件默认包含了对fragments和Activities的支持,但是你也可以添加对新类型目标的支持。一系列的目标集合组成了app的导航图。
另外,导航图内不同目标的连接被称作"actions"。图1展示了示例app内一个假设的导航图表现,包含了6个目标,由5个actions连接。
导航架构组件是基于Principles of nagivation实现的。
Set up nagivation in a project
在你创建一个导航图之前,你必须在你的工程内配置导航架构组件。以下是具体步骤:
- 在你的app或者模块的build.gradle文件内天井Navigation Architecture Component。点击Adding components to your project查看更多。
- 在工程窗口,右击 res 目录,选择 New > Android resource file。显示New Resource对话框。
- 输入资源名称在File name栏目,比如“nav_graph”。
- 选择Navigation在Resource type下拉列表。
- 点击 OK。出现以下项目:
a. 一个 nagivation 资源目录在res目录下创建。
b. 一个nav_graph.xml文件在navigation目录下创建。
c. nav_graph.xml文件将在Navigation Editor中打开。这个文件包含了你的导航图。 - 点击Texttab可以打开或者关闭text View。空导航图如下:
<?xml version="1.0" encoding="utf-8"?><navigation xmlns:android="http://schemas.android.com/apk/res/android"></navigation>
- 点击 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。
在导航编辑器,你可以快速地构建导航图,不需要手动构建graph’s xml文件。按照图2显示的,导航编辑器分为三个部分。
1. 目标列表-罗列当前导航编辑器内所有目标。
2. 图编辑器-包含导航图内所有虚拟目标。
3. 属性编辑器-包含目标属性和导航图内的actions。
Identify destinations
为你的app验证目标是创建导航图的第一部。你可以创建一个空目标或者在一个已经存在的工程内为fragments和Acitvities创建目标。
Note:导航架构组件是为一个activity内包含多个fragment的场景而设计的。主activity拥有导航图。在一个拥有众多activity目标的app内,每一个附加的activity都拥有自己的导航图。修改一个activity到主导航将在之后的文档进行讨论。
请参照一下步骤验证app内目标:
- 在图编辑器,点击** New Destination。New Destination** 显示。
- 点击Create blank destination或者点击一个fragment或者activity。会出现新的Android Component 对话框。
- 在Fragment Name字段输入名字。这个名字是fragment的类的名字。
- 在Fragment layout Name字段输入一个名字。这个名字是fragment布局文件的名字。
- 点击完成,在图标编辑器的目标列表出现一个目标,出现以下现象:
- 如果你创建一个空白的目标,在图标编辑器出现一个空白的一个“Hello blank fragment” 的信息的目标,如果你点击了一个Fragment或者Activity,图标编辑器会显示对应的布局。
- 一个fragment子类将会被创建,名字在第三步被指定。
- 一个资源文件会被创建,名字在第四步被指定。
图3 展示了一个空白的已经存在的目标。
- 点击高亮显示新插入的目标。在属性面板显示以下属性。
- 在类型字段显示“fragment”或者“activity”,表示目标是否执行了fragment或者activity。
- 标签字段表示了目标布局文件的名字;
- ID字段表示了目标的ID,用来在代码中被引用;
- 类字段表示了引用的目标的类名;
- 点击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;
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示例:
NavHostFragment.findNavController(Fragment)
Navigation.findNavController(Activity, @IdRes int viewId)
-
Navigation.findNavController(View)
通过NavController
的navigate()
方法导航到destination。navigate()
方法接受一个资源Id的参数。该Id可以是一个特别的destination的Id或者是action Id。相比destination的Id,使用action Id可以使用关联的transition动画。点击Create a transition between destinations查询更多关于transition信息。
以下代码示例展示了ViewTransactionsFragment的导航:
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有两个流程:转钱流程和余额流程。
把destinations分组为nested graph:
1、在graph editor,按住shift点击需要加入nested graph的destination,该destination高亮。
2、打开菜单选择Move to Nested Graph > New Graph,如图7.
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的区别)