navigation_navigationbar - CSDN
  • Android Navigation 详解

    2019-08-18 22:01:19
    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>

    在导航图中编辑

    展开全文
  • Navigation的使用

    2020-03-19 23:28:01
    Navigation 文章目录NavigationMainActivity中 activity_main.xml 资源:建立导航图建立、连接目的地导航到目的地手势导航边缘到边缘的应用程序内容设置透明系统栏设置UI可见性标志 使用条件: AndroidStudio version ...

    Navigation


    使用条件: AndroidStudio version >= 3.3

    一种交互允许用户在应用程序中的不同内容之间进行浏览,浏览和退出。

    导航组件由以下三个关键部分组成:

    • 导航图:在一个集中位置包含所有与导航有关的信息的一种XML资源。这包括您应用程序中的所有单独内容区域(称为destinations),以及用户可以通过您的应用程序访问的可能路径。

    • NavHost:一个空容器,用于显示导航图中的目的地。导航组件包含一个默认NavHost实现 NavHostFragment,它显示片段目的地。

    • NavController: 在NavHost中管理应用程序导航的对象 。 当用户在整个应用程序中移动时,NavController在NavHost中协调目标内容的交换 。

      MainActivity中 activity_main.xml 资源:

    <?xml version="1.0" encoding="utf-8"?>
    <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">
    
        <androidx.appcompat.widget.Toolbar
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            tools:ignore="MissingConstraints"
            />
    
        <fragment
            android:id="@+id/nav_host_fragment"
            android:name="androidx.navigation.fragment.NavHostFragment"
            android:layout_width="0dp"
            android:layout_height="0dp"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            tools:ignore="MissingConstraints"
            app:defaultNavHost= "true"
            app:navGraph="@navigation/nav_graph"
            />
    
    </androidx.constraintlayout.widget.ConstraintLayout>
    
    • android:name属性包含您的NavHost 实现的类名称。
    • app:navGraph属性将关联到NavHostFragment导航图。导航图指定了NavHostFragment用户可以导航到的所有目的地。
    • app:defaultNavHost="true"属性确保您NavHostFragment 拦截系统的“后退”按钮。请注意,NavHost默认值只能是一个。如果您在同一布局中有多个主机(例如,两窗格布局),请确保仅指定一个default NavHost

    建立导航图

    1. 在“项目”窗口中,右键单击res目录,然后选择“ 新建”>“ Android Resource File ”。出现“ 新资源文件”对话框。
    2. 在“ File name ”字段中键入一个名称,例如“ nav_graph”。
    3. 从资源类型下拉列表中选择 Navigation ,然后单击确定
    <?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/nav_graph">
    
    </navigation>
    

    建立、连接目的地

    通过nav_graph中的Design 选项,点击白绿“+”,出现"Create new destination",可以建立Fragment

    然后可以通过“ 房子 ”符号或者右键set as Start destination建立启动目的地

    连接目的地,只要选中Design区域的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/nav_graph"
        app:startDestination="@id/blankFragment">
    
        <fragment
            android:id="@+id/blankFragment"
            android:name="com.fight.navigation.BlankFragment"
            android:label="fragment_blank"
            tools:layout="@layout/fragment_blank" >
            <action
                android:id="@+id/action_blankFragment_to_whiteFragment"
                app:destination="@id/whiteFragment" />
        </fragment>
        <fragment
            android:id="@+id/whiteFragment"
            android:name="com.fight.navigation.BlankFragment"
            android:label="fragment_white"
            tools:layout="@layout/fragment_blank">
          
        </fragment>
    
    </navigation>
    

    导航到目的地

    Java

    手势导航

    从Android 10(API级别29)开始,Android系统支持完全基于手势的导航。应用程序开发人员应做两件事以确保其应用程序与此功能兼容:

    • 将应用程序内容从一个边缘扩展到另一个边缘。
    • 处理冲突的应用手势
    边缘到边缘的应用程序内容

    为了利用浮动导航栏提供的额外屏幕空间,您需要对应用程序进行一些更改。

    设置透明系统栏

    您可以通过在主题中设置以下值来实现( API目标级别为29或更高 ):

    边缘到边缘的应用程序内容
    为了利用浮动导航栏提供的额外屏幕空间,您需要对应用程序进行一些更改。
    
    设置透明系统栏
    您可以通过在主题中设置以下值来实现:
    
    <!-- values-29/themes.xml: -->
    
    <style name="AppTheme" parent="...">
        <item name="android:navigationBarColor">@android:color/transparent</item>
    
        <!-- Optional, but recommended for full edge-to-edge rendering -->
        <item name="android:statusBarColor">@android:color/transparent</item>
    </style>
    

    或者 可以使用Window.setNavigationBarColor()Window.setStatusBarColor() 来动态地执行此操作

    设置UI可见性标志

    为了能够边到边布置视图,应用必须告知系统该应用可以处理这种视图。您可以通过View.setSystemUiVisibility() 设置以下标志来完成此操作 :

    STABLE)

    这些标志一起告诉系统,你的应用程序应该全屏显示,就像导航栏和状态栏不存在一样。对于其他全屏事件,您还可以设置SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN,它允许您在状态栏后面绘图。

    展开全文
  • 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

    展开全文
  • Google 2018 I/O大会上,谷歌隆重推出一个新的架构组件:Navigation。从名字上可以看出他是一个导航,其实我们可以把它理解为FragmentNavigation。它提供了多Fragment之间的转场,栈管理,帮助你更轻松的使用Fragment。...

    Google 2018 I/O大会上,谷歌隆重推出一个新的架构组件:Navigation。从名字上可以看出他是一个导航,其实我们可以把它理解为FragmentNavigation。它提供了多Fragment之间的转场,栈管理,帮助你更轻松的使用Fragment。在以后抽屉式导航栏,底部、顶部导航栏的需求中我们可以尝试使用这个新组件。甚至,你可以尝试写一个单Activity的应用。

    下面是我写的一个Sample,我将引导大家去实现这个小功能。


    使用Navigation需要AndroidStudio的版本3.2以上,下面的网址可以下载最新的AndroidStudio

    http://www.androiddevtools.cn/

    第一步:创建两个Fragment,FristFragment和SecondFragment

    <?xml version="1.0" encoding="utf-8"?>
    <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".FirstFragment">
    
        <!-- TODO: Update blank fragment layout -->
        <TextView
            android:layout_marginTop="100dp"
            android:layout_gravity="center_horizontal"
            android:text="我是第一个Fragment"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
    
        <Button
            android:textAllCaps="false"
            android:id="@+id/btn_next"
            android:layout_gravity="center"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="跳转到第二个Fragment" />
    
    </FrameLayout>
    <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        tools:context=".SecondFragment">
    
        <!-- TODO: Update blank fragment layout -->
    
        <TextView
            android:layout_gravity="center_horizontal"
            android:layout_marginTop="100dp"
            android:text="我是第二个Fragment"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
    
        <Button
            android:layout_gravity="center"
            android:id="@+id/btn_back"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="返回" />
    
    </FrameLayout>

    第二步:创建导航视图文件

    在res目录下新建navigation文件夹,然后新建一个navigation资源文件(需要AndroidStudio3.2以上版本)


    打开资源文件,切换为design可视化编辑模式,在这个界面,我们可以任意添加Fragment和Activity,用箭头线连接它们,就为它们绑定了跳转事件。


    切换为Text模式,下面就是自动编辑好的代码。action标签描述的Fragment转场事件,在里面我们除了可以设置目标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/firstFragment">
    
        <fragment
            android:id="@+id/firstFragment"
            android:name="com.example.navigation.FirstFragment"
            android:label="fragment_first"
            tools:layout="@layout/fragment_first" >
            <action
                android:id="@+id/action_firstFragment_to_secondFragment"
                app:destination="@id/secondFragment" />
        </fragment>
        <fragment
            android:id="@+id/secondFragment"
            android:name="com.example.navigation.SecondFragment"
            android:label="fragment_second"
            tools:layout="@layout/fragment_second" >
        </fragment>
    </navigation>

    第三步:编辑MainActivity

    在Acitvity布局文件中添加fragment组件,设置name属性为androidx.navigation.fragment.NavHostFragment。在传统的单Activity多Fragment场景中,我们往往需要为Activity添加一个FrameLayout作为Fragment的容器。在Navigation中HavHostFragment就是Fragment的容器。布局中的navGraph属性建立了与navigation资源文件的关系。

    <?xml version="1.0" encoding="utf-8"?>
    <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">
    
       <fragment
           android:name="androidx.navigation.fragment.NavHostFragment"
           android:id="@+id/fragment"
           android:layout_width="0dp"
           android:layout_height="0dp"
           app:layout_constraintBottom_toBottomOf="parent"
           app:layout_constraintLeft_toLeftOf="parent"
           app:layout_constraintRight_toRightOf="parent"
           app:layout_constraintTop_toTopOf="parent"
           app:defaultNavHost="true"
           app:navGraph="@navigation/nav_fragment" />
    
    </android.support.constraint.ConstraintLayout>

    重写onSupportNavigateUp()方法,目的是将back事件委托出去。若栈中有两个以上Fragment,点击back键就会返回到上一个Fragment。

    public class MainActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
        }
    
        @Override
        public boolean onSupportNavigateUp() {
            Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment);
            return NavHostFragment.findNavController(fragment).navigateUp();
        }
    }

    第四步:在Fragment中设置相应的跳转事件。

    public class FirstFragment extends Fragment {
    
    
        public FirstFragment() {
            // Required empty public constructor
        }
    
    
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                                 Bundle savedInstanceState) {
            // Inflate the layout for this fragment
            return inflater.inflate(R.layout.fragment_first, container, false);
        }
    
        @Override
        public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
            super.onViewCreated(view, savedInstanceState);
            Button btnNext = view.findViewById(R.id.btn_next);
            btnNext.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Navigation.findNavController(v).navigate(R.id.action_firstFragment_to_secondFragment);
                }
            });
        }
    }
    public class SecondFragment extends Fragment {
    
    
        public SecondFragment() {
            // Required empty public constructor
        }
    
    
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                                 Bundle savedInstanceState) {
            // Inflate the layout for this fragment
            return inflater.inflate(R.layout.fragment_second, container, false);
        }
    
        @Override
        public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
            super.onViewCreated(view, savedInstanceState);
            Button btnBack = view.findViewById(R.id.btn_back);
            btnBack.setOnClickListener(new View.OnClickListener(){
    
                @Override
                public void onClick(View v) {
                    Navigation.findNavController(v).navigateUp();
                }
            });
        }
    }

    可以看到,关键API就是一行,界面切换如此简单。如果需要传值,把数据设置在Bundle中,使用以下api即可

    Navigation.findNavController(view).navigate(actionid,bundle);

    至此,我们就可以把程序运行起来。如果需要添加转场动画,需要在navigation资源文件的action标签下,添加动画属性。

    <action
                app:enterAnim="@anim/slide_in_right"
                app:exitAnim="@anim/slide_out_left"
                app:popEnterAnim="@anim/slide_in_left"
                app:popExitAnim="@anim/slide_out_right"
                android:id="@+id/action_firstFragment_to_secondFragment"
                app:destination="@id/secondFragment" />

    哇!跳转,传值,管理栈,还有转场动画!简直跟Activity的使用一模一样。从此,Fragment除了拥有自身的优点之外,还get了Activity的技能,我们在构建App的时候是不是可以更加肆意得使用Fragment呢?


    展开全文
  • Navigation 详解二

    2019-04-20 11:03:25
    创建 Bottom Navigation Activity BottomNavigationView 以更简洁的方式来实现过去的 BottomNavigationBar 的样式。Android Studio 中创建一个 Bottom Navigation Activity,自动生成 Activity。 class ...

    配合 BottomNavigationView

    创建 Bottom Navigation Activity

    BottomNavigationView 以更简洁的方式来实现过去的 BottomNavigationBar 的样式。Android Studio 中创建一个 Bottom Navigation Activity,自动生成 Activity。

    class BottomNavigationActivity : AppCompatActivity() {
    
        private val mOnNavigationItemSelectedListener = BottomNavigationView.OnNavigationItemSelectedListener { item ->
            when (item.itemId) {
                R.id.navigation_home -> {
                    message.setText(R.string.title_home)
                    return@OnNavigationItemSelectedListener true
                }
                R.id.navigation_dashboard -> {
                    message.setText(R.string.title_dashboard)
                    return@OnNavigationItemSelectedListener true
                }
                R.id.navigation_notifications -> {
                    message.setText(R.string.title_notifications)
                    return@OnNavigationItemSelectedListener true
                }
            }
            false
        }
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_bottom_navigation)
    
            navigation.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener)
        }
    }
    
    <!-- activity_bottom_navigation.xml -->
    <?xml version="1.0" encoding="utf-8"?>
    <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".navigation.BottomNavigationActivity">
    
        <TextView
            android:id="@+id/message"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="@dimen/activity_horizontal_margin"
            android:layout_marginLeft="@dimen/activity_horizontal_margin"
            android:layout_marginTop="@dimen/activity_vertical_margin"
            android:text="@string/title_home"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    
        <android.support.design.widget.BottomNavigationView
            android:id="@+id/navigation"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginStart="0dp"
            android:layout_marginEnd="0dp"
            android:background="?android:attr/windowBackground"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:menu="@menu/navigation" />
    
    </android.support.constraint.ConstraintLayout>
    
    <!-- menu/navigation.xml -->
    <?xml version="1.0" encoding="utf-8"?>
    <menu xmlns:android="http://schemas.android.com/apk/res/android">
    
        <item
            android:id="@+id/navigation_home"
            android:icon="@drawable/ic_home_black_24dp"
            android:title="@string/title_home" />
    
        <item
            android:id="@+id/navigation_dashboard"
            android:icon="@drawable/ic_dashboard_black_24dp"
            android:title="@string/title_dashboard" />
    
        <item
            android:id="@+id/navigation_notifications"
            android:icon="@drawable/ic_notifications_black_24dp"
            android:title="@string/title_notifications" />
    
    </menu>
    

    创建 Fragment

    res/navigation 文件夹右击选择 New -> Navigation resource file,创建 bottom_nav_graph.xml 文件。在 design 视图下连续 Create blank destination 三次创建三个 Fragment。

    <!-- bottom_nav_graph.xml -->
    <?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/bottom_nav_graph"
        app:startDestination="@id/homeFragment">
    
        <fragment
            android:id="@+id/homeFragment"
            android:name="pot.ner347.androiddemo.HomeFragment"
            android:label="fragment_home"
            tools:layout="@layout/fragment_home" />
        <fragment
            android:id="@+id/dashboardFragment"
            android:name="pot.ner347.androiddemo.DashboardFragment"
            android:label="fragment_dashboard"
            tools:layout="@layout/fragment_dashboard" />
        <fragment
            android:id="@+id/notificationFragment"
            android:name="pot.ner347.androiddemo.NotificationFragment"
            android:label="fragment_notification"
            tools:layout="@layout/fragment_notification" />
    </navigation>
    

    修改三个 Fragment 里面的 TextView,以区分三者。

    BottomNavigationView 引起 Fragment 切换

    bottom_nav_graph.xml 中 fragment 的 id 必须和 menu 中 item 的 id 保持一致。修改 menu/navigation.xml 中的三个 id。

    <?xml version="1.0" encoding="utf-8"?>
    <menu xmlns:android="http://schemas.android.com/apk/res/android">
    
        <item
            android:id="@id/homeFragment"
            android:icon="@drawable/ic_home_black_24dp"
            android:title="@string/title_home" />
    
        <item
            android:id="@id/dashboardFragment"
            android:icon="@drawable/ic_dashboard_black_24dp"
            android:title="@string/title_dashboard" />
    
        <item
            android:id="@id/notificationFragment"
            android:icon="@drawable/ic_notifications_black_24dp"
            android:title="@string/title_notifications" />
    
    </menu>
    

    实际发现这个 id 用 @+id 也行。

    在 BottomNavigationActivity 的 xml 里添加一个 fragment

    <fragment
        android:id="@+id/nav_fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        app:defaultNavHost="true"
        app:navGraph="@navigation/bottom_nav_graph"
        ... />
    

    代码关联

    val navHostFragment = nav_fragment as NavHostFragment
    NavigationUI.setupWithNavController(navigation, navHostFragment.navController)
    

    遇到的一个坑:无法切换 Fragment,看源码

    public static void setupWithNavController(
            @NonNull final BottomNavigationView bottomNavigationView,
            @NonNull final NavController navController) {
        bottomNavigationView.setOnNavigationItemSelectedListener(
                new BottomNavigationView.OnNavigationItemSelectedListener() {
                    @Override
                    public boolean onNavigationItemSelected(@NonNull MenuItem item) {
                        return onNavDestinationSelected(item, navController, true);
                    }
                });
        navController.addOnNavigatedListener(new NavController.OnNavigatedListener() {
            @Override
            public void onNavigated(@NonNull NavController controller,
                    @NonNull NavDestination destination) {
                Menu menu = bottomNavigationView.getMenu();
                for (int h = 0, size = menu.size(); h < size; h++) {
                    MenuItem item = menu.getItem(h);
                    if (matchDestination(destination, item.getItemId())) {
                        item.setChecked(true);
                    }
                }
            }
        });
    }
    

    发现对 BottomNavigationView 加了切换监听去切换 Fragment,也对 Fragment 加了监听,能够自动切换 BottomNavigationView 的 item,互相监听变化。

    而自动创建 Bottom Navigation Activity 时生成的代码里有一句 navigation.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener),我又放在后面调用,相当于里面设置的监听被覆盖了,才导致无法切换。

    navigation_bottomnavigationview_1.gif

    Fragment 切换改变 BottomNavigationView 的 item

    在 destination 上随便拖了几个 action,如下图

    navigation_actions.png

    生成代码多了几个 action

    <?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/bottom_nav_graph"
        app:startDestination="@id/homeFragment">
    
        <fragment
            android:id="@+id/homeFragment"
            android:name="pot.ner347.androiddemo.navigation.HomeFragment"
            android:label="fragment_home"
            tools:layout="@layout/fragment_home" >
            <action
                android:id="@+id/action_homeFragment_to_notificationFragment"
                app:destination="@id/notificationFragment" />
            <action
                android:id="@+id/action_homeFragment_to_dashboardFragment"
                app:destination="@id/dashboardFragment" />
        </fragment>
        <fragment
            android:id="@+id/dashboardFragment"
            android:name="pot.ner347.androiddemo.navigation.DashboardFragment"
            android:label="fragment_dashboard"
            tools:layout="@layout/fragment_dashboard" >
            <action
                android:id="@+id/action_dashboardFragment_to_notificationFragment"
                app:destination="@id/notificationFragment" />
        </fragment>
        <fragment
            android:id="@+id/notificationFragment"
            android:name="pot.ner347.androiddemo.navigation.NotificationFragment"
            android:label="fragment_notification"
            tools:layout="@layout/fragment_notification" />
    </navigation>
    

    然后修改 Fragment 的代码:

    <!-- fragment_home.xml -->
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        tools:context=".navigation.HomeFragment">
    
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="home_fragment" />
    
        <Button
            android:id="@+id/homeToDashboard"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textAllCaps="false"
            android:text="to dashboard"/>
    
        <Button
            android:id="@+id/homeToNotification"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textAllCaps="false"
            android:text="to notification"/>
    </LinearLayout>
    
    class HomeFragment : Fragment() {
    
        override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                                  savedInstanceState: Bundle?): View? {
            return inflater.inflate(R.layout.fragment_home, container, false)
        }
    
        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)
    
            homeToDashboard.onClick {
                NavHostFragment.findNavController(this@HomeFragment) // NavHostFragment 方式获取 NavController
                        .navigate(R.id.dashboardFragment) // 根据 fragment 的 id 跳转
            }
            homeToNotification.onClick {
                Navigation.findNavController(view) // Navigation 方式获取 NavController
                        .navigate(R.id.action_homeFragment_to_notificationFragment) // 根据 action 的 id 跳转
            }
        }
    
    }
    
    <!-- fragment_dashboard.xml -->
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        tools:context=".navigation.HomeFragment">
    
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="dashboard_fragment" />
    
        <Button
            android:id="@+id/dashboardToNotification"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textAllCaps="false"
            android:text="to notification"/>
        
    </LinearLayout>
    
    class DashboardFragment : Fragment() {
    
        override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                                  savedInstanceState: Bundle?): View? {
            return inflater.inflate(R.layout.fragment_dashboard, container, false)
        }
    
        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)
            dashboardToNotification.onClick {
                Navigation.findNavController(view).navigate(R.id.notificationFragment)
            }
        }
    }
    

    navigation_bottomnavigationview_2.gif

    下面的 item 跟着切换。

    返回

    Activity 中 defaultNavHost 设为 true,默认的按返回键就是按进入栈的顺序退出。

    可以通过 NavController 的 navigateUp 方法主动退出。

    修改 fragment_notification.xml 为

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        tools:context=".navigation.HomeFragment">
    
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="notification_fragment" />
    
        <Button
            android:id="@+id/back"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textAllCaps="false"
            android:text="back"/>
    
    </LinearLayout>
    

    在 NotificationFragment 里添加如下方法:

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        back.onClick {
            Navigation.findNavController(view).navigateUp()
        }
    }
    

    执行效果是从哪个 Fragment 跳转过去,就是返回到哪个。

    navigation_back.gif

    navigateUp 看源码可知如果栈里有多个页面,调用 popBackStack,如果只有一个,是调用了所依附的 Activity 的 finish 方法。

    public boolean popBackStack(@IdRes int destinationId, boolean inclusive)
    

    popBackStack 的另一个重载方法,可以指定回到哪个 Fragment,即可以一次关闭多个页面。inclusive 表示 destinationId 这个页面是否也要弹出关闭。

    NotificationFragment 中再添加两个 Button

    <Button
        android:id="@+id/backToHome"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAllCaps="false"
        android:text="backToHome"/>
    
    <Button
        android:id="@+id/backToHomeInclusive"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAllCaps="false"
        android:text="backToHomeInclusive"/>
    
    backToHome.onClick {
        Navigation.findNavController(view).popBackStack(R.id.homeFragment, false)
    }
    backToHomeInclusive.onClick {
        Navigation.findNavController(view).popBackStack(R.id.homeFragment, true)
    }
    

    然后从 Home 到 Dashboard,从 Dashboard 再到 Notification,即 NotificationFragment 下面还有两个 Fragment。结果效果一样,都是回到了 HomeFragment,但是看栈有区别。修改代码为

    
    class NotificationFragment : Fragment() {
    
        lateinit var navController : NavController
    
        override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                                  savedInstanceState: Bundle?): View? {
            navController = NavHostFragment.findNavController(this@NotificationFragment)
            return inflater.inflate(R.layout.fragment_notification, container, false)
        }
    
        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)
            back.onClick { Navigation.findNavController(view).navigateUp() }
            backToHome.onClick { pop(false) }
            backToHomeInclusive.onClick { pop(true) }
        }
    
        fun pop(inclusive : Boolean) {
            printBackStack("inclusive=$inclusive before pop -----")
            navController.popBackStack(R.id.homeFragment, inclusive)
            printBackStack("inclusive=$inclusive after pop ------")
        }
    
        fun printBackStack(pre : String) {
            val mBackStack : Deque<NavDestination> = NavController::class.java.getDeclaredField("mBackStack").apply {
                isAccessible = true
            }.get(navController) as Deque<NavDestination>
            mBackStack.log(pre)
        }
    
        fun Deque<NavDestination>.log (pre : String) {
            if (this.isEmpty()) {
                Log.e("OMG", "$pre back stack size 0")
            } else {
                this.forEach {
                    Log.e("OMG", "$pre ${it.label},id:${it.id}")
                }
            }
        }
    }
    

    日志显示:

    2018-09-05 18:32:15.869 17142-17142/pot.ner347.androiddemo E/OMG: inclusive=false before pop ----- null,id:2131230766
    2018-09-05 18:32:15.869 17142-17142/pot.ner347.androiddemo E/OMG: inclusive=false before pop ----- fragment_home,id:2131230946
    2018-09-05 18:32:15.870 17142-17142/pot.ner347.androiddemo E/OMG: inclusive=false before pop ----- fragment_dashboard,id:2131230909
    2018-09-05 18:32:15.870 17142-17142/pot.ner347.androiddemo E/OMG: inclusive=false before pop ----- fragment_notification,id:2131230990
    2018-09-05 18:32:15.917 17142-17142/pot.ner347.androiddemo E/OMG: inclusive=false after pop ------ null,id:2131230766
    2018-09-05 18:32:15.917 17142-17142/pot.ner347.androiddemo E/OMG: inclusive=false after pop ------ fragment_home,id:2131230946
    2018-09-05 18:32:25.387 17142-17142/pot.ner347.androiddemo E/OMG: inclusive=true before pop ----- null,id:2131230766
    2018-09-05 18:32:25.388 17142-17142/pot.ner347.androiddemo E/OMG: inclusive=true before pop ----- fragment_home,id:2131230946
    2018-09-05 18:32:25.388 17142-17142/pot.ner347.androiddemo E/OMG: inclusive=true before pop ----- fragment_dashboard,id:2131230909
    2018-09-05 18:32:25.388 17142-17142/pot.ner347.androiddemo E/OMG: inclusive=true before pop ----- fragment_notification,id:2131230990
    2018-09-05 18:32:25.425 17142-17142/pot.ner347.androiddemo E/OMG: inclusive=true after pop ------ back stack size 0}
    

    看栈,当 inclusive 为 true 时确实把自己也弹出了,之所以还能看到 HomeFragment,应该是 Activity 的布局问题,一开始就显示了 NavHostFragment。

    修改 start destination

    navigation_change_start.png

    start destination 的名字前有个小房子,如果要修改,可以点击 destination,然后点击右侧的 Set Start Destination。看代码 app:startDestination 已经变了。

    比如把 NotificationFragment 设为 start destination,进入 Activity,点击下面的 Item 切换到 HomeFragment,然后再到 NotificationFragment,执行返回:

    2018-09-05 18:44:43.776 19162-19162/pot.ner347.androiddemo E/OMG: inclusive=true before pop ----- null,id:2131230766
    2018-09-05 18:44:43.776 19162-19162/pot.ner347.androiddemo E/OMG: inclusive=true before pop ----- fragment_notification,id:2131230990
    2018-09-05 18:44:43.776 19162-19162/pot.ner347.androiddemo E/OMG: inclusive=true before pop ----- fragment_home,id:2131230946
    2018-09-05 18:44:43.777 19162-19162/pot.ner347.androiddemo E/OMG: inclusive=true before pop ----- fragment_dashboard,id:2131230909
    2018-09-05 18:44:43.777 19162-19162/pot.ner347.androiddemo E/OMG: inclusive=true before pop ----- fragment_notification,id:2131230990
    2018-09-05 18:44:43.798 19162-19162/pot.ner347.androiddemo E/OMG: inclusive=true after pop ------ null,id:2131230766
    2018-09-05 18:44:43.798 19162-19162/pot.ner347.androiddemo E/OMG: inclusive=true after pop ------ fragment_notification,id:2131230990
    

     

    展开全文
  • Navigation框架介绍

    2019-03-08 18:14:46
    Navigation框架 概述 Navigation,导航, Google官方对它的描述: 今天,我们宣布推出Navigation组件,作为构建您的应用内界面的框架,重点是让单 Activity 应用成为首选架构。利用Navigation组件对 Fragment 的...
  • Navigation简单使用

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

    2020-04-06 21:55:04
    01. 02.
  • ROS与navigation教程.pdf

    2020-07-30 23:31:34
     ROS 与 navigation 教程-目录  ROS 与 navigation 教程-设置机器人使用 TF  ROS 与 navigation 教程-基本导航调试指南  ROS 与 navigation 教程-安装和配置导航包  ROS 与 navigation 教程-结合 RVIZ 与...
  • 上篇文章我们简单的介绍了Navigation组件的使用,以及深入分析了源码中的具体实现,基本原理我们已经很清晰了。本篇文章主要介绍下我在项目中遇到的问题,以及目前关于Navigation实现的一些探讨。还没有看过上篇文章...
  • Principles of GNSS, Inertial, and Multisensor Integrated Navigation Systems offers you a solid understanding of satellite navigation, inertial navigation, terrestrial radio navigation, dead reckoning,...
  • 在浏览器中我们可以通过&lt;a&gt;标签与url实现不同页面之间的跳转,利用浏览器的返回按钮返回之前浏览的页面,但是在React Native中却没有集成内嵌的全局栈来...通过如下方法安装react navigation到你的...
  • Hello React Navigation 在浏览器中,链接到其他页面你可以使用HTML锚点标签(anchor tag)&amp;amp;amp;lt;a&amp;amp;amp;gt;。当用户点击一个链接时,当前页面的URL被压到浏览器历史记录栈中。当用户点击了...
  • react-navigation的使用的巨坑 这个库安装到成功使用搞了大半天,弄得心态崩溃。主要是不好意思问别人这么基础的问题,只能自己一点点趟坑,不过搞定之后信心暴涨啊。这位兄台好像和我一样,哈哈,遇到同道中人了。 ...
  • 但是,本文给出一个更简单的例子,在机器人前方1m处设定目标点,文章内容几乎来自ROS navigation 官方wiki文档。 在进行下面的试验之前,希望你已经在自己的机器人上完成了使用move_base进行控制的任务。
  • github:https://github.com/zack24q/vue-navigation     需要 vue 2.x 与 vue-router 2.x。     导航默认行为类似手机APP的页面导航(A、B、C为页面): A前进到B,再前进到C; C返回到B时,B会...
  • 谷歌jetpack开源库更新了很多内容,最近比较感兴趣的时候其中新增的Navigation,专门用来实现单Activity多Fragment的方案,以前项目中要自己实现栈来维护,开源的可能就是鼎鼎大名的Fragmentation,可能感觉坑有点多...
  • 关于react-navigation,由于是社区维护的路由库,所以更新和问题会比较多,版本更新也比较快,我用的2个版本比较多,一个是beta7版本,现在master是beta11(截止到7月4日为止),关于react-navigation和redux进行绑定...
  • performance.navigation.type(该属性返回一个整数值,表示网页的加载来源,可能有以下4种情况):  0:网页通过点击链接、地址栏输入、表单提交、脚本操作等方式加载,相当于常数performance.navigation.TYPE_...
  • Estimation with Applications to Tracking and Navigation 【原书作者】: Yaakov Bar-Shalom, X.-Rong Li, Thiagalingam Kirubarajan 【ISBN 】: ISBN-10: 047141655X / ISBN-13: 978-0471416555 【页数 】:...
1 2 3 4 5 ... 20
收藏数 118,849
精华内容 47,539
关键字:

navigation