Navigation框架介绍_navoptions-程序员宅基地

技术标签: 生命周期  navigation  fragment  

Navigation框架

概述

Navigation,导航, Google官方对它的描述:

今天,我们宣布推出Navigation组件,作为构建您的应用内界面的框架,重点是让单 Activity 应用成为首选架构。利用Navigation组件对 Fragment 的原生支持,您可以获得架构组件的所有好处(例如生命周期和 ViewModel),同时让此组件为您处理 FragmentTransaction 的复杂性。此外,Navigation组件还可以让您声明我们为您处理的转场。它可以自动构建正确的“向上”和“返回”行为,包含对深层链接的完整支持,并提供了帮助程序,用于将导航关联到合适的UI小部件,例如抽屉式导航栏和底部导航。## 一.相关文档

1. 入门阅读:

2.相关博客

3. 提升阅读:

二.使用方法(按照NavigationDemo进行讲解)

Demo运行需要的虚拟机尺寸:

generic_x86:/ $ wm density
wm density
Physical density: 240
Override density: 200
generic_x86:/ $ wm size
wm size
Physical size: 1920x720
generic_x86:/ $

step1 在Module下的build.gradle中添加以下依赖:

    // Navigation kotlin版本(依赖Kotlin插件)
    implementation 'android.arch.navigation:navigation-fragment-ktx:1.0.0-alpha08' 
    implementation 'android.arch.navigation:navigation-ui-ktx:1.0.0-alpha08' 
     // Navigation Java版本
    implementation 'android.arch.navigation:navigation-fragment:1.0.0-alpha08' 
    implementation 'android.arch.navigation:navigation-ui:1.0.0-alpha08'

step2 分析Fragment层级,创建Fragment:

层级关系分为两种类型:并列关系和上下级关系。

  • 并列关系的Fragment需要在转场切换中保持实例的唯一性,确保不会因为转场和切换致使实时数据丢失而影响用户体验。
  • 上下级关系的Fragment,下级Fragment的生命周期需要从推出开始然后到返回到上一级Fragment的时候结束。在本项目中各个Fragment的层级关系如图2-1所示:image注:
  • 实线表示构成,而黄色表示实体所含Fragment对象除初始destination的生命周期跟随父Fragment之外其他的Fragment只拥有一次生命周期。
  • 蓝色虚线表示并列关系.
  • 青色带箭头实线按照指向表示上下级关系.
  • 青色带箭头虚线线按照指向表示越级转场.
  • 青色实体表示只拥有一次生命周期的Fragment。

step3 创建导航配置文件:如上图2-1所示,由同一实体发出由实线构成的一组组合对应一个graph文件,创建步骤如下:

  • 在项目module的res文件夹下创建navigation文件夹。
  • 在navigation文件夹下创建对应的 graph xml文件,如图3-1所示:*根据名称能反映出与之对应的Activity或Fragment实体
    image
  • graph文件xml内容格式如main_navigation.xml:
<?xml version ="1.0" encoding ="utf-8"?>
 <!--startDestination 表示初始destination,在这里每一个Fragment就是一个destination节点未来可能支持Activity-->
 <!--keep_state_fragment 表示一个destination节点,节点标签名keep_state_fragment可自定义-->
