Navigation Architecture Component 学习笔记

Navigation Architecture Component 的一些概念

导航原则

任何应用内导航的目标应该是为用户提供一致且可预测的体验,为了实现这一目标,导航架构组件可帮助您构建符合以下几个导航原则的应用程序。

  • 堆栈用于表示应用程序的“导航状态”
  • 前进(Up)按钮永远不会退出您的应用
  • 前进(Up)和后退(Back)在您的应用程序任务中是等效的
    • 如果一直后退不能退出你的应用,那就跟前进没什么两样了。
  • 深度链接到目标或导航到同一目标应产生相同的堆栈
    • 应该是跟Activity任务栈管理一致的意思吧

目标 destination

一个destination是你在你的App上能导航到的任何地方,虽然destination通常是碎片(Fragment),但是Navigation Architecture Component也支持其它类型的destination:

  • Activity
  • 导航图(navigation graph)或者次级导航图(navigation subgraph)
  • 自定义的destination类型

操作 action

一个导航图与目标之间的联系称作操作,下图就展示了一个应用的导航图,来自于一个包含了6个目标,目标之间被5个action连接的应用

一个导航图

提示:如果要在Android Studio中使用导航架构组件,则必须使用Android Studio 3.2 Canary 14或更高版本。不知道AS 3.2正式版好久出,应该快了。

角色

NaviHostFragmnet

简单例子

1. 实现类似首页多Fragment的切换

效果

导航效果

这个业务很常见,一般用于应用首页,但如图切换的四个Fragment并不是前进与后退关系,而是同级横向切换关系。

以前的实现方案

我自己考虑的实现方案是优先使用Android官方库

  1. 使用FragmentTabHost

设置好Tab和对应的Fragment,FragmentTabHost可以自动帮我们做tab和Fragment之间的绑定,切换Fragment的业务代码也给我们封装好了。不过FragmentTabHost的切换方案是先add所有的fragment,再执行dettach上一个fragment,attach要切换的fragment。所以每次切换到fragment,fragment的生命周期都会从onCreateView开始执行,一直执行到onResume,这一点也要考虑下。

  1. 使用BottomNavigationView + 自己写的切换代码

FragmentTabHost虽然帮我们实现了切换,但是切换Fragment的方式也是固定的,如果我想fragment之前已经被add显示过,之后再切换到它,不执行它的onCreateView直接show行不行,当然行,那就要自己的这份代码了。

  1. 使用TabLayout + 自己写的切换代码

这个跟方案二差不多,不过通常都搭配ViewPager使用

  1. 自定义Tab + 自己写的切换代码

这个较2,3方案就是导航的View也是自己定义,一般用于简单的导航需求。

试试BottomNavigationView+Navigation 新方案

我比较关注Navigation是怎样实现Fragment切换的

1.创建NavHostFragment

NavHostFragment可以看作放置显示Fragment的位置容器控件,本质就是个Fragment。

跟Fragment的用法差不多,有两种创建方式

  • 静态方式,贴在xml布局文件上

比如我这里,activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:orientation="vertical"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
<fragment
    android:layout_width="match_parent"
    android:id="@+id/main_nav_host"
    android:name="androidx.navigation.fragment.NavHostFragment"
    app:navGraph="@navigation/nav_graph"
    app:defaultNavHost="true"
    android:layout_weight="1"
    android:layout_height="wrap_content"/>
<android.support.design.widget.BottomNavigationView
    android:layout_width="match_parent"
    android:id="@+id/main_bottom_nav"
    app:menu="@menu/main_bottom_nav"
    android:background="@drawable/main_nav_background"
    android:layout_height="60dp"/>
</LinearLayout>

Activity创建的时候会自动解析xml,把fragment添加进Fragmentmanger,在Activity中,通过 (NavHostFragment) fragmentManager.findFragmentById(R.id.main_nav_host)就能获取到你添加的NavHostFragment实例。

  • java代码动态创建

官方例子:

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();

NavHostFragment有create方法获取一个新实例,然后添加到fragmentManager里,跟xml配置差不多,还是需要一个容器,setPrimaryNavigationFragment(finalHost)是设置back键拦截

一些参数设置

