1

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

最新的 Android Studio (3.5.3) 会生成一个全高 NavigationDrawer,我的问题是需要更改哪些内容才能获得剪裁的 NavigationDrawer?

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

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

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

引用:

“标准的导航抽屉可以使用以下海拔位置之一:

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

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

在上面的网页上,还有一个指向 android 特定页面的链接:

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

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

相反,当我查看有关 DrawerLayout 的新 androidx 页面时,我仍然找不到任何有关“剪辑”抽屉的信息。(因为“剪辑”是谷歌材料设计中使用的术语,所以谷歌也应该使用相同的词在文档页面中进行搜索)。

以下是一些应该可以找到有关“剪辑”的页面的页面,但不幸的是目前没有:

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

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

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

下面的第一个屏幕截图是使用 Android Studio 3.5.3(当前是最新版本)生成 Android 应用程序以及使用 Kotlin 和最低 API 级别 19(Android 4.4)生成 Android 应用程序后的结果(有两个修改,见下文) )。

我所做的两个更改(在“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>
4

2 回答 2

2

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

DrawerLayout 必须使用 MeasureSpec.EXACTLY 进行测量

这是Kotlin的解决方案:

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

    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 类的一个实例:

    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>

那应该这样做。 在此处输入图像描述

于 2020-02-05T13:09:09.617 回答
0

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

main_drawer.xml - 包含自定义抽屉布局、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)
    }

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

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

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

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

在此处输入图像描述

于 2020-02-05T20:48:33.567 回答