<navigation 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"
    app:startDestination="@+id/testCrossLevelFragment">
  
    <keep_state_fragment
        android:id="@+id/testCrossLevelFragment"
        android:name="com.oaksdance.navigationdemo.ui.fragment.test.TestCrossLevelContainerFragment"
        android:label="TestCrossLevelFragment"
        tools:layout="@layout/fragment_test_cross_level_container" />
    <keep_state_fragment
        android:id="@+id/blueToolFragment"
        android:name="com.oaksdance.navigationdemo.ui.fragment.BlueToolFragment"
        android:label="@string/blue_tool"
        tools:layout="@layout/fragment_blue_tool" />
    <keep_state_fragment
        android:id="@+id/screenFragment"
        android:name="com.oaksdance.navigationdemo.ui.fragment.ScreenFragment"
        android:label="@string/screen"
        tools:layout="@layout/fragment_screen" />
    <keep_state_fragment
        android:id="@+id/soundFragment"
        android:name="com.oaksdance.navigationdemo.ui.fragment.SoundFragment"
        android:label="@string/sound"
        tools:layout="@layout/fragment_sound" />
    <keep_state_fragment
        android:id="@+id/commonFragment"
        android:name="com.oaksdance.navigationdemo.ui.fragment.CommonFragment"
        android:label="@string/common"
        tools:layout="@layout/fragment_common" />
</navigation>
  • graph文件支持可视化编辑,点击左下角design进入如图的可视化界面,点击红色箭头所示加号按钮即可添加destination节点,如下图3-2所示:
    image
  • graph文件针对不同层级关系采用不同的destination标签:
    • 对于并列关系的Fragment destination节点,需要拓展Naviagtion框架中的Navigator类,利用拓展的Navigator类对Fragment的添加和调用方式进行适应性改变。在Demo中com.oaksdance.navigationdemo.navigation.KeepStateNavigator就是这个拓展的类,它自定义了一个名称为keep_state_fragment的destination标签名。
    • 框架默认的Fragment destination节点是上下级关系型的,它每一次的显现其实都是一次replace,而默认的destination标签名就是fragment。
    • 在Demo中由如2-1所示的每组蓝色实线构成对应的graph文件其destination标签就是采用的keep_state_fragment而每组黄色实线构成对应的graph文件则采用框架默认的fragment标签名。

step4 根据层级关系类型的不同采用不同方式调用graph文件:

首先在Activity或Fragment的布局文件中引用fragment,并且指定它为androidx.navigation.fragment.NavHostFragment类,注意引用的时候一定不能忘记定义它的android:id。

  • 对于上下级关系的仅仅需要在app:navGraph配上graph文件的资源地址,如Demo中的NetContainerFragment:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.oaksdance.navigationdemo.ui.fragment.net.NetFragment">
    <fragment
        android:id="@+id/net_container_host_fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginTop="@dimen/fragment_content_top"
        app:defaultNavHost="true"
        app:navGraph="@navigation/net_container_navigation" />
</LinearLayout>
  • 对于并列关系的,因为是自定义的Navigator所以需要使用代码向androidx.navigation.fragment.NavHostFragment中添加对应的graph文件,如Demo中的MainActivity:
//获取NavHostFragment
NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.main_nav_host_fragment);// 实例化自定义的navigator
Navigator navigator = new KeepStateNavigator(this, navHostFragment.getChildFragmentManager(), R.id.main_nav_host_fragment);// 将自定义的navigator配给NavHostFragment的NavController
navHostFragment.getNavController().getNavigatorProvider().addNavigator(navigator);// 将graph文件赋给NavHostFragment的NavController
navHostFragment.getNavController().setGraph(R.navigation.main_navigation);

step5 调用graph文件中action ID或destination ID进行跳转操作:

Action就是配置在graph中的一种表示跳转行为的标签,其往往配置在上下级关系类型的graph文件中,如Demo中的test1_container_navigation,根据其ID命名可以直观的了解到它的作用:

<?xml version ="1.0" encoding ="utf-8"?>
<navigation 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"
    app:startDestination="@id/test1Fragment">
    <fragment
        android:id="@+id/test1Fragment"
        android:name="com.oaksdance.navigationdemo.ui.fragment.test.test1.Test1Fragment"
        android:label="Test1Fragment">
        <action
            android:id="@+id/action_test1Fragment_to_child1Fragment"
            app:destination="@id/child1Fragment" />
    </fragment>
    <fragment
        android:id="@+id/child1Fragment"
        android:name="com.oaksdance.navigationdemo.ui.fragment.test.test1.Child1Fragment"
        android:label="Child1Fragment">
        <action
            android:id="@+id/action_child1Fragment_popUpTo_test1Fragment"
            app:popUpTo="@id/test1Fragment" />
    </fragment>