BottomNavigationView的菜单参数,main_bottom_nav.xml:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:id="@+id/oneFragment"
        android:icon="@drawable/ic_one"
        android:title="@string/title_one" />
    <item
        android:id="@+id/twoFragment"
        android:icon="@drawable/ic_two"
        android:title="@string/title_two" />
    <item
        android:id="@+id/threeFragment"
        android:icon="@drawable/ic_three"
        android:title="@string/title_three" />
    <item
        android:id="@+id/fourFragment"
        android:icon="@drawable/ic_four"
        android:title="@string/title_four" />
</menu>

四个Fragment

四个Fragment

代码略

naviHostFragment的参数

NaviHostFragment的navGraph参数,像菜单一样也可用xml写,这是很方便的,用xml就意味着AS有GUI界面能够让你拖拽点点点生成代码。
所以现在项目的res又有了新类型的资源文件:navigation

如果需要AS支持navigation资源文件编辑,还要再Settings里面开启Enable Navigation Editor

开启Enable Navigation Editor

比如我现在四个碎片的例子,nav_graph.xml

<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    app:startDestination="@id/oneFragment">
    <fragment
        android:name="me.newtrekwang.navigationdemo.nav1.OneFragment"
        android:id="@+id/oneFragment"
        android:label="one_fragment"
        tools:layout="@layout/one_fragment"
        />
    <fragment
        android:name="me.newtrekwang.navigationdemo.nav2.TwoFragment"
        android:id="@+id/twoFragment"
        android:label="two_fragment"
        tools:layout="@layout/two_fragment"
        />
    <fragment
        android:id="@+id/threeFragment"
        android:name="me.newtrekwang.navigationdemo.nav3.ThreeFragment"
        android:label="three_fragment"
        tools:layout="@layout/three_fragment" />
    <fragment
        android:id="@+id/fourFragment"
        android:name="me.newtrekwang.navigationdemo.nav4.FourFragment"
        android:label="four_fragment"
        tools:layout="@layout/four_fragment" />
</navigation>

我把navigation里包含的每个标签看作是目标,所以目标就必须有自己的id等属性,比如fragment,id为目标id,name为目标完成类名,label是标签,layout方便AS解析预览。

nav_graph预览

nav_graph预览

可以看到预览里,总动解析出host为MainActivity那个navHostFragment,以及四个我新定义的Fragment,oneFragment标记为start,表示为起始显示的Fragment.

因为现在是四个碎片之间横向切换,所以他们之间没有Action

BottomNavigationView与NavController绑定

回到MainActivity

public class MainActivity extends AppCompatActivity {
    
    private BottomNavigationView bottomNavigationView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        bottomNavigationView = findViewById(R.id.main_bottom_nav);
        BottomNavigationViewHelper.disableShiftMode(bottomNavigationView);

        NavHostFragment navHostFragment = (NavHostFragment)getSupportFragmentManager().findFragmentById(R.id.main_nav_host);
        NavController navController = navHostFragment.getNavController();
        NavigationUI.setupWithNavController(bottomNavigationView,navController);
    }
    @Override
    public boolean onSupportNavigateUp() {
        return Navigation.findNavController(this,R.id.main_nav_host).navigateUp();
    }
}

Navigation Architecture Component 的设计是导航都通过NaviController负责导航,NavController可以通过三种静态方法获得

  • NavHostFragment.findNavController(Fragment)
  • Navigation.findNavController(Activity, @IdRes int viewId)
  • Navigation.findNavController(View)

NavHostFragment.findNavController(fragment)就是不断遍历fragment的getParentFragment(),直到找到一个NavHostFragment实例,然后返回它的navController.我们这里MainActivty很显然可以直接获取到navHostFragment,然后调用getNavController即刻。

Navigation.findNavController(Activity, @IdRes int viewId),Navigation.findNavController(View)其实是一个方法,最终会遍历view的 view.getParent(),直到找到tag为NavController的View,然后返回controller。

所以获取NavController的来源一种是NavHostFragment,另一种是tag为NaviController的View。

最后NavigationUI.setupWithNavController(bottomNavigationView,navController),NavigationUI把bottomNavigationView和navController绑定在一起,这样一个碎片切换的业务几行代码就搞定了,大家可以实践下,貌似比之前的方案少了很多代码量。

NavigationUI 怎么替我们绑定bottomNavigationView和navController的?

NavigationUI替我们实现的切换是啥样的?Fragment切换时的生命周期如何?令我有点小失望的是,每次切换到新Fragment都会new 一次Fragment,然后执行它的生命周期方法,是的,就是重新new 一次,再replace,你可以再Fragment里加几个log验证一下。

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

推荐阅读更多精彩内容