如何在 AppBar 下面创建一个剪裁的 NavigationDrawer?

How can you create a clipped NavigationDrawer below the AppBar?

如何在 AppBar 下面创建一个剪辑的 NavigationDrawer?

最新的 Android Studio (3.5.3) 生成了一个全高的 NavigationDrawer,我的问题是需要更改什么来代替截断的 NavigationDrawer?

( 请不要通过 linking 将此问题标记为“重复”到带有一长串古老答案的古老(例如 2015)问题。 现在是 2020 年,我希望现在有一种简单的方法来实现裁剪的 NavigationDrawer。 希望现在有一个简单的解决方案可以很好地与 androidx 和喷气背包导航以及 Kotlin 方法(例如 setupActionBarWithNavController)配合使用。 当我在上面提到使用 Android Studio 生成的代码时,我现在谈论的是 Android Studio 3.5.3,即当前最新版本,以及它的项目模板“Navigation Drawer Activity”与 Kotlin 和最低 API 19 级,即 Android 4.4。 当今天的开发人员想要找到一种方法来执行此操作并使用 google 和 Whosebug 进行搜索时,我们不想查找并滚动浏览具有大量 old/outdated 页答案的长页面。 由于这个问题现在是在 2020 年 2 月提出的,所以每个人都会清楚,下面所有可能出现的答案也将晚于此。 )

奇怪的是,很难找到关于如何使用 Android 实现裁剪抽屉的文档。 这里提到了 NavigationDrawers 的两种类型("full-height""clipped"):

https://material.io/components/navigation-drawer/#standard-drawer

引用:

"A standard navigation drawer can use one of these elevation positions:

 At the same elevation as a top app bar (full-height)

 At a lower elevation than a top app bar (clipped)"

在上面的网页中还有一个 link 到 android 特定页面:

https://material.io/develop/android/components/navigation-view/

但是,该页面目前没有提及任何有关如何创建剪辑的 NavigationDrawer 的内容。 此外,该 android 页面似乎没有很更新,因为它目前 link 是关于 DrawerLayout 的旧支持 v4 库。

当我转而查看有关 DrawerLayout 的新 androidx 页面时,我仍然找不到任何关于“剪裁”抽屉的信息。 (由于“clipped”是 google 的 material 设计中使用的术语,因此 google 也应该使用相同的词以便在文档页面中进行搜索)。

这里有一些页面,应该可以找到关于“剪辑”的内容,但不幸的是,目前还没有:

https://developer.android.com/jetpack/androidx/releases/drawerlayout

https://developer.android.com/guide/navigation/navigation-ui#add_a_navigation_drawer

为了说明我在寻找什么(独立于上面的 material 设计页面,可能会更改)我在下面提供了一些图片。

下面的第一个屏幕截图是使用 Android Studio 3.5.3(当前最新)和“导航抽屉”生成 Android 应用程序后的结果(有两个修改,见下文) Activity" 使用 Kotlin 和最低 API 级别 19 (Android 4.4)。

我所做的两个更改(在“activity_main.xml”中)是我从 NavigationView 中删除了 app:headerLayout 并将 android:layout_height="match_parent" 替换为 android:layout_height="wrap_content"。

然后我用 GIMP 编辑了一些屏幕截图来说明我真正想要的东西,如下图所示。 “汉堡包图标”应该可以关闭 NavigationDrawer,即将其用于切换。

以下是使用“全高”NavigationDrawer 生成的一些相关文件,我的问题是我必须执行哪些更改才能获得如上图编辑图片中的“裁剪”NavigationDrawer?


MainActivity.kt

package com.myapplication

import android.os.Bundle
import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.google.android.material.snackbar.Snackbar
import androidx.navigation.findNavController
import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.navigateUp
import androidx.navigation.ui.setupActionBarWithNavController
import androidx.navigation.ui.setupWithNavController
import androidx.drawerlayout.widget.DrawerLayout
import com.google.android.material.navigation.NavigationView
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
import android.view.Menu

class MainActivity : AppCompatActivity() {

    private lateinit var appBarConfiguration: AppBarConfiguration

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val toolbar: Toolbar = findViewById(R.id.toolbar)
        setSupportActionBar(toolbar)