</navigation>

调用Action的场景有两种:

  • 内部调用,即在配置了Action的Fragment内部调用,如Test1Fragment调用ID为action_test1Fragment_to_child1Fragment的Action。
 NavHostFragment
    .findNavController(Test1Fragment.this)
         .navigate(R.id.action_test1Fragment_to_child1Fragment);
  • 越级调用,即子Fragment调用配置在父Fragment甚至是顶级Fragment内部的Action,如HotFragment调用它的父Fragment——NetFragment内部配置的Action,action_netContainerFragment_to_updateNameFragment和action_netContainerFragment_to_updatePasswordFragment:
 NavigationUtil.takeAction(
    HotFragment.this, 
    R.id.net_container_host_fragment, //NetFragment寄生的NavHostFragment的ID
    R.id.action_netContainerFragment_to_updateNameFragment //ActionID
 );

注意:使用Action越级跳转是为了方便Fragment的返回退栈,因此这类Acition往往成对出现,例如UpdateNameFragment的返回,不过这次调用返回Action属于内部调用:

 NavHostFragment
    .findNavController(UpdateNameFragment.this)
        .navigate(R.id.action_pop_up_from_updateNameFragment);

在一组并列关系的Fragment中,因为他们之间不存在返回,所以它们之间相互调转直接调用它们各自对应的destinationID,如MainActivity中:

private void showFragment(int position) {
    Navigation
        .findNavController(this, R.id.main_nav_host_fragment)
         .navigate(getResId(position));
}
private int getResId(int position) {
    switch (position) {
        case 1:
            return R.id.blueToolFragment;
        case 2:
            return R.id.screenFragment;
        case 3:
            return R.id.soundFragment;
        case 4:
            return R.id.commonFragment;
        default:
            return R.id.testCrossLevelFragment;
    }
}

对于Navigation框架结合DrawerLayout,BottomNavigationView等等来使用的方法各式各样,但是查看源码可知,其跳转的逻辑都是想法设法获取NavController,然后通过NavController调用destinationID来跳转。

参数传递和转场动画设置,根据官方Demo

Navaigtion框架支持参数传递和转场动画设置,使用到的同样是NavController的navigate()方法:

public void navigate(@IdRes int resId, @Nullable Bundle args,
        @Nullable NavOptions navOptions) {
    navigate(resId, args, navOptions, null);
}
  • 参数传递

首先在graph文件中destination节点下利用argument标签配置所要接受的参数名、参数默认值和参数类型,如:

 <fragment
        android:id="@+id/flow_step_one_dest"
        android:name="com.example.android.codelabs.navigation.FlowStepFirstFragment"
        tools:layout="@layout/flow_step_one_fragment">
       
        <argument
            android:name="flowStepNumber"
            android:defaultValue="1"
            app:argType="integer" />
            
        <action
            android:id="@+id/next_action"
            app:destination="@+id/flow_step_two_dest"/>
    </fragment>

传递参数:

    val bundle = Bundle()
    bundle.putInt("flowStepNumber", 2)
    findNavController().navigate(R.id.flow_step_one_dest, bundle)

接收参数:

  val flowStepNumber = arguments?.getInt("flowStepNumber")
  • 两种方法设置转场动画

代码实例化一个NavOptions,然后把配置好的动画资源文件传给它,最后交给NavController的navigate()

val options = navOptions {
    anim {
        enter = R.anim.slide_in_right
        exit = R.anim.slide_out_left
        popEnter = R.anim.slide_in_left
        popExit = R.anim.slide_out_right
    }
}
view.findViewById<Button>(R.id.navigate_destination_button)?
    .setOnClickListener {
        findNavController()
            .navigate(R.id.flow_step_one_dest, null, options)
}

