精华内容
参与话题
问答
  • Navigation框架介绍

    千次阅读 2019-03-08 18:12:23
    Navigation框架 概述 Navigation,导航, Google官方对它的描述: 今天,我们宣布推出Navigation组件,作为构建您的应用内界面的框架,重点是让单 Activity 应用成为首选架构。利用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框架的知识总结完毕,如果迭代将会持续更新,谢谢!!!

    展开全文
  • 最全面的Navigation的使用指南

    千次阅读 2019-05-04 07:02:52
    Navigation可以目前看做Google对于之前的Fragment的不满, 重新搭建的一套Fragment管理框架. 但是Navgation未来应该不仅限于Fragment导航. 并且Navigation可以和BottomNavigationView/NavigationView/Toolbar等结合...

    Navigation可以目前看做Google对于之前的Fragment的不满, 重新搭建的一套Fragment管理框架. 但是Navgation未来应该不仅限于Fragment导航.

    并且Navigation可以和BottomNavigationView/NavigationView/Toolbar等结合使用, 不再需要去写冗余代码管理Fragment.

    并且具备完善的Fragment回退栈管理.

    如果是使用Java语言我不推荐使用任何新框架了, 就自己玩自己的吧.

    ktx (除基本依赖外还包含一些Kotlin新特性的函数)

    implementation 'androidx.navigation:navigation-fragment-ktx:2.0.0'
    implementation 'androidx.navigation:navigation-ui-ktx:2.0.0'
    复制代码

    一些常用的关键字解释

    navigation(nv): 导航, 即Navigation框架的fragment返回栈

    graph: 指一个描述返回栈关系的xml文件 (NavigationResourceFile) 也是布局编辑器用于显示图表的界面数据来源

    destination: 目标, 即在返回栈中要跳转的新页面

    pop: 弹出栈, 会弹出所有不符合目标的页面, 直至找到目标页面(默认情况不弹出目标页面也可以设置), 可以理解为Fragment的singleTask模式

    navHost: 即所有页面的容器. 类似网页中的host, 所有path路径都是在host之后跟随, host固定不变.

    XML编辑

    点击NavResourceFile中的Design即可查看布局编辑器, 布局编辑器分为三栏.

    左侧是已添加的导航, 中间是页面浏览, 中间栏的工具栏可以创建和快速添加标签以及整理页面, 右侧属性栏方便添加属性.

    navigation这是个嵌套的图表, 可以点击打开新的图表页面.

    Activity布局中

    <LinearLayout
        .../>
        <androidx.appcompat.widget.Toolbar
            .../>
        <fragment
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"
            android:id="@+id/my_nav_host_fragment"
            android:name="androidx.navigation.fragment.NavHostFragment"
            app:navGraph="@navigation/mobile_navigation"
            app:defaultNavHost="true"
            />
        <com.google.android.material.bottomnavigation.BottomNavigationView
            .../>
    </LinearLayout>
    复制代码
    android:name="androidx.navigation.fragment.NavHostFragment"  
    固定写法
    
    app:navGraph 
    指定navigation资源文件, 也可以不指定后面通过代码中动态设置
    
    app:defaultNavHost 
    是否拦截返回键事件, false表示不需要回退栈.
    复制代码

    NavigationResourceFile

    res目录创建 AndroidResourceFile 选择 Navigation. 然后 new-> NavigationResourceFile

    navigation

    app:startDestination="@+id/home_dest" 指定初始目标
    复制代码

    navigation可以嵌套navigation标签.

    在布局编辑器中会显示为

    嵌套navigation无法互相关联

    <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"
        android:id="@+id/nav_global"
        app:startDestination="@id/mainFragment">
    
     <!--   <action
            android:id="@+id/global_action"
            app:destination="@id/navigation" />-->
    
        <fragment
            android:id="@+id/mainFragment"
            android:name="com.example.frameexample.MainFragment"
            android:label="fragment_main"
            tools:layout="@layout/fragment_main">
            <action
                android:id="@+id/action_mainFragment_to_personInfoFragment"
                app:destination="@id/settingFragment" />
        </fragment>
    
        <navigation
            android:id="@+id/navigation"
            app:startDestination="@id/settingFragment">
            <fragment
                android:id="@+id/settingFragment"
                android:name="com.example.frameexample.SettingFragment"
                android:label="fragment_setting"
                tools:layout="@layout/fragment_setting" />
        </navigation>
    
    </navigation>
    复制代码

    上面的mainFragment无法直接app:destination="@id/settingFragment"这会导致运行错误. 只能先导航到navigation.(即NavHostFragment所在的界面)

    fragment

    android:id 不言而喻
    
    android:name 目标要实例化的fragment完全限定类名
     
    tools:layout 用于显示在布局编辑器
    
    android:label  用于后面绑定Toolbar等自动更新标题
    复制代码

    argument

    android:name="myArg"
    app:argType="integer"
    android:defaultValue="0"
    复制代码
    • 参数名称
    • 参数类型
    • 参数默认值

    在跳转导航页面的时候会自动在argument中带上参数(要求指定参数默认值). 数组和Paraclable/Serializable不支持默认值设置, 通过下面要讲的SafeArg可以在编译器校验参数类型安全问题.

    action

    动作 用于页面跳转时指定目标页面

    android:id="@+id/next_action" 
    动作id
    app:destination="@+id/flow_step_two_dest"> 
    目标页面
    app:popUpTo="@id/home_dest" 
    当前属于弹出栈
    app:popUpToInclusive="true/false" 
    弹出栈是否包含目标
    app:launchSingleTop="true/false" 
    是否开启singleTop模式
    
    app:enterAnim=""
    app:exitAnim=""
    导航动画
    
    app:popEnterAnim=""
    app:popExitAnim=""
    弹出栈动画
    复制代码

    如果从导航页面到新的Activity页面, 动画不支持. 请使用默认的Activity设置动画去支持.

    全局动作

    一般情况下NavController只能使用当前Fragment在NavXML中声明的子标签action, 但是可以通过直接给navigation标签创建子标签action实现全局动作, 即每个Fragment都能使用的动作.

    给navigation添加action子标签时要求给navigation指定熟悉android:id

    占位页面如果运行时没有指定Class并且导航到该占位页面时会抛出异常

    类关系

    涉及到的类关系

    • NavController 控制导航的跳转和弹出栈
    • NavOptions 控制跳转过程中的配置选项, 例如动画和singleTop模式
    • Navigation 工具类 创建点击事件或者获取控制器
    • NavHostFragment 导航的容器, 可以设置和获取导航图(NavGraph)
    • NavGraph 用于描述导航中页面关系的对象 可以增删改查页面,设置起始页等
    • NavigationUI 用于将导航和一系列菜单控件自动绑定的工具类
    • Navigator 页面的根接口, 如果想创建一个新的类型页面就要自定义他
    • NavDeepLinkBuilder 构建一个能打开导航页面的Intent

    NavController

    NavController用于跳转页面和参数传递等控制, 可以通过扩展函数得到实例.

    Fragment.findNavController()
    View.findNavController()
    Activity.findNavController(viewId: Int)
    复制代码

    导航

    public final void navigate (int resId)
    
    public final void navigate (int resId, 
                    Bundle args)
    
    public void navigate (int resId, 
                    Bundle args, 
                    NavOptions navOptions)
    
    public void navigate (NavDirections directions)
    
    public void navigate (NavDirections directions, 
                    NavOptions navOptions)
      
    public void navigate(@IdRes int resId, @Nullable Bundle args, @Nullable NavOptions navOptions,
                           @Nullable Navigator.Extras navigatorExtras)
    
    private void navigate(@NonNull NavDestination node, @Nullable Bundle args,
                            @Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras)
    
    public boolean navigateUp ()
    返回到上一个页面
    
    public boolean navigateUp (DrawerLayout drawerLayout)
    
    public boolean navigateUp (AppConfiguration appConfiguration)
    复制代码

    resId 可以是NavXML中的action或者destination标签id, 如果是action则会附带action的选项, 如果是页面destination则不会附带destination标签下的子标签action(写了白写).

    args 即需要在fragment之间传递的Bundle参数, 但是导航还支持另外一种插件形式的传递参数方式-安全参数SafeArgs, 后面提到.

    navOptions 即导航页面一些配置选项(例如动画)

    navigatorExtras 目前是用于支持转场动画的共享元素.

    ActivityNavigator和FragmentNavigator内部都实现了Navigator.Extras

    通过扩展函数可以快速创建

    fun FragmentNavigatorExtras(vararg sharedElements: Pair<View, String>) =
            FragmentNavigator.Extras.Builder().apply {
                sharedElements.forEach { (view, name) ->
                    addSharedElement(view, name)
                }
            }.build()
    
    fun ActivityNavigatorExtras(activityOptions: ActivityOptionsCompat? = null, flags: Int = 0) =
            ActivityNavigator.Extras.Builder().apply {
                if (activityOptions != null) {
                    setActivityOptions(activityOptions)
                }
                addFlags(flags)
            }.build()
    
    复制代码

    可以从源码看到内部都是使用的Extras.Builder构造器创建的.

    示例

        <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>
    复制代码

    上面你给action指定动画, 但是如果你使用的navigate中的参数resId不是R.id.next_action而是R.id.home_dest. 那么你这个action相当于不生效.

    弹出栈, 即从Nav回退栈中清除Fragment.

    public boolean popBackStack (int destinationId,  // 目标id
                    boolean inclusive) // 是否包含参数目标
      
    public boolean popBackStack ()
    弹出当前Fragment
    复制代码

    监听导航

    public void addOnDestinationChangedListener (NavController.OnDestinationChangedListener listener)
    public void removeOnDestinationChangedListener (NavController.OnDestinationChangedListener listener)
    复制代码

    回调

        public interface OnDestinationChangedListener {
            /**
             * 导航完成以后回调函数(但是可能动画还在播放中)
             *
             * @param 控制导航到目标的导航控制器NavController
             * @param 目标页面
             * @param 导航到目标页面的参数
             */
            void onDestinationChanged(@NonNull NavController controller,
                    @NonNull NavDestination destination, @Nullable Bundle arguments);
        }
    复制代码

    状态保存和恢复

    public Bundle saveState ()
    public void restoreState (Bundle navState)
    复制代码
    public NavDestination getCurrentDestination ()
    
    public NavGraph getGraph ()
    public void setGraph (int graphResId)
    public void setGraph (NavGraph graph)
    
    public NavigatorProvider getNavigatorProvider ()
    
    public NavDeepLinkBuilder createDeepLink ()
    创建一个打开当前页面的深层链接构造器
    复制代码

    NavigatorProvider是一个提供者, 可以添加和查询Navigator.

    NavOptions

    属于导航时的附加选项设置

    相当于代码动态实现了NavigationResourceFile中的<action>标签的属性设置(但是无法指定目标).

    目前功能只有设置动画和singleTop(启动模式), popUp(弹出栈)

    提供一个DSL作用域

    fun navOptions(optionsBuilder: NavOptionsBuilder.() -> Unit): NavOptions
    复制代码

    示例

    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
      }
    
      launchSingleTop = true
      popUpTo = R.id.categoryFragment
    }
    
    findNavController().navigate(R.id.flow_step_one_dest, null, options)
    复制代码

    如果是跳转到新的Activity当前设置的动画都不支持

    弹出栈

    public NavOptions.Builder setPopUpTo (int destinationId, 
                    boolean inclusive)
    复制代码

    NavOptions这个函数和NavController有所区别, 他并不会决定目的地, 只是在导航到目的地之前先执行一个弹出栈指令.

    场景: 例如我现在购买一个商品支付成功, 这个时候我要将前面的商品详情; 订单配置等页面全部关闭 然后进入<支付成功>页面

    顺便说下在之前的做法是发送事件然后finish

    Navigation

    工具类

    目前只支持创建点击事件和获取控制器

    public static View.OnClickListener createNavigateOnClickListener (int resId)
    public static View.OnClickListener createNavigateOnClickListener (int resId, 
                    Bundle args)
    快速创建一个跳转到目标的View.OnClickListenner
    
    
    public static NavController findNavController (Activity activity, 
                    int viewId)                
    public static NavController findNavController (View view)
    public static void setViewNavController (View view, 
                    NavController controller)
    以上都可以使用Kotlin扩展函数获取
    复制代码

    NavHostFragment

    该对象为Navigation提供一个容器

    一般使用情况是在布局中直接定义, 但是也可以通过代码构建实例, 然后通过代码创建视图(例如ViewPager等)

    public static NavHostFragment create (int graphResId)
    复制代码
    public static NavController findNavController (Fragment fragment)
    这个函数实际上就是findNavController()扩展函数的实际执行函数
    复制代码

    NavigationUI

    该工具类负责绑定视图控件和导航, 所有绑定都只需要id对应即可自动导航.

    设置导航到新页面时自动更新标题文字

    fun AppCompatActivity.setupActionBarWithNavController(
        navController: NavController,
        drawerLayout: DrawerLayout?
    ) 
    
    fun AppCompatActivity.setupActionBarWithNavController(
        navController: NavController,
        configuration: AppBarConfiguration = AppBarConfiguration(navController.graph)
    )
    复制代码

    这里出现个参数AppBarConfiguration, 用于配置Toolbar/ActionBar/CollapsingToolbarLayout.

    构造器模式使用Builder创建实例

    AppBarConfiguration.Builder(NavGraph navGraph)
    顶级目标是NavGraph的起始页面
    
    AppBarConfiguration.Builder(Menu topLevelMenu)
    菜单包含的全部是顶级目标
    
    AppBarConfiguration.Builder(int... topLevelDestinationIds)
    顶级目标集合
    AppBarConfiguration.Builder(Set<Integer> topLevelDestinationIds)
    复制代码

    顶级目标: 顶级目标即表示为回退栈最底位置, 无法再返回, 故可以理解为不需要返回键导航的页面(Toolbar等就不会显示返回箭头).

    函数

    AppBarConfiguration.Builder setDrawerLayout(DrawerLayout drawerLayout)
    绑定Toolbar同时绑定一个DrawerLayout联动
    
    AppBarConfiguration.Builder setFallbackOnNavigateUpListener(AppBarConfiguration.OnNavigateUpListener fallbackOnNavigateUpListener)
    
    AppBarConfiguration  build()
    复制代码

    AppBarConfiguration.OnNavigateUpListener 该回调接口会在每次点击向上导航时回调

    public interface OnNavigateUpListener {
    /**
    * 回调处理向上导航
    *
    * @return 返回true表示向上导航, false不处理
    */
    boolean onNavigateUp();
    }
    复制代码

    Toolbar也可以绑定Nav自动更新对应页面的标题

    fun Toolbar.setupWithNavController(
        navController: NavController,
        drawerLayout: DrawerLayout?
    ) {
        NavigationUI.setupWithNavController(this, navController,
            AppBarConfiguration(navController.graph, drawerLayout))
    }
    
    fun Toolbar.setupWithNavController(
        navController: NavController,
        drawerLayout: DrawerLayout?
    ) {
        NavigationUI.setupWithNavController(this, navController,
            AppBarConfiguration(navController.graph, drawerLayout))
    }
    复制代码

    CollapsingToolbarLayout

    fun CollapsingToolbarLayout.setupWithNavController(
        toolbar: Toolbar,
        navController: NavController,
        configuration: AppBarConfiguration = AppBarConfiguration(navController.graph)
    )
    
    fun CollapsingToolbarLayout.setupWithNavController(
        toolbar: Toolbar,
        navController: NavController,
        drawerLayout: DrawerLayout?
    )
    复制代码

    绑定菜单条目点击自动导航

    fun MenuItem.onNavDestinat ionSelected(navController: NavController): Boolean =
            NavigationUI.onNavDestinationSelected(this, navController)
    复制代码

    在onOptionsItemSelected

        override fun onOptionsItemSelected(item: MenuItem): Boolean {
            return item.onNavDestinationSelected(findNavController(R.id.my_nav_host_fragment))
        }
    复制代码

    Nav绑定NavigationView菜单

    
    
    fun NavigationView.setupWithNavController(navController: NavController) {
        NavigationUI.setupWithNavController(this, navController)
    }
    
    fun BottomNavigationView.setupWithNavController(navController: NavController) {
        NavigationUI.setupWithNavController(this, navController)
    }
    复制代码

    DeepLink

    Nav声明一个DeepLink(深层链接)只需要给Fragment添加一个子标签即可

    首先要在AndroidManifest中的activity中添加一个子标签nav-graph为NavRes注册DeepLink.

    <activity>
    	<nav-graph android:value="@navigation/mobile_navigation" />
    </activity>
    复制代码

    深层链接

    <deepLink app:uri="www.example.com/{myarg}" />
    复制代码

    通过ADB测试

    adb shell am start -a android.intent.action.VIEW -d "http://www.example.com/2334456"
    复制代码

    2334456即传递过去的参数

    {}包裹的字段属于变量, *可以匹配任意字符

    通过NavDeepLinkBuilder创建DeepLink Intent

    NavDeepLinkBuilder	setArguments(Bundle args)
    
    NavDeepLinkBuilder	setDestination(int destId)
    
    NavDeepLinkBuilder	setGraph(int navGraphId)
    
    NavDeepLinkBuilder	setGraph(NavGraph navGraph)
    复制代码

    生成PendingIntent可以用于开启界面(例如传给Notification)

    PendingIntent	createPendingIntent()
    
    TaskStackBuilder	createTaskStackBuilder()
    复制代码

    SafeArgs

    安全类型插件, 基于Gradle实现的插件.

    他的目的就是根据你在NavRes中声明argument标签生成工具类, 然后全部使用工具类而不是字符串去获取和设置参数. 避免前后两者参数类型不一致而崩溃.

    插件

    buildscript {
        repositories {
            jcenter()
            google()
        }
        dependencies {
            classpath 'android.arch.navigation:navigation-safe-args-gradle-plugin:1.0.0-alpha01'
        }
    }
    复制代码

    应用插件

    apply plugin: 'androidx.navigation.safeargs'
    复制代码

    在NavigationResourceFile中声明<argument>标签后会自动生成类

    插件会根据页面自动生成*Directions类, 该类包含该页面能使用的所有跳转动作(包含全局动作和自身动作).

    生成类会包含一个有规则的静态函数用于获取Directions的实现类(*Directions的静态内部类), 函数名称规则为

    action<页面名称>To<目标页面名称>
    
    全局动作名称规则则为: 动作id的变量命名法
    
    例: public static ActionMainFragmentToPersonInfoFragment actionMainFragmentToPersonInfoFragment()
    复制代码

    这里看下NavDirections接口的含义

    public interface NavDirections {
    
        /**
         * 返回动作id
         *
         * @return id of an action
         */
        @IdRes
        int getActionId();
    
        /**
         * 返回目标参数
         */
        @NonNull
        Bundle getArguments();
    }
    复制代码

    可以总结为 包含携带参数和动作.

    但是如果navRes中还包含<argument>标签, 则还会生成对应的*Args类, 并且上面提到的自动生成的*Directions中的NavDirection静态内部类还会生成参数的构造和访问器

    还有一系列 hashCode/equals/toString 函数

    完整的导航页面且传递数据写法

    导航至目标页面

    val action =
    MainFragmentDirections.actionMainFragmentToPersonInfoFragment().setName("设计师吴彦祖")
    
    findNavController().navigate(action)
    复制代码

    在目标页面接受数据

    tv.text = PersonInfoFragmentArgs.fromBundle(arguments!!).name
    复制代码

    这里可以再次想下什么是安全类型参数.

    总结

    关于Google推动的SingleActivity构建应用我说下我的看法, 我认为整个应用使用一个Activity还是比较麻烦的.

    列举下所谓麻烦

    1. Fragment无法设置默认动画, 动画统一管理起来很麻烦
    2. 所谓Fragment减少内存开销用户都无法感知
    3. 很多框架还是基于Activity实现的(例如路由,状态栏), 可能某些项目架构会受到局限

    我认为Navigation替代FragmentManger还是得心应手的, 并且导航图看起来也很有逻辑感.

    转载于:https://juejin.im/post/5ccd38fd6fb9a031f0380b2b

    展开全文
  • Android Navigation 详解

    千次阅读 2019-08-18 22:00:22
    Navigation用于 Fragment的管理。他可以让Fragrant之间的切换,拥有像Activity间一样的跳转。与 DrawerLayout(抽屉式布局)、ActionBar(导航栏)等有简洁完美的对接。 二、开发环境设置 注意:Navigation需要在...

    一、导航概述

        Navigation 用于 Fragment 的管理。他可以让 Fragrant 之间的切换,拥有像 Activity 间一样的跳转。与 DrawerLayout(抽屉式布局)、ActionBar(导航栏)等有简洁完美的对接。

    二、开发环境设置

    注意:Navigation 需要在 Android Studio 3.3 或更高版本中才可使用。(并且在 androidx 中支持的更好,之前没有使用 androidx 一直无法添加 Fragment 目的地)

    若要在项目中包含导航支持,请将以下依赖项添加到应用程序的 build.gradle 文件中:

    dependencies {
      def nav_version = "2.1.0-rc01"
    
      // Java
      implementation "androidx.navigation:navigation-fragment:$nav_version"
      implementation "androidx.navigation:navigation-ui:$nav_version"
    
      // Kotlin
      implementation "androidx.navigation:navigation-fragment-ktx:$nav_dep"
      implementation "androidx.navigation:navigation-ui-ktx:$nav_dep"
    
    }

    三、创建 navigation graph (导航图)

      导航图是一个 XML 资源文件,在导航图中有两个概念:

        1.Destination:导航图中的每一个导航被称为 Destination (目的地)。

        2.Action:导航与导航之间使用 Action (事件) 连接,用于说明两个导航之间的跳转关系。

    若要向项目添加导航图,请执行以下操作:

        在项目的 res 目录下,新建一个 navigation 文件夹,右键 navigation 文件夹,依次选择:NewNavigation resource file

        填写文件名称,如:nav_graph,点击 OK。

    四、编辑导航图

        在编辑导航图前,需要先创建 Activity,与相关的 Fragment。(此处使用 1 个 Activity,三个 Fragment)

    /**
     * @Author: Eli Shaw
     * @Date: 2019-08-18 10:10:42
     * @Description: Activity 容器类
     */
    class NavigationActivity : AppCompatActivity() {
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_navigation)
        }
    }
    /**
     * @Author: Eli Shaw
     * @Date: 2019-08-18 10:07:13
     * @Description: 登录页
     */
    class LoginFragment : Fragment(), View.OnClickListener {
    
        override fun onCreateView(
            inflater: LayoutInflater, container: ViewGroup?,
            savedInstanceState: Bundle?
        ): View? {
            // Inflate the layout for this fragment
            val viewRoot = inflater.inflate(R.layout.fragment_login, container, false)
            return viewRoot
        }
    }
    /**
     * @Author: Eli Shaw
     * @Date: 2019-08-18 10:08:52
     * @Description: 注册页
     */
    class RegisterFragment : Fragment(), View.OnClickListener {
    
        override fun onCreateView(
            inflater: LayoutInflater, container: ViewGroup?,
            savedInstanceState: Bundle?
        ): View? {
            // Inflate the layout for this fragment
            val viewRoot = inflater.inflate(R.layout.fragment_register, container, false)
            return viewRoot
        }
    }
    /**
     * @Author: Eli Shaw
     * @Date: 2019-08-18 10:40:42
     * @Description: 首页
     */
    class HomeFragment : Fragment() {
    
        override fun onCreateView(
            inflater: LayoutInflater, container: ViewGroup?,
            savedInstanceState: Bundle?
        ): View? {
            // Inflate the layout for this fragment
            return inflater.inflate(R.layout.fragment_home, container, false)
        }
    }

        创建好 nav_graph 导航图后,双击进行编辑(与常规布局文件很相似)

    1、指定 Activity 容器

        在 Activity 布局文件中指定导航图的容器

    <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"
            tools:context=".navigation.NavigationActivity">
    
        <fragment
                android:id="@+id/nav_host_fragment"
                android:name="androidx.navigation.fragment.NavHostFragment"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
    
                app:defaultNavHost="true"
                app:navGraph="@navigation/nav_graph"/>
    </LinearLayout>

        android:name:指定 Fragment 的类型为 NavHostFragment。

        app:defaultNavHost="true":让 Navigation 容器处理返回事件,在 Navigation 容器中如果有页面的跳转,点击返回按钮会先处理 容器中 Fragment 页面间的返回,处理完容器中的页面,再处理 Activity 页面的返回。如果值为 false 则直接处理 Activity 页面的返回。

        app:navGraph:指定 Navigation 文件。

    2、添加 Action (事件)

        拖动选中的(目的地)右方的圆点到另一个(目的地)即可创建一个 Action(事件)。

        点击左下角的 Text ,将显示如下代码

    <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"
                android:id="@+id/nav_graph"
                app:startDestination="@id/loginFragment">
    
        <!--登录 Fragment 包含两个事件:一个是注册页,一个是首页 -->
        <fragment android:id="@+id/loginFragment" android:name="cn.eli.jetpack.navigation.LoginFragment"
                  android:label="fragment_login" tools:layout="@layout/fragment_login">
            <action android:id="@+id/actionLoginToRegister" app:destination="@id/registerFragment"/>
            <action android:id="@+id/actionLoginToHome" app:destination="@id/homeFragment"/>
        </fragment>
    
        <!--注册 Fragment 包含一个事件:返回到登录页-->
        <fragment android:id="@+id/registerFragment" android:name="cn.eli.jetpack.navigation.RegisterFragment"
                  android:label="fragment_register" tools:layout="@layout/fragment_register">
            <action android:id="@+id/actionRegisterToLogin" app:destination="@id/loginFragment"/>
        </fragment>
    
        <!--首页 Fragment 没有事件-->
        <fragment android:id="@+id/homeFragment" android:name="cn.eli.jetpack.navigation.HomeFragment"
                  android:label="fragment_home" tools:layout="@layout/fragment_home"/>
    </navigation>

    3、导航图的复用

        <navigation> 是导航图的根元素,Destination (目的地) 与 Action (事件) 都是 <navigation> 元素中的节点。

        <navigation> 中可以包含 <navigation> 节点,这样使用的目的是可以复用相同的导航图。例如:增加一个找回密码页,这个找回密码页在另一个导航图中,找回密码后再继续登录操作:

        此图是一个包含导航图的预览页,文本内容如下

    <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" android:id="@+id/nav_multiplex"
                app:startDestination="@id/findPwdFragment">
    
        <fragment android:id="@+id/findPwdFragment" android:name="cn.eli.jetpack.navigation.FindPwdFragment"
                  android:label="fragment_find_pwd" tools:layout="@layout/fragment_find_pwd">
            <action android:id="@+id/action_findPwdFragment_to_nav_graph" app:destination="@id/nav_graph"/>
        </fragment>
    
        <!--引入另一个导航图-->
        <include app:graph="@navigation/nav_graph"/>
    
    </navigation>

    五、导航图间的跳转

    1、获取 NavController 的方式

        导航图使用 NavController 控制(目的地)之间的跳转,在 Activity,Fragment 中获取 NavController 有以下 6 种方式:

    1.Fragment.findNavController()
    2.View.findNavController()
    3.Activity.findNavController(viewId: Int) //只有 Activity 中可以使用
    4.NavHostFragment.findNavController(Fragment)
    5.Navigation.findNavController(Activity, @IdRes int viewId) //只有 Activity 中可以使用
    6.Navigation.findNavController(View)

    2、导航中目的地间的传值

        导航中目的地间也可以使用 Bundle 进行传值

    //使用 bundle 传值
    val bundle = Bundle()
    bundle.putInt(ARG_NAV, navController)
    
    findNavController().navigate(R.id.actionLoginToRegister, bundle)

    下面是登录页、注册页完整代码

    private const val ARG_NAV = "nav"
    
    /**
     * @Author: Eli Shaw
     * @Date: 2019-08-18 10:07:13
     * @Description: 登录页
     */
    class LoginFragment : Fragment(), View.OnClickListener {
    
        private var navController: Int = 0
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            arguments?.let {
                navController = it.getInt(ARG_NAV)
            }
        }
    
        override fun onCreateView(
            inflater: LayoutInflater, container: ViewGroup?,
            savedInstanceState: Bundle?
        ): View? {
            // Inflate the layout for this fragment
            val viewRoot = inflater.inflate(R.layout.fragment_login, container, false)
            //注册点击事件
            viewRoot.findViewById<Button>(R.id.btnRegister).setOnClickListener(this)
            //登录点击事件
            viewRoot.findViewById<Button>(R.id.btnLogin).setOnClickListener {
                findNavController().navigate(R.id.actionLoginToHome)
            }
            return viewRoot
        }
    
        /**
         * 注册点击事件
         */
        override fun onClick(view: View) {
            if (++navController > 4)
                navController = 1
    
            //使用 bundle 传值
            val bundle = Bundle()
            bundle.putInt(ARG_NAV, navController)
    
            //根据 navController 值的不同,使用不同的获取 NavController 的方式
            when (navController) {
                //Fragment.findNavController()
                1 -> findNavController().navigate(R.id.actionLoginToRegister, bundle)
                //View.findNavController()
                2 -> view.findNavController().navigate(R.id.actionLoginToRegister, bundle)
                //NavHostFragment.findNavController(Fragment)
                3 -> NavHostFragment.findNavController(this).navigate(R.id.actionLoginToRegister, bundle)
                //Navigation.findNavController(View)
                4 -> Navigation.findNavController(view).navigate(R.id.actionLoginToRegister, bundle)
            }
        }
    }
    
    private const val ARG_NAV = "nav"
    
    /**
     * @Author: Eli Shaw
     * @Date: 2019-08-18 10:08:52
     * @Description: 注册页
     */
    class RegisterFragment : Fragment(), View.OnClickListener {
    
        private var navController: Int = 0
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            arguments?.let {
                navController = it.getInt(ARG_NAV)
            }
        }
    
        override fun onCreateView(
            inflater: LayoutInflater, container: ViewGroup?,
            savedInstanceState: Bundle?
        ): View? {
            // Inflate the layout for this fragment
            val viewRoot = inflater.inflate(R.layout.fragment_register, container, false)
            viewRoot.findViewById<Button>(R.id.btnBackLogin).setOnClickListener(this)
            return viewRoot
        }
    
        override fun onClick(view: View) {
            val bundle = Bundle()
            bundle.putInt(ARG_NAV, navController)
            
            view.findNavController().navigate(R.id.actionRegisterToLogin, bundle)
        }
    }

    3、导航中目的地间的转场动画

    自定义转场动画

    cut_to_enter.xml (入场动画)

    <set xmlns:android="http://schemas.android.com/apk/res/android">
        <translate
                android:toYDelta="0"
                android:fromYDelta="100%"
                android:duration="1500"/>
    </set>

    cut_to_exit.xml(出场动画)

    <set xmlns:android="http://schemas.android.com/apk/res/android">
        <translate
                android:duration="1500"
                android:fromYDelta="0"
                android:toYDelta="-100%"/>
    </set>

    在导航图中编辑

    展开全文
  • Jetpack Navigation页面导航

    千次阅读 2020-08-13 15:15:49
    Jetpack Navigation页面导航 简介 该组件可以实现用户界面跳转、转场动画以及安全的参数传递等功能。无论是简单的按钮点击,还是应用栏和抽屉式导航栏等更为复杂的模式,该组件均可应对。还可以使用 Android Studio ...

    Jetpack Navigation页面导航

    简介
    该组件可以实现用户界面跳转、转场动画以及安全的参数传递等功能。无论是简单的按钮点击,还是应用栏和抽屉式导航栏等更为复杂的模式,该组件均可应对。还可以使用 Android Studio 的 Navigation Editor 来查看和编辑导航图,使页面跳转流程更加直观。
    Navigation 组件旨在用于具有一个主 Activity 和多个 Fragment 目的地的应用

    依赖
    在app的build.gradle文件中添加依赖:

    dependencies {
        ......
    
        def nav_version = "2.3.0"
    
        // Navigation依赖(Java版)
        //implementation "androidx.navigation:navigation-fragment:$nav_version"
        //implementation "androidx.navigation:navigation-ui:$nav_version"
    
        // Navigation依赖(Kotlin版)
        implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
        implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
    
        // 动态功能模块支持
        //implementation "androidx.navigation:navigation-dynamic-features-fragment:$nav_version"
    
        // 测试模块
        //androidTestImplementation "androidx.navigation:navigation-testing:$nav_version"
    }
    

    如果要使用Safe Args的话,还需要添加插件依赖,
    在项目的build.gradle文件中添加依赖:

    dependencies {
        ......
        
        def nav_version = "2.3.0-alpha01"
        classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
    }
    

    在app的build.gradle文件最上面添加依赖:

    //适用于 Kotlin 独有的模块的 Kotlin 代码
    apply plugin: "androidx.navigation.safeargs.kotlin"
    //或
    //适用于 Java 或 Java 和 Kotlin 混合模块的 Java 语言代码
    //apply plugin: "androidx.navigation.safeargs"
    

    使用步骤
    1、创建导航图
    2、添加导航宿主NavHost
    3、添加目的地
    4、连接目的地

    创建导航图
    右键点击res目录–>选择New–>选择Android Resource File
    在这里插入图片描述

    创建一个导航图文件app_graph.xml
    在这里插入图片描述

    在资源目录下,生成一个navigation导航文件夹和一个app_graph.xml导航图文件
    在这里插入图片描述

    添加导航宿主NavHost
    打开主Activity布局文件
    在这里插入图片描述

    添加fragment,通过设置属性,将主布局与导航图关联

    <androidx.constraintlayout.widget.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:id="@+id/fm_nva_host"
            android:name="androidx.navigation.fragment.NavHostFragment"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:defaultNavHost="true"
            app:navGraph="@navigation/app_graph" />
    
    </androidx.constraintlayout.widget.ConstraintLayout>
    
    android:name  固定值(也可以自己实现NavHostFragment),NavHost 实现的类名称
    app:navGraph 设置导航图文件,将 NavHostFragment 与导航图相关联
    app:defaultNavHost="true" 拦截系统返回按钮,多级Fragment界面,逐级返回
    

    添加目的地
    如果没有提前创建fragment界面,需要创建一个新的frament(目的地),
    如果创建过,则可以直接选择已经创建的fragment界面
    在这里插入图片描述

    创建一个新的fragment界面
    在这里插入图片描述
    在这里插入图片描述

    连接目的地
    鼠标按住连接点,拖动到目的地就可以连接目的地
    在这里插入图片描述

    连接目的地会生成如下代码:

    <?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"
        android:id="@+id/nva_graph"
        app:startDestination="@id/blankFragment"
        tools:ignore="UnusedNavigation">
    
        <fragment
            android:id="@+id/blankFragment"
            android:name="com.zzs.jetpack_navigation.BlankFragment"
            android:label="fragment_blank"
            tools:layout="@layout/fragment_blank" >
            //action相当于连接线
            <action
                android:id="@+id/action_blankFragment_to_blankFragment2"
                app:destination="@id/blankFragment2" />
        </fragment>
        <fragment
            android:id="@+id/blankFragment2"
            android:name="com.zzs.jetpack_navigation.BlankFragment2"
            android:label="fragment_blank_fragment2"
            tools:layout="@layout/fragment_blank2" >
        </fragment>
    </navigation>
    

    界面跳转
    界面跳转有有多种方式,下面介绍两种:
    1、action跳转(本质上也是id跳转),需实现Safe Args插件依赖

    tv_jump.setOnClickListener {
        val action = BlankFragmentDirections.actionBlankFragmentToBlankFragment2()
        it.findNavController().navigate(action)
    }
    

    actionBlankFragmentToBlankFragment2实际上就是连接目的地后生成的action中的id的驼峰式命名
    actionBlankFragmentToBlankFragment2()方法的源码:

    class BlankFragmentDirections private constructor() {
      companion object {
        fun actionBlankFragmentToBlankFragment2(): NavDirections =
            ActionOnlyNavDirections(R.id.action_blankFragment_to_blankFragment2)
      }
    }
    

    2、id跳转
    action_blankFragment_to_blankFragment2就是连接目的地后生成的action中的id

    tv_jump.setOnClickListener {
        it.findNavController().navigate(R.id.action_blankFragment_to_blankFragment2)
    }
    

    界面返回
    按下返回键可直接返回上一级界面,不需要处理
    如果需要主动返回上一级界面,可通过如下方式:

    tv_back.setOnClickListener {
        it.findNavController().navigateUp()
        //或者
        //it.findNavController().popBackStack()
    }
    

    navigateUp和popBackStack都可以返回上一级
    区别:
    navigateUp 如果当前的返回栈是空会停留在当前页面
    popBackStack 如果当前的返回栈是空会报错

    转场动画
    设置转场动画有两种方式:
    1、手动代码设置
    在导航图文件中,添加如下代码:

    <fragment
        android:id="@+id/blankFragment"
        android:name="com.zzs.jetpack_navigation.BlankFragment"
        android:label="fragment_blank"
        tools:layout="@layout/fragment_blank" >
        <action
            android:id="@+id/action_blankFragment_to_blankFragment2"
            app:destination="@id/blankFragment2"
            app:enterAnim="@anim/anim_in_enter"
            app:exitAnim="@anim/anim_in_exit"
            app:popEnterAnim="@anim/anim_out_enter"
            app:popExitAnim="@anim/anim_out_exit" />
    </fragment>
    

    enterAnim 和 exitAnim 是往栈里添加一个 目的地 时两个 目的地 的动画
    popEnterAnim 和 popExitAnim 是从栈里移除一个 目的地 时的动画

    2、Degisn设计页属性设置
    在这里插入图片描述

    转场动画文件
    在资源文件中,新建anim文件夹,添加如下文件:
    anim_in_enter.xml

    <?xml version="1.0" encoding="utf-8"?>
    <set xmlns:android="http://schemas.android.com/apk/res/android">
        <translate
            android:duration="200"
            android:fromXDelta="100%p"
            android:toXDelta="0%p" />
    </set>
    

    anim_in_exit.xml

    <?xml version="1.0" encoding="utf-8"?>
    <set xmlns:android="http://schemas.android.com/apk/res/android">
        <translate
            android:duration="200"
            android:fromXDelta="0%p"
            android:toXDelta="-100%p" />
    </set>
    

    anim_out_enter.xml

    <?xml version="1.0" encoding="utf-8"?>
    <set xmlns:android="http://schemas.android.com/apk/res/android">
        <translate
            android:duration="200"
            android:fromXDelta="-100%p"
            android:toXDelta="0%p" />
    </set>
    

    anim_out_exit.xml

    <?xml version="1.0" encoding="utf-8"?>
    <set xmlns:android="http://schemas.android.com/apk/res/android">
        <translate
            android:duration="200"
            android:fromXDelta="0%p"
            android:toXDelta="100%p" />
    </set>
    

    传递参数
    传递参数有两种方式:
    1、Safe Args传值,需实现Safe Args插件依赖
    在导航图文件中设置传递的参数,两种方式
    ①手动添加代码

    <fragment
        android:id="@+id/blankFragment"
        android:name="com.zzs.jetpack_navigation.BlankFragment"
        android:label="fragment_blank"
        tools:layout="@layout/fragment_blank" >
        <action
            android:id="@+id/action_blankFragment_to_blankFragment2"
            app:destination="@id/blankFragment2"/>
        <argument
            android:name="name"
            app:argType="string"
            android:defaultValue="张三" />
        <argument
            android:name="sex"
            app:argType="string"
            android:defaultValue="" />
        <argument
            android:name="age"
            app:argType="integer"
            android:defaultValue="20" />
    </fragment>
    
    ②在Design设计图中通过属性添加
    

    在这里插入图片描述

    添加完了之后,需要重新编译项目,通过插件生成对应的文件
    在这里插入图片描述
    在这里插入图片描述

    在目的地BlankFragment2中接收参数

     val args: BlankFragmentArgs by navArgs()
    
        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)
            getArgs()
        }
    
        private fun getArgs() {
            val name = args.name
            val sex = args.sex
            val age = args.age
            tv_agrs.text =
                "${resources.getString(R.string.send_args)}\nname=${name}\nsex=$sex\nage=$age"
        }
    

    如果navArgs()报红,需要在在app的build.gradle文件中添加如下代码:

    android {
        ......
        
        compileOptions {
            sourceCompatibility = 1.8
            targetCompatibility = 1.8
        }
    
        kotlinOptions {
            jvmTarget = "1.8"
        }
    
    }
    
    注意:我用Safe Args传值,接收到的中文参数都显示为乱码,目前没有找到解决办法
    

    2、Bundle传值
    在传值目的地BlankFragment中,代码如下:

    tv_jump.setOnClickListener {
        val bundle = bundleOf(
            "name" to "张三",
            "sex" to "男",
            "age" to 20
        )
        it.findNavController().navigate(R.id.action_blankFragment_to_blankFragment2, bundle)
    }
    

    在接收目的地BlankFragment2中,代码如下:

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)
            getArgs()
        }
    
        private fun getArgs() {
            val name = arguments?.getString("name")
            val sex = arguments?.getString("sex")
            val age = arguments?.getInt("age")
            tv_agrs.text =
                "${resources.getString(R.string.send_args)}\nname=${name}\nsex=$sex\nage=$age"
        }
    

    在这里插入图片描述

    项目地址:https://github.com/zhangzs1994/Jetpack_Navigation

    展开全文
  • 1、UINavigationController导航控制器如何使用UINavigationController可以翻译为导航控制器,在iOS里经常用到。我们看看它的如何使用:下面的图显示了导航控制器的流程。...相应地,在对象管理上,导航控制器使用了
  • Navigation 详解二

    2019-04-19 06:44:41
    配合 BottomNavigationView 创建 Bottom Navigation Activity BottomNavigationView 以更简洁的方式来实现过去的 BottomNavigationBar 的样式。Android Studio 中创建一个 Bottom Navigation Activity,自动生成 ...
  • 手把手教你使用Android官方组件Navigation

    万次阅读 热门讨论 2018-06-01 18:39:44
    Google 2018 I/O大会上,谷歌隆重推出一个新的架构组件:Navigation。从名字上可以看出他是一个导航,其实我们可以把它理解为FragmentNavigation。它提供了多Fragment之间的转场,栈管理,帮助你更轻松的使用Fragment。...
  • Navigation简单使用

    2020-07-14 00:26:26
    Navigation简单使用 针对Jitpack 组件中 Navigation的简单使用说明。 添加依赖 需要在对应模块层级的build.gradle文件中添加依赖: //版本指定为当时的最新稳定版本 ext.navigationVersion = "2.0.0" dependencies {...
  • Navigation实践总结

    2020-10-05 13:57:41
    Navigation作为Jetpack四大组件之一,可以为单Activity多Fragment结构提供重要支持。我们知道Activity是属于比较重的组件,而Fragment是比较轻量化的,因此这种结构对界面性能方面有很大影响 但是每种框架都有他的...
  • 欢迎大家关注【跨平台开发那些事】公众号,定期推送跨平台开发技术实践。 ... 基于最新版本React Native实现JsBundle预加载,界面秒开优化 ...今年1月份,新开源的react-natvigation库备受瞩目。...
  • navigation

    2006-02-16 11:27:00
    /OrderError.jspretry/PlaceOrder.jsp/PlaceOrder.jspsuccess/ConfirmOrder.jspfailure/OrderError.jsp
  • Navigation

    2018-05-30 10:05:51
    Navigation
  • BottomNavigationView+ViewPager+Fragment 底部导航按钮

    万次阅读 多人点赞 2019-07-23 00:10:08
    博主声明: 转载请在开头附加本文链接及作者信息,并标记为转载。本文由博主威威喵原创,请多支持与指教。 ... 感受 Material Design UI 魅力,你不能错过的Material Design 全系列文章: ...Material Design 之一 ...
  • navigationdrawer

    千次下载 热门讨论 2013-10-10 15:17:31
    Navigation Drawer是从屏幕的左侧滑出,显示应用导航的视图 http://blog.csdn.net/xyz_lmn/article/details/12523895
  • 在VUE中路由遇到Error: Avoided redundant navigation to current location:报错显示是路由重复, 虽然对项目无影响,但是看到有红的还是不舒服。 于是查了一下发现可以这样解决 在你引入VueRouter的时候再加上一句...
  • 前言 我在项目中尽量避免 使用 和 管理 Fragment, 尤其是处理Fragment的 多重嵌套 和 回退栈的情况。所幸有Activity在,我绕过了很多 Fragment 复杂的使用场景——必须承认,相比Activity的简单易上手,Fragment的...
  • 转载来自React Navigation(需要科学上网):https://reactnavigation.org/docs/navigators/navigation-prop Screen Navigation Prop:  Each screen in your app will receive a navigation prop which contain the...
  • Navigation使用

    2020-08-04 17:41:52
    implementation 'androidx.navigation:navigation-fragment:2.3.0' implementation 'androidx.navigation:navigation-ui:2.3.0' 2、res 下 创建 navigation 资源文件夹 3、navigation 资源文件夹下创建资源文件...
  • Grewal的第三版Global Navigation Satellite Systems, Inertial Navigation, and Integration [ISBN: 978-1118447000]。 An updated guide to GNSS, and INS, and solutions to real-world GNSS/INS problems with...
  • Android 自己实现 NavigationView [Design Support Library(1)]

    万次阅读 多人点赞 2015-06-30 14:13:22
    转载请标明出处: [http://blog.csdn.net/lmj623565791/article/details/46678867]... 本文出自:【张鸿洋的博客】 一、概述周末游戏打得过猛,于是周天熬夜码代码,周一早上浑浑噩噩的发现android-percent-s

空空如也

1 2 3 4 5 ... 20
收藏数 139,976
精华内容 55,990
关键字:

navigation