        val fab: FloatingActionButton = findViewById(R.id.fab)
        fab.setOnClickListener { view ->
            Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
                .setAction("Action", null).show()
        }
        val drawerLayout: DrawerLayout = findViewById(R.id.drawer_layout)
        val navView: NavigationView = findViewById(R.id.nav_view)
        val navController = findNavController(R.id.nav_host_fragment)
        // Passing each menu ID as a set of Ids because each
        // menu should be considered as top level destinations.
        appBarConfiguration = AppBarConfiguration(
            setOf(
                R.id.nav_home, R.id.nav_gallery, R.id.nav_slideshow,
                R.id.nav_tools, R.id.nav_share, R.id.nav_send
            ), drawerLayout
        )
        setupActionBarWithNavController(navController, appBarConfiguration)
        navView.setupWithNavController(navController)
    }

    override fun onCreateOptionsMenu(menu: Menu): Boolean {
        // Inflate the menu; this adds items to the action bar if it is present.
        menuInflater.inflate(R.menu.main, menu)
        return true
    }

    override fun onSupportNavigateUp(): Boolean {
        val navController = findNavController(R.id.nav_host_fragment)
        return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
    }
}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout 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/drawer_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    tools:openDrawer="start">

    <include
        layout="@layout/app_bar_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <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="true"
        app:menu="@menu/activity_main_drawer" />

</androidx.drawerlayout.widget.DrawerLayout>

app_bar_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout 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">

    <com.google.android.material.appbar.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/AppTheme.AppBarOverlay">

        <androidx.appcompat.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            app:popupTheme="@style/AppTheme.PopupOverlay" />

    </com.google.android.material.appbar.AppBarLayout>

    <include layout="@layout/content_main" />

    <com.google.android.material.floatingactionbutton.FloatingActionButton
        android:id="@+id/fab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|end"
        android:layout_margin="@dimen/fab_margin"
        app:srcCompat="@android:drawable/ic_dialog_email" />

</androidx.coordinatorlayout.widget.CoordinatorLayout>

content_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"
    app:layout_behavior="@string/appbar_scrolling_view_behavior"
    tools:showIn="@layout/app_bar_main">

    <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_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:navGraph="@navigation/mobile_navigation" />
</androidx.constraintlayout.widget.ConstraintLayout>

事实证明,这比我想象的要复杂一些,但这条错误消息帮助我到达了那里:

DrawerLayout must be measured with MeasureSpec.EXACTLY

这是 Kotlin 解决方案:

  1. 创建一个扩展 DrawerLayout class 的新 class。在 onMeasure 方法中,为 widthMeasureSpec 和 heightMeasureSpec 创建两个新变量并将它们传递给 super class:

    class CustomDrawerLayout @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
    ) : DrawerLayout(context, attrs, defStyleAttr) {
    
    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        var newWidthMeasureSpec = MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec),MeasureSpec.EXACTLY)
        var newHeightMeasureSpec = MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(heightMeasureSpec),MeasureSpec.EXACTLY)
        super.onMeasure(newWidthMeasureSpec, newHeightMeasureSpec)
      }
    }
    
  2. 在您的 activity_main.xml 文件中,更新最外面的标签以使用新的 CustomDrawerLayout。将 CustomDrawerLayout 和 NavigationView layout_height 更改为:

    android:layout_height="wrap_content"
    
  3. 确保当您找到 drawer_layout 视图时,您正在将其初始化为 CustomDrawerLayout class:

    var drawerLayout : CustomDrawerLayout = findViewById(R.id.clipped_drawer_layout)
    
  4. 要保持​​操作栏可见,您需要将其添加到 NavigationView 组件中:

    android:layout_marginTop="?android:attr/actionBarSize"
    

完整的 activity_main.xml 文件如下所示:

<com.mullr.neurd.Miscellaneous.CustomDrawerLayout
android:id="@+id/clipped_drawer_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:openDrawer="start"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

    <include
        layout="@layout/app_bar_main"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <com.google.android.material.navigation.NavigationView
        android:id="@+id/nav_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="start"
        android:layout_marginTop="?android:attr/actionBarSize"
        app:headerLayout="@layout/nav_header_main"
        app:menu="@menu/activity_main_drawer" />