在graph文件中Action配置转场动画

<fragment
        android:id="@+id/home_dest"
        android:name="com.example.android.codelabs.navigation.HomeFragment"
        android:label="@string/home"
        tools:layout="@layout/home_fragment">
        <action
            android:id="@+id/next_action"
            app:destination="@+id/flow_step_one_dest"
            app:enterAnim="@anim/slide_in_right"
            app:exitAnim="@anim/slide_out_left"
            app:popEnterAnim="@anim/slide_in_left"
            app:popExitAnim="@anim/slide_out_right" />
    </fragment>

三.源码分析UML图片来源Android官方架构组件Navigation:大巧不工的Fragment管理框架

image

NavGraphFragment Fragment容器

两个作用:

  • 作为导航界面容纳Fragment的容器;
  • 管理并控制导航的行为本质上就是一个跟布局为FrameLayout的Fragment,它的OnCreateView方法如下:
public View onCreateView(@NonNull LayoutInflater inflater, 
                         @Nullable ViewGroup container,
                         @Nullable Bundle savedInstanceState) {
                         
        FrameLayout frameLayout = new FrameLayout(inflater.getContext());
        frameLayout.setId(getId());
        return frameLayout;
        
    }

根据单一职责原则,管理并控制导航的行为 交给另外一个类,这个类的作用应该仅是 控制导航行为,命名为 NavController,由NavGraphFragment引用它的实例。这里同时将NavController的持有者抽象为一个接口NavHost,它只有一个方法getNavController()需要实现。

public interface NavHost {
   /**
    * Returns the {@link NavController navigation controller} for this navigation host.
    *
    * @return this host's navigation controller
    */
   @NonNull
   NavController getNavController();
}

为了保证导航的 安全,NavHostFragment 在其作用域内有且仅有一个NavController 的实例。然而Navigation.findNavController(View),参数中传递任意一个 view的引用似乎都可以获取 NavController——如何保证 NavController 的局部单例呢?

事实上,NavHostFragment的View会以Tag的形式与其持有的NavController绑定,findNavController(View)内部实现是通过遍历View树,直到找到最底部 NavHostFragment中View持有的NavController对象,并将其返回的:

//NavHostFragment的onViewCreated里调用 Navigation的setViewNavController()进行Tag绑定
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    if (!(view instanceof ViewGroup)) {
        throw new IllegalStateException("created host view " + view + " is not a ViewGroup");
    }
    View rootView = view.getParent() != null ? (View) view.getParent() : view;
    Navigation.setViewNavController(rootView, mNavController);
}    
//遍历View树
private static NavController findViewNavController(@NonNull View view) {
        while (view != null) {
            NavController controller = getViewNavController(view);
            if (controller != null) {
                return controller;
            }
            ViewParent parent = view.getParent();
            view = parent instanceof View ? (View) parent : null;
        }
        return null;
  }
  
 //根据Tag找到NavController
 @Nullable
private static NavController getViewNavController(@NonNull View view) {
    Object tag = view.getTag(R.id.nav_controller_view_tag);
    NavController controller = null;
    if (tag instanceof WeakReference) {
        controller = ((WeakReference<NavController>) tag).get();
    } else if (tag instanceof NavController) {
        controller = (NavController) tag;
    }
    return controller;
}

NavController导航控制器

职责是:

  • 对navigation资源文件夹下nav_graph.xml的解析。
  • 通过解析xml,获取所有Destination(目标点)的引用或者Class的引用。
  • 记录当前栈中Fragment的顺序。
  • 管理控制导航行为。

NavController 持有了一个 NavInflater ,并通过 NavInflater 解析xml文件。之后,获取了所有 Destination的 Class对象,并通过反射的方式,实例化对应的 Destination,通过一个队列保存:

