Navigation使用重顾

Navigation基本使用

  • 概述:

    • JetPack组件库共有差不多60个库

    • Navigation的初心:Android应该只有一个Activity

      • 一个Activity作为控制层,有许多Fragment显示页面(对比前端的脚手架)
  • Navigation概述:

    • 官网描述:用于在Android应用的目标之间导航,提供了一致的API,无论目标是Fragment还是Activity还是其他组件

    • 未引入Navigation:

      • 采用TableLayout或者Fragment手动管理
    • 主要功能:

      • 完成Fragment的导航,特别是在单个Activity配合多个Fragment实现
    • 项目架构:

      • 整体示意图

      图片.png

      • 细节:

        • 整体采用单个MainActivity+多个fragment(配合ViewModel),其中Activity只做控制,fragment展示页面

        • nav_graph_main.xml:导航图而已

          • 位置:res/navigation
          • 作用:指定所导航的fragment信息(id,name,label,layout,action)
        • 多个fragment实例:

          • 显示页面的具体UI
        • fragmenti :fragmentiViewModel = 1 :1

          • ViewModel(维护fragment的数据)
        • 在JetPack标准架构模型中仓库只有一个

代码实现:

  • 实现思路:

    1. 构建MainActivity:做控制

      1. onCreate之外:声明布局中的BottomNavigationView
      2. onCreate之内:实例化布局中的BottomNavigationView
      3. 获取navHostFragment
      4. 获取navController
      5. 绑定BottomNavigationView与navController
    2. 构建activity_main.xml

      1. 编写FragmentContainerView

        1. id
        2. name:NavHostFragment
        3. 屏蔽系统返回键,启用fragment任务栈:defaultNavHost=”true”
        4. 指定导航图:navGraph=”@navigation/nav_graph_main”
      2. 编写BottomNavigationView

        1. 内嵌menu
      3. 编写menu:完善item细节

        1. id
        2. icon
        3. title
    3. 编写导航图:

      1. 新建navigation包:res/navigation

      2. 新建导航图文件:nav_graph_main

      3. 向导航图填充fragment标签:真正用来展示界面的

      4. 完善fragment标签细节:

        1. 指定id:作为跳转地址

           android:id="@+id/page1Fragment"
          复制代码
        2. 指定name:反射执行

           android:name="com.derry.navigation.MainPage1Fragment"
          复制代码
        3. 指定label:fragment的标签

        4. 指定layout:fragment布局文件

        5. 新建action标签:指定跳转顺序(可以使用图形化界面玩)

           <action
               android:id="@+id/action_page2"
               app:destination="@id/page2Fragment" />
          复制代码