</com.mullr.neurd.Miscellaneous.CustomDrawerLayout>

最后,从您的 styles.xml (v21) 文件中删除这一行,这样状态栏就不会被遮盖:

<item name="android:statusBarColor">@android:color/transparent</item>

应该可以了。

打开导航抽屉时将应用栏保留在屏幕上的新答案:

main_drawer.xml - 包含自定义 drawerLayout、app_bar_main 布局和导航视图的新布局文件

<com.example.myapp.Miscellaneous.CustomDrawerLayout 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/full_drawer_layout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:fitsSystemWindows="true"
    tools:openDrawer="start">

    <include
        layout="@layout/app_bar_main"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <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="true"
        app:headerLayout="@layout/nav_header_main"
        app:menu="@menu/activity_main_drawer">

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

</com.example.myapp.Miscellaneous.CustomDrawerLayout>

app_bar_main.xml - 从此文件中删除工具栏

<androidx.coordinatorlayout.widget.CoordinatorLayout 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">

    <com.google.android.material.appbar.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/AppTheme.AppBarOverlay">

        <!--<androidx.appcompat.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            app:popupTheme="@style/AppTheme.PopupOverlay"
            />-->

    </com.google.android.material.appbar.AppBarLayout>

    <include layout="@layout/content_main" />

    <com.google.android.material.floatingactionbutton.FloatingActionButton
        android:id="@+id/fab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|end"
        android:layout_margin="@dimen/fab_margin"
        android:visibility="gone"
        app:srcCompat="@android:drawable/ic_dialog_email" />

</androidx.coordinatorlayout.widget.CoordinatorLayout>

activity_main.xml - 在此处添加工具栏和 main_drawer 布局。

<androidx.appcompat.widget.LinearLayoutCompat 
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:fitsSystemWindows="true"
>

<androidx.appcompat.widget.Toolbar
    android:id="@+id/toolbar"
    android:layout_width="match_parent"
    android:layout_height="?attr/actionBarSize"
    android:background="@color/primaryBlue"
    android:theme="@style/AppTheme"
    app:popupTheme="@style/AppTheme.PopupOverlay"
    app:titleTextColor="@color/design_default_color_background" />


<include
    layout="@layout/main_drawer"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

</androidx.appcompat.widget.LinearLayoutCompat>

MainActivity - 添加这个以使菜单切换仍然有效

toolbar.setNavigationOnClickListener {
            if(drawerLayout.isDrawerOpen(GravityCompat.START)){
                drawerLayout.closeDrawer(GravityCompat.START)
            }
            else{
                drawerLayout.openDrawer(GravityCompat.START)
            }
        }

最终结果应如下所示:

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.activity_main)


        // NAVIGATION 
        val toolbar: Toolbar = findViewById(R.id.toolbar)
        setSupportActionBar(toolbar)

        var drawerLayout: CustomDrawerLayout = findViewById(R.id.full_drawer_layout)
        navView = findViewById(R.id.nav_view)

        // This needs to come after drawerLayout has been initialized
        toolbar.setNavigationOnClickListener {
            if(drawerLayout.isDrawerOpen(GravityCompat.START)){
                drawerLayout.closeDrawer(GravityCompat.START)
            }
            else{
                drawerLayout.openDrawer(GravityCompat.START)
            }
        }

        navController = findNavController(R.id.nav_host_fragment)
        navView.itemIconTintList = null
        toolbar.setupWithNavController(navcontroller,drawerLayout)
        // Passing each menu ID as a set of Ids because each
        // menu should be considered as top level destinations.
        appBarConfiguration = AppBarConfiguration(
            setOf(
                R.id.nav_home, R.id.nav_favorites, R.id.nav_settings
            ), drawerLayout
        )
        setupActionBarWithNavController(navController, appBarConfiguration)
        navView.setupWithNavController(navController)
    }

要保留工具栏上后退按钮(或 "Up" 按钮)的行为,请确保将此行包含在 activity 的 onCreate 方法中 (setUpWithNavController) :

    toolbar.setupWithNavController(findNavController(R.id.nav_host_fragment),drawerLayout)

最后,从您的 styles.xml (v21) 文件中删除这一行,这样状态栏就不会被遮盖:

<item name="android:statusBarColor">@android:color/transparent</item>