欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android Jetpack 学习之Navigation

程序员文章站 2022-03-13 17:09:05
...

先上效果图:

Android Jetpack 学习之Navigation

Navigation组件,也可以理解成FragmentNavigation。它提供了多Fragment之间的转场栈管理,帮助我们可以更轻松的使用Fragment。在抽屉式导航栏、底部导航栏、顶部导航栏的需求中我们可以尝试使用这个新组件。甚至,可以尝试写一个单Activity的应用

Navigation

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

  • Navigation视图:在res/navigation包下面的xml视图资源,包括应用内所有单个内容区域以及用户可以通过应用获取的可能路径。
  • NavHost:显示导航图中目的地的空白容器。导航组件包含一个NavHost实现(NavHostFragment),可显示Fragment目标
  • NacController:在NavHost中管理应用导航的对象。当用户在整个应用中移动时,NavController会安排NavHost中目标内容切换。

添加依赖

dependencies {
  def nav_version = "2.3.0"

  // Java language implementation
  implementation "androidx.navigation:navigation-fragment:$nav_version"
  implementation "androidx.navigation:navigation-ui:$nav_version"

  // Kotlin
  implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
  implementation "androidx.navigation:navigation-ui-ktx:$nav_version"

  // Dynamic Feature Module Support
  implementation "androidx.navigation:navigation-dynamic-features-fragment:$nav_version"

  // Testing Navigation
  androidTestImplementation "androidx.navigation:navigation-testing:$nav_version"
}

创建导航视图

选择res / New / Android Resource File

Android Jetpack 学习之Navigation

点击ok之后,android studio 会自动帮我们在res文件夹下创建一个navigation目录,目录下面有一个刚刚创建的nav_graph.xml导航视图。导航视图中包含应用中各个目的地,即应用中用户可以导航到的任意位置。

接下来我们编辑导航视图,首先创建几个目的地Fragment:AFragment、BFragment、DFragment、EFragment、FFragment

Android Jetpack 学习之Navigation

Android Jetpack 学习之Navigation

这里我们创建了几个目的地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/mainFragment">

    <fragment
        android:id="@+id/mainFragment"
        android:name="com.business.navigationdemo.ui.main.MainFragment"
        android:label="main_fragment"
        tools:layout="@layout/main_fragment">
        <action
            android:id="@+id/action_mainFragment_to_AFragment"
            app:destination="@id/AFragment" />
        <action
            android:id="@+id/action_mainFragment_to_BFragment"
            app:destination="@id/BFragment" />
    </fragment>
    <fragment
        android:id="@+id/AFragment"
        android:name="com.business.navigationdemo.ui.a.AFragment"
        android:label="AFragment"
        tools:layout="@layout/a_fragment">
        <action
            android:id="@+id/action_AFragment_to_DFragment"
            app:destination="@id/DFragment" />
    </fragment>
    <fragment
        android:id="@+id/BFragment"
        android:name="com.business.navigationdemo.ui.b.BFragment"
        android:label="BFragment"
        tools:layout="@layout/b_fragment">
        <action
            android:id="@+id/action_BFragment_to_EFragment"
            app:destination="@id/EFragment" />
    </fragment>
    <fragment
        android:id="@+id/DFragment"
        android:name="com.business.navigationdemo.ui.d.DFragment"
        android:label="DFragment"
        tools:layout="@layout/d_fragment">
        <action
            android:id="@+id/action_DFragment_to_mainFragment"
            app:destination="@id/mainFragment"
            app:popUpTo="@id/mainFragment"
            app:popUpToInclusive="true" />
    </fragment>
    <fragment
        android:id="@+id/EFragment"
        android:name="com.business.navigationdemo.ui.e.EFragment"
        android:label="EFragment"
        tools:layout="@layout/e_fragment">
        <action
            android:id="@+id/action_EFragment_to_FFragment"
            app:destination="@id/FFragment" />
    </fragment>
    <fragment
        android:id="@+id/FFragment"
        android:name="com.business.navigationdemo.ui.f.FFragment"
        android:label="FFragment"
        tools:layout="@layout/f_fragment">
        <action
            android:id="@+id/action_FFragment_to_BFragment"
            app:destination="@id/BFragment"
            app:popUpTo="@id/BFragment"
            app:popUpToInclusive="true" />
    </fragment>
</navigation>

<navigation>元素时导航视图的根元素,当向图表中添加目的地和连接操作时,可以看到相应的<action>元素在这里显示为子元素。

当DFragment回退到MainFragment和FFragment回退到BFragment时,都添加了app:popUpto="",这样在导航过程中会从堆栈中移除它们中间的Fragment,利用app:popUpToInclusive="true",会将第一个MainFragment和第一个BFragment从堆栈中弹出并清除。如果不是使用app:popUpToInclusive="true",则堆栈中会包含两个目的地的实例。

添加导航宿主

NavHost即导航宿主,是Navigation组件的核心部分。导航宿主是一个空容器,用户在应用中导航时,目的地会在该容器中交换进出。导航宿主必须派生于NavHost。Navigation组件的默认NavHost实现(NavHostFragment)负责处理Fragment目的的交换。

注意:Navigation组件旨在用于具有一个主Activity和多个Fragment目的地的应用。主Activity与导航视图相关联,且包含一个负责根据需要交换目的地的NavHostFragment。在具有多个Activity目的地应用中,每个Activity都拥有其自己的导航图。

打开main_activity.xml文件,以在Layout Editor中打开,在Palette窗口内搜索“Nav”

Android Jetpack 学习之Navigation

<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/nav_host_fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:defaultNavHost="true"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:navGraph="@navigation/nav_graph" />
</androidx.constraintlayout.widget.ConstraintLayout>

接下来我们看一下MainActivity.kt的代码

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.main_activity)
    }

    override fun onSupportNavigateUp(): Boolean {
        val fragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment)
        return if (fragment != null) {
            NavHostFragment.findNavController(fragment).navigateUp()
        } else {
            super.onSupportNavigateUp()
        }
    }
}

这里重写onSupportNavigationUp()方法,目的时将back时间委托出去。若堆栈中由两个以上Fragment,点击back键就会返回到上一个Fragment。

MainFragment.kt如下:

class MainFragment : Fragment() {

    companion object {
        fun newInstance() = MainFragment()
    }

    private val viewModel by lazy { ViewModelProviders.of(this).get(MainViewModel::class.java) }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        return inflater.inflate(R.layout.main_fragment, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        val btnNextA = view.findViewById<Button>(R.id.btn_next_A)
        val btnNextB = view.findViewById<Button>(R.id.btn_next_B)
       
        btnNextA.setOnClickListener { v ->
            Navigation.findNavController(v).navigate(R.id.action_mainFragment_to_AFragment)
        }
        btnNextB.setOnClickListener { v ->
            Navigation.findNavController(v).navigate(R.id.action_mainFragment_to_BFragment)
        }
        
    }

}

AFragment.kt

class AFragment : Fragment() {

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.a_fragment, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        val btnNextD = view.findViewById<Button>(R.id.btn_next_D);
        btnNextD.setOnClickListener { v ->
            //官方推荐使用Safe Args的插件用于导航的数据传递
            //也可以使用Bundle的方式
            val bundle = bundleOf("AA" to "Hello", "BB" to " World")

            Navigation.findNavController(v).navigate(R.id.action_AFragment_to_DFragment, bundle);
        }
    }
}

DFragment.kt

class DFragment : Fragment() {

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.d_fragment, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        val tvInfo = view.findViewById<TextView>(R.id.tv_info)
        val btnBackToMain = view.findViewById<Button>(R.id.btn_back_main)
        tvInfo.text = "${arguments?.getString("AA")} ${arguments?.getString("BB")}"
        btnBackToMain.setOnClickListener { v ->
            Navigation.findNavController(v).navigate(R.id.action_DFragment_to_mainFragment)
        }
    }
}

FFragment.kt

class FFragment : Fragment() {
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.f_fragment, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        val btnBackToB = view.findViewById<Button>(R.id.btn_back_B)
        val btnBack = view.findViewById<Button>(R.id.btn_back)
        btnBack.setOnClickListener { v ->
            Navigation.findNavController(v).navigateUp()
        }
        btnBackToB.setOnClickListener { v ->
            Navigation.findNavController(v).navigate(R.id.action_FFragment_to_BFragment)
        }
    }
}

我们运行看一效果:

Android Jetpack 学习之Navigation

BottomNavigationView

我们直接创建一个BottomNavigationView工程

Android Jetpack 学习之Navigation

Android Studio会自动帮我们创建导航视图并添加导航宿主

Android Jetpack 学习之Navigation

<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>

res/menu文件下多了一个bottom_nav_menu.xml的文件,这里定义了底部导航栏的图标文字等信息

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingTop="?attr/actionBarSize">

    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/nav_view"
        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/bottom_nav_menu" />

    <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:layout_constraintBottom_toTopOf="@id/nav_view"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:navGraph="@navigation/mobile_navigation" />

</androidx.constraintlayout.widget.ConstraintLayout>

activity_main.xml中自动帮我们定义了BottomNavigationView,并指定了底部导航的menu,以及导航的宿主。

至此,我们可以看到,利用Navigation,我们可以非常轻松的处理Fragment的跳转、回退、转场动画。这几乎跟Activity一模一样了。有了它,我们完全可以实现一个单个Activity的App了。