private NavInflater mInflater;  //NavInflater 
private NavGraph mGraph;        //解析xml,得到NavGraph
private int mGraphId;           //xml对应的id,比如 nav_graph_main
//所有Destination的队列,用来处理回退栈
private final Deque<NavDestination> mBackStack = new ArrayDeque<>(); 

从这里可以看出Navigation是一个导航框架,今后可能并非只为Fragment导航。因此要将导航的Destination抽象出来,这个类叫做NavDestination——无论Fragment还是Activity,只要实现了这个接口,对于NavController来讲,他们都是Destination(目标点)而已。

对于不同的 NavDestination 来讲,它们之间的导航方式是不同的,比如Activity 和 Fragment,这就需要根据不同的NavDestination进行不同的导航处理,遵循策略模式这里需要Navigator类。

Navigator

职责是:

  • 能够实例化对应的 NavDestination;
  • 能够指定导航;
  • 能够后退导航;

以 FragmentNavigator为例,我们来看看它是如何执行的职责:

public class FragmentNavigator extends Navigator<FragmentNavigator.Destination> {
    //省略大量非关键代码,请以实际代码为主!
    @Override
    public boolean popBackStack() {
        return mFragmentManager.popBackStackImmediate();
    }
    @NonNull
    @Override
    public Destination createDestination() {
        // 核心代码如下,通过反射实例化Fragment
        Class<? extends Fragment> clazz = getFragmentClass();
        return  clazz.newInstance();
    }
    @Override
    public void navigate(@NonNull Destination destination, @Nullable Bundle args,
                            @Nullable NavOptions navOptions) {
        // 实际上还是通过FragmentTransaction进行的跳转处理
        final Fragment frag = destination.createFragment(args);
        final FragmentTransaction ft = mFragmentManager.beginTransaction();
        ft.replace(mContainerId, frag);
        ft.commit();
        mFragmentManager.executePendingTransactions();
    }
}

结尾

至此关于Navigation框架的知识总结完毕,如果迭代将会持续更新,谢谢!!!

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/MissTerven/article/details/88356278

智能推荐

JWT(Json Web Token)实现无状态登录_无状态token登录-程序员宅基地

文章浏览阅读685次。1.1.什么是有状态?有状态服务,即服务端需要记录每次会话的客户端信息,从而识别客户端身份,根据用户身份进行请求的处理,典型的设计如tomcat中的session。例如登录:用户登录后,我们把登录者的信息保存在服务端session中,并且给用户一个cookie值,记录对应的session。然后下次请求,用户携带cookie值来,我们就能识别到对应session,从而找到用户的信息。缺点是什么?服务端保存大量数据,增加服务端压力 服务端保存用户状态,无法进行水平扩展 客户端请求依赖服务.._无状态token登录

SDUT OJ逆置正整数-程序员宅基地

文章浏览阅读293次。SDUT OnlineJudge#include<iostream>using namespace std;int main(){int a,b,c,d;cin>>a;b=a%10;c=a/10%10;d=a/100%10;int key[3];key[0]=b;key[1]=c;key[2]=d;for(int i = 0;i<3;i++){ if(key[i]!=0) { cout<<key[i.

年终奖盲区_年终奖盲区表-程序员宅基地

文章浏览阅读2.2k次。年终奖采用的平均每月的收入来评定缴税级数的,速算扣除数也按照月份计算出来,但是最终减去的也是一个月的速算扣除数。为什么这么做呢,这样的收的税更多啊,年终也是一个月的收入,凭什么减去12*速算扣除数了?这个霸道(不要脸)的说法,我们只能合理避免的这些跨级的区域了,那具体是那些区域呢?可以参考下面的表格:年终奖一列标红的一对便是盲区的上下线,发放年终奖的数额一定一定要避免这个区域,不然公司多花了钱..._年终奖盲区表

matlab 提取struct结构体中某个字段所有变量的值_matlab读取struct类型数据中的值-程序员宅基地