构建MainActivity与Fragment

  • 工程结构:

    image-20220105142241961

  • 代码实现:

    • MainPage1Fragment

      image-20220105142409979

      • 点击事件:跳转到fragment1

         val btn = view.findViewById<Button>(R.id.btn)
                 btn.setOnClickListener { view ->
             配合导航图进行跳转           Navigation.findNavController(view).navigate(R.id.action_page2)
                 }
        复制代码
    • MainPage2Fragment

      image-20220105142443729

      • 点击事件:跳转到fragment1、fragment2

         val btn = view.findViewById<Button>(R.id.btn)
                 btn.setOnClickListener { view ->
                    //配合导航图进行页面跳转
                                         Navigation.findNavController(view).navigate(R.id.action_page1)
                     // Navigation.findNavController(view).navigateUp(); 返回上一个Fragment
                 }
                 val btn2 = view.findViewById<Button>(R.id.btn2)
                 btn2.setOnClickListener { view ->
                     Navigation.findNavController(view).navigate(R.id.action_page3)
         ​
                 }
        复制代码
      • navigateUp():回退到上一个fragment(上次跳转的起点)

      • navigate(R.id.action_page1):指定id,进行跳转

      • 这两句话,效果一样

    • MainPage3Fragment

      image-20220105142517111

      • 点击事件:跳转到fragment2

         btn.setOnClickListener { view ->
             Navigation.findNavController(view).navigate(R.id.action_page2)
             // 回退上一步
             // Navigation.findNavController(view).navigateUp();
         }
        复制代码
  • 构建Navgation导航图:nav_gragh_main.xml

    • 原始res目录

      image-20220105142821850

    • 选中res—>右键—>New—>Android Resource File

      image-20220105142916010

    • type选择Navigation(会在res文件夹下自动新建一个navigation文件夹,存放新建 .xml)

      image-20220105143129443

    • 新建后的res资源目录

      image-20220309111354400

  • 建立导航图与Fragment之间的链接:导航图去寻找实例并且实例是在导航图之前建立的

    • 先点击res/navigation下的nav_graph_main.xml再点击屏幕右边的Design选项(切换到以下视图)

      image-20220105145456109

      • 细节:

        1. 当点击上图红色按钮后,可以在下拉列表中选择fragment实例并且第一个选择的实例作为导航图的默认启动页面(就是最上角有个小房子的哪个)
        1. 修改导航图默认启动页面:在Design视图中,默认启动页面左上角是有一个小房子的

          修改nav_graph_main.xml:app:startDestination=”@id/page1Fragment

           <navigation 
               ……
           <--! 此时导航图以page1Fragment为默认启动页面-->
               app:startDestination="@id/page1Fragment">
          复制代码
    • 通过拖动建立Fragment之间的跳转关系(指定action)

      • 效果图:

        image-20220105145818482

      • 实现细节:

        1. 如何建立视图之间的跳转关系?

          • 将鼠标悬停至跳转起点页面右侧,出现一个蓝色的圆圈,点一下(拉出一根线),将其连给在本次跳转的目标页面上即可

          • 代码描述:由page1Fragment跳转到page2Fragment

             <fragment
                …… >
                 <!--
                     action:程序中使用id跳到destination对应的类
                 -->
                 <action
                     android:id="@+id/action_page2"
                     app:destination="@id/page2Fragment" />
             </fragment>
            复制代码
        2. 为什么在单个fragment布局文件中name属性为此fragment的全类名?

          • 为了通过反射进行激活

             <fragment
                      android:id="@+id/page1Fragment"
                      android:name="com.wasbry.navigation.MainPage1Fragment"
                      android:label="fragment_page1"
                      tools:layout="@layout/fragment_main_page1">
             ​
             </fragment>
            复制代码
    • nav_graph_main.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/nav_graph_main.xml"
            app:startDestination="@id/page1Fragment">
        
        
            <!-- TODO 这个是第一个 Fragment -->
            <fragment
                android:id="@+id/page1Fragment"
                android:name="com.derry.navigation.MainPage1Fragment"反射实例化
                android:label="fragment_page1"
                tools:layout="@layout/fragment_main_page1">
                <!--
                    action:程序中使用id跳到destination对应的类
                -->
                <action
                    android:id="@+id/action_page2"
                    app:destination="@id/page2Fragment" />
            </fragment>
        
        
            <!-- TODO 这个是第二个 Fragment -->
            <fragment
                android:id="@+id/page2Fragment"
                android:name="com.derry.navigation.MainPage2Fragment"
                android:label="fragment_page2"
                tools:layout="@layout/fragment_main_page2">
                <action
                    android:id="@+id/action_page1"
                    app:destination="@id/page1Fragment" />
                <action
                    android:id="@+id/action_page3"
                    app:destination="@id/page3Fragment" />
            </fragment>
        
        
            <!-- TODO 这个是第三个 Fragment -->
            <!--    <navigation-->
            <!--        android:id="@+id/nav_graph_page3"-->
            <!--        app:startDestination="@id/page3Fragment">-->
            <fragment
                android:id="@+id/page3Fragment"
                android:name="com.derry.navigation.MainPage3Fragment"
                android:label="fragment_page3"
                tools:layout="@layout/fragment_main_page3">
                <action
                    android:id="@+id/action_page2"
                    app:destination="@id/page2Fragment" />
            </fragment>
        
        </navigation>
      复制代码
      • 细节:

        • 导航图(nav_graph_main.xml),只是一个xml,不能去激活三个fragment实例;
        • 需要在Activity布局引入此导航图,在Activity中指挥导航图
  • 编写Activity布局文件:控制导航图实现对Fragment的控制(这个导航图是很流行的)

    • 引入fragment

         <androidx.fragment.app.FragmentContainerView
               android:id="@+id/my_nav_host_fragment"
               android:layout_width="match_parent"
               android:layout_height="wrap_content"
               android:layout_weight="9"
               android:name="androidx.navigation.fragment.NavHostFragment"
               app:defaultNavHost="true"
               app:navGraph="@navigation/nav_graph_main"/>
      复制代码
      • 可以将外部标签androidx.fragment.app.FragmentContainerView替换为fragment:功能会减少许多,这个是次要的
      • NavHostFragment:这个才是主导航,在Activity的布局文件中被引入并且指向导航图,这个是主要的
      • app:defaultNavHost:拦截系统返回键,激活fragment的任务栈
      • app:navGraph:在activity_main.xml中激活导航图
  • 实现功能:模仿微信,点击底部导航栏跳转页面

    • 实现思路:

      1. 在activity_main.xml中添加菜单

         <com.google.android.material.bottomnavigation.BottomNavigationView
                 android:id="@+id/nav_view"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
                 app:itemTextColor="#ff0000"
                 app:menu="@menu/menu" />
        复制代码
      2. 引入菜单布局

        • 菜单存储位置:

          image-20220309145804446

        • 实现效果:

          image-20220309145854947

        • 代码实现:

           <?xml version="1.0" encoding="utf-8"?>
           <menu xmlns:android="http://schemas.android.com/apk/res/android">
           ​
               <item
                   android:id="@+id/page1Fragment"
                   android:icon="@drawable/ic_launcher_foreground"
                   android:title="第一页"/>
           ​
               <item
                   android:id="@+id/page2Fragment"
                   android:icon="@drawable/ic_launcher_foreground"
                   android:title="第二页"/>
           ​
               <item
                   android:id="@+id/page3Fragment"
                   android:icon="@drawable/ic_launcher_foreground"
                   android:title="第三页"/>
           ​
           </menu>
          复制代码
      1. 在MainActivity中绑定navigation

        • 代码展示:

           val navHostFragment =
               supportFragmentManager.findFragmentById(R.id.my_nav_host_fragment) as NavHostFragment?
          复制代码
        • 实现细节:来自于Google官网代码

          1. 获取底部导航栏实例

             bottomNavigationView = findViewById(R.id.nav_view)
            复制代码
          1. 需要去拿到fragment

             public FragmentManager getSupportFragmentManager() {
                 return mFragments.getSupportFragmentManager();
             }
            复制代码
          2. 根据id拿到主导航页面

             findFragmentById(R.id.my_nav_host_fragment)
            复制代码
          3. 此时拿到的fragment需要将其强转成fragment

             as NavHostFragment
            复制代码
          4. 有可能强转失败,所以添加?,失败时将其赋值为空

             as NavHostFragment?
            复制代码
          5. 获得navigation的控制者

             val controller = navHostFragment!!.navController
            复制代码
          6. 拿到UI:由于这句话,底部的导航栏才能实现点击

             NavigationUI.setupWithNavController(bottomNavigationView!!, controller)
            复制代码
      • 实现效果

        image-20220105160752456

  • 修改MainActivity细节:

    • 别名问题:action也有自己的id,它包含的page又有单独的id;可以实现调用action的id拿到指定的Page的id(destination),再跳转到指定的page

      image-20220105160111298

    • 根据id与destination进行页面跳转

      • MainFragment1

         val btn = view.findViewById<Button>(R.id.btn)
         btn.setOnClickListener { view ->
             Navigation.findNavController(view).navigate(R.id.action_page2)
         }
        复制代码
      • MainFragment2:存在弹栈机制(按下返回键会默认调用这个up)(也可以直接指定id来玩)

         val btn = view.findViewById<Button>(R.id.btn)
         btn.setOnClickListener { view ->
             Navigation.findNavController(view).navigate(R.id.action_page1)
             // Navigation.findNavController(view).navigateUp(); 返回上一个Fragment,内部存在fragment任务栈
         }
         val btn2 = view.findViewById<Button>(R.id.btn2)
         btn2.setOnClickListener { view ->
             Navigation.findNavController(view).navigate(R.id.action_page3)
         ​
         }
        复制代码
      • MainFragment3

         val btn = view.findViewById<Button>(R.id.btn)
         btn.setOnClickListener { view ->
             Navigation.findNavController(view).navigate(R.id.action_page2)
             // 回退上一步
             // Navigation.findNavController(view).navigateUp();
         }
        复制代码
  • 实际上在新建project的时候,就可以选择ButtomNavigationActivity

    • 示意图:

      image-20220309184836691

    • 项目架构:

      • src下有三个包,每个包对应一个fragment+ViewModel

        image-20220309184431192

      • res/layout:有对应的fragment的布局文件

        image-20220309184448753

      • res/navigation:有对应的导航图

        image-20220309184502553

      • res/menu:有对应的菜单

        image-20220309184537113

    • 使用这种方法与自己写的相同点:

      • 项目架构与实现思路基本一致
      • 也在activity_main.xml中引入了navHostFragment
    • 使用这种方法与自己写的不同点:

      • 它的内部是没有连线的,采用fragment直接来玩的

      • 这个更加专业:

        • ViewModel配合LiveData(MutableLiveData)
        • 分层更加规范
    • 使用这种方法的问题

      • 组件在高版本中会遇到很多问题:

        • 将SDK版本改成30

        • 31在Windows与mac上面都是有点问题的

      • 修改注册清单文件:修改内置的label为 BootomNavigationActivity的Activity

        • 增加属性修饰exported

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享