Android 具有抽屉式布局和动态菜单项的导航组件

Android Navigation Component with Drawer Layout and Dynamic Menu Items

所以,这就是我想要的。

顶级数据的结构如下:

Section 1 Title -> Section 1 Content (List)
Section 2 Title -> Section 2 Content (List)
.
.
.
Section N Title -> Section N Content (List)

我想在选择汉堡包是图标时打开的侧菜单中的部分标题,以及选择特定部分时,侧面菜单关闭,该部分的内容已加载在屏幕下的下方操作栏。

因此,要使用的相关 UI 组件是:ToolbarDrawerLayout(侧边菜单)和 Fragment 用于在中心加载内容。

现在,我正在尝试使用 Navigation Components 并从中获得尽可能多的好处。我无法上班的事情是:

  1. 正在侧边菜单中加载动态项目。示例显示使用 android menu resources。我想使用我自己的回收器视图并动态加载菜单。
  2. 如何为这样的数据结构定义导航图。我试图创建一个从 ContentFragment 到自身的动作,但我不确定这是正确的,因为其中没有将 UI 从一个 ContentFragment 到另一个 UI 的动作 ContentFragment。这是加载不同 ContentFragment.
  3. 的侧边菜单的顶级操作

除了上述 2 个问题,我想知道这是否适合使用导航组件,还是使用传统方法更好?

我将从我对您的问题的理解开始:您有一个 Fragment 包含不同的数据。现在,您希望在 NavigationDrawer 中有一个动态列表 (RecyclerView),并基于此更改提供给片段 ContentFragment 的数据列表。此外,您想使用 NavigationGraph,我实际上看不到它有任何用途,因为您只使用一个可以直接加载到 FragmentContainer 中的片段。现在,让我们集中讨论你的两点:

动态 ListRecyclerView

  1. 要在 Drawer 中创建动态列表,请在 NavigationView 中放置一个 RecyclerView 为:

    <androidx.drawerlayout.widget.DrawerLayout
      android:id="@+id/drawer"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      tools:openDrawer="start">
    
      <!--...Other content...-->
    
      <com.google.android.material.navigation.NavigationView
        android:id="@+id/navView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="start">
    
        <androidx.recyclerview.widget.RecyclerView
          android:id="@+id/recyclerViewDrawer"
          android:layout_width="match_parent"
          android:layout_height="match_parent"
          tools:listitem="@layout/row_drawer" />
    
      </com.google.android.material.navigation.NavigationView>
    </androidx.drawerlayout.widget.DrawerLayout>
    

    您也可以将 RecyclerView 放在布局中,这是您的选择。

  2. 为要填充的 RecyclerView 创建布局项和适配器。基本的东西,不包括。

  3. 将项目列表传递给要在抽屉中显示的适配器,重要的是,传递一个回调,单击任何项​​目时都可以调用该回调。您可以设置接口或传递回调:

     /* You can set the callback to a variable of the AdapterDrawer using the object adapter, 
     neat way, avoids crash instead of passing in constructor.*/
     val adapter = AdapterDrawer(list) 
     adapter.callback = {position: Int, item: String ->
         //This will be called whenever any item is clicked 
         //and will return the clicked position and the item text. 
     }
    
     //In the adapter, Create a variable as
     var callback: ((position: Int, item: String) -> Unit)? = null
     //Call in item's onClickListener as
     callback?.invoke(position, itemValue)
    
  4. 这是您完成的第一点 - 在 Drawer 中设置动态列表。


Creating/Re-creating 来自 Drawer 列表回调的片段

  1. 不要在布局中使用图表 属性。它将被动态设置。

  2. arguments 添加到图中的片段为:

    <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_classes"
      app:startDestination="@id/class_view_pager_fragment">
    
      <fragment
        android:id="@+id/class_view_pager_fragment"
        android:name="com.tech.cyndi.fragments.FragmentClassViewPager"
        tools:layout="@layout/activity_class">
    
        //to set a Model as a type, pass its whole path like com.android.app.DataModel
        <argument
          android:name="DataList"
          app:argType="com.x.x.ModelClass[]" />
      </fragment>
    </navigation>
    
  3. 现在,将片段创建逻辑放在一个函数中,该函数最初将被调用以设置默认片段,然后在稍后调用回调时调用。然后将根据 callback.

    返回的值更新数据
    //You can declare and initialize the fragmentHost variable before this.
    fun setUpFragment (list: ArrayList<YourModel>) {
        val fragmentHost = supportFragmentManager.findFragmentById(binding.yourFragment.id) as NavHostFragment
        fragmentHost.navController.apply {
            val args = Bundle()
            args.putParcelableArrayList("list", list);
            setGraph(navInflater.inflate(R.navigation.yourNavGraph), args)
    }
    
  4. 最后也是最后一步,第一次从 activity 的 onCreate() 调用此函数,每当数据更改时从上面声明的 callback 调用此函数。

使用 AndroidNavigation 组件可以轻松实现这一点。

1 - 在里面添加RecyclerView可以实现动态DrawerLayout com.google.android.material.navigation.NavigationView

<com.google.android.material.navigation.NavigationView
    android:id="@+id/nav_view"
    android:layout_width="wrap_content"
    android:layout_height="match_parent"
    android:layout_gravity="start"
    android:fitsSystemWindows="false">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/rvDrawer"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

</com.google.android.material.navigation.NavigationView>

2 - Activity 使用 DrawerLayout 时的导航初始化

appBarConfiguration = AppBarConfiguration(
    setOf(R.id.mainFragment),
    binding.drawerLayout
)

binding.navView.setupWithNavController(navController)

// Setup action bar with nav controller
setupActionBarWithNavController(navController, appBarConfiguration)

3 - com.google.android.material.navigation.NavigationView 初始化

private fun loadDrawerItems(items: Array<String>) {
        binding.rvDrawer.apply {
            layoutManager = LinearLayoutManager(this@MainActivity)
            adapter = DrawerAdapter(items, this@MainActivity)
        }
    }

4 - DrawerAdapter 在用户点击项目时通知 activity

 override fun onDrawerItemClick(value: String) {
        binding.drawerLayout.closeDrawer(GravityCompat.START)
        val direction = NavGraphDirections.refreshMainFragment(value)
        navController.navigate(direction)
    }

5 - 导航

<?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"
    android:id="@+id/nav_graph"
    app:startDestination="@id/mainFragment">

    <fragment
        android:id="@+id/mainFragment"
        android:name="dh.sos.MainFragment"
        android:label="Main fragment">
        <argument
            android:name="value"
            app:argType="string"
            android:defaultValue="Default value"/>


    </fragment>

    <action android:id="@+id/refreshMainFragment"
        app:destination="@id/mainFragment"
        app:popUpTo="@id/mainFragment"
        app:popUpToInclusive="true">

        <argument
            android:name="value"
            app:argType="string"
            android:defaultValue="Default value"/>
    </action>
</navigation>

app:popUpToInclusive="true" 表示 popUpTo destination 也应该从堆栈中删除,这意味着每次调用此操作时我们都会获得 Fragment 的新实例。

完整源代码: https://github.com/dautovicharis/sos_android/tree/q_68441622