文章浏览阅读7.5k次,点赞5次,收藏19次。matlab结构体struct字段变量值提取_matlab读取struct类型数据中的值

Android fragment的用法_android reader fragment-程序员宅基地

文章浏览阅读4.8k次。1,什么情况下使用fragment通常用来作为一个activity的用户界面的一部分例如, 一个新闻应用可以在屏幕左侧使用一个fragment来展示一个文章的列表,然后在屏幕右侧使用另一个fragment来展示一篇文章 – 2个fragment并排显示在相同的一个activity中,并且每一个fragment拥有它自己的一套生命周期回调方法,并且处理它们自己的用户输_android reader fragment

FFT of waveIn audio signals-程序员宅基地

文章浏览阅读2.8k次。FFT of waveIn audio signalsBy Aqiruse An article on using the Fast Fourier Transform on audio signals. IntroductionThe Fast Fourier Transform (FFT) allows users to view the spectrum content of _fft of wavein audio signals

随便推点

Awesome Mac:收集的非常全面好用的Mac应用程序、软件以及工具_awesomemac-程序员宅基地

文章浏览阅读5.9k次。https://jaywcjlove.github.io/awesome-mac/ 这个仓库主要是收集非常好用的Mac应用程序、软件以及工具,主要面向开发者和设计师。有这个想法是因为我最近发了一篇较为火爆的涨粉儿微信公众号文章《工具武装的前端开发工程师》,于是建了这么一个仓库,持续更新作为补充,搜集更多好用的软件工具。请Star、Pull Request或者使劲搓它 issu_awesomemac

java前端技术---jquery基础详解_简介java中jquery技术-程序员宅基地

文章浏览阅读616次。一.jquery简介 jQuery是一个快速的,简洁的javaScript库,使用户能更方便地处理HTML documents、events、实现动画效果,并且方便地为网站提供AJAX交互 jQuery 的功能概括1、html 的元素选取2、html的元素操作3、html dom遍历和修改4、js特效和动画效果5、css操作6、html事件操作7、ajax_简介java中jquery技术

Ant Design Table换滚动条的样式_ant design ::-webkit-scrollbar-corner-程序员宅基地

文章浏览阅读1.6w次,点赞5次,收藏19次。我修改的是表格的固定列滚动而产生的滚动条引用Table的组件的css文件中加入下面的样式:.ant-table-body{ &amp;amp;::-webkit-scrollbar { height: 5px; } &amp;amp;::-webkit-scrollbar-thumb { border-radius: 5px; -webkit-box..._ant design ::-webkit-scrollbar-corner

javaWeb毕设分享 健身俱乐部会员管理系统【源码+论文】-程序员宅基地

文章浏览阅读269次。基于JSP的健身俱乐部会员管理系统项目分享:见文末!

论文开题报告怎么写?_开题报告研究难点-程序员宅基地

文章浏览阅读1.8k次,点赞2次,收藏15次。同学们,是不是又到了一年一度写开题报告的时候呀?是不是还在为不知道论文的开题报告怎么写而苦恼?Take it easy!我带着倾尽我所有开题报告写作经验总结出来的最强保姆级开题报告解说来啦,一定让你脱胎换骨,顺利拿下开题报告这个高塔,你确定还不赶快点赞收藏学起来吗?_开题报告研究难点

原生JS 与 VUE获取父级、子级、兄弟节点的方法 及一些DOM对象的获取_获取子节点的路径 vue-程序员宅基地

文章浏览阅读6k次,点赞4次,收藏17次。原生先获取对象var a = document.getElementById("dom");vue先添加ref <div class="" ref="divBox">获取对象let a = this.$refs.divBox获取父、子、兄弟节点方法var b = a.childNodes; 获取a的全部子节点 var c = a.parentNode; 获取a的父节点var d = a.nextSbiling; 获取a的下一个兄弟节点 var e = a.previ_获取子节点的路径 vue