美文网首页
Navigation Architecture Componen

Navigation Architecture Componen

作者: WangGavin | 来源:发表于2018-08-30 17:11 被阅读366次

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验证一下。

相关文章

网友评论

      本文标题:Navigation Architecture Componen

      本文链接:https://www.haomeiwen.com/subject/glsdwftx.html