关于导航抽屉:在Android中将Statusbar padding设置为NavigationView

Setting Statusbar padding to NavigationView in Android

我有一个活动,它承载来自支持库的 DrawerLayout 和 NavigationView。我正在为导航视图设置标题布局,并且我希望导航标题高度为"wrap_content"。因此,当我将高度设置为"wrap_content"时,标题布局位于状态栏后面。

我想要的结果是导航抽屉应该在状态栏后面绘制,但导航标题应该被状态栏高度向下推。

下面是我得到的截图。请注意状态栏后面的"登录"按钮。

screenshot

活动布局

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<android.support.v4.widget.DrawerLayout
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"
android:id="@+id/nav_drawer"
android:fitsSystemWindows="true">

<android.support.design.widget.CoordinatorLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent">

    <android.support.v4.view.ViewPager
        android:id="@+id/view_pager"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_behavior="@string/appbar_scrolling_view_behavior"></android.support.v4.view.ViewPager>
    <android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

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

        <android.support.design.widget.TabLayout
            app:theme="@style/ThemeOverlay.AppCompat.Dark"
            style="@style/MyCustomTabLayout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:id="@+id/tabs"
            />

    </android.support.design.widget.AppBarLayout>

</android.support.design.widget.CoordinatorLayout>

<android.support.design.widget.NavigationView
    android:id="@+id/navigation_view"
    android:layout_width="wrap_content"
    android:layout_height="match_parent"
    app:headerLayout="@layout/nav_header"
    app:menu="@menu/menu_navigation"
    android:fitsSystemWindows="true"
    android:layout_gravity="start"/>

</android.support.v4.widget.DrawerLayout>

导航视图标题布局

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          android:background="?attr/colorPrimaryDark"
          android:padding="16dp"
          android:theme="@style/ThemeOverlay.AppCompat.Dark"
          android:orientation="vertical"
          android:fitsSystemWindows="true"
          android:gravity="bottom">

<TextView
    android:visibility="gone"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:id="@+id/text_user_name"
    android:textAppearance="@style/TextAppearance.AppCompat.Body1"/>

<TextView
    android:visibility="gone"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:id="@+id/text_email"
    android:textAppearance="@style/TextAppearance.AppCompat.Body2"/>

<Button
    android:id="@+id/button_sign_in"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Sign In"/>

</LinearLayout>

我已经通过 StackOverflow 搜索解决方案,但找不到。所以有人请阐明一下。提前致谢。


1
2
3
4
5
6
7
8
<android.support.design.widget.NavigationView
android:id="@+id/navigation_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
app:headerLayout="@layout/nav_header"
app:menu="@menu/menu_navigation"
android:fitsSystemWindows="false"
android:layout_gravity="start"/>

android:fitsSystemWindows="false"

和 CoordinatorLayout => android:fitsSystemWindows="false"


首先,请记住,您不能在 Lollipop 之前在状态栏后面绘图。

解决方案

对于 Lollipop,您应该为 DrawerLayout 设置 android:fitsSystemWindows="true" 并为 NavigationView 保留默认值。

然后在你的activity或fragment上设置header:

1
2
3
4
5
6
7
8
9
10
11
12
13
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH) {
    navigationView.setOnApplyWindowInsetsListener { v, insets ->

        val header = navigationView.getHeaderView(0)
        header.setPadding(
            header.paddingLeft,
            header.paddingTop + insets.systemWindowInsetTop,
            header.paddingRight,
            header.paddingBottom
        )
        insets.consumeSystemWindowInsets()
    }
}

解释

窗口正在将一个名为 insets 的对象(具有上、左、下、右的尺寸)传递给它的子视图。具有 fitSystemWindows=true 的子视图不会对此插入执行任何操作,并且可能会将其转发给其子视图。设置为 false 的视图可能会使用这些值为其自身添加额外的填充或边距。

NavigationView

Set fitSystemWindows=false 不起作用,因为它正在使用 insets 为其自身应用额外的边距。你想要的是你的标题视图 LinearLayout 来应用这个边距。但是 NavigationView 内部有两个中间容器视图,它们不会将插图传递给您的 LinearLayout,因此您必须在这些容器之前截取窗口插图并自己应用页眉视图的边距或填充。

你不应该做的事

永远不要假设窗口插图是固定值或从资源中获取它们,无论 Android 版本是什么。特定设备对窗口嵌入有特定的值,有些具有较厚的"凹槽"以适应大型相机,有些制造商决定使其比 Android 推荐的规格更薄。


您正在使用 LinearLayout 作为标题布局的根元素。 FrameLayoutLinearLayout 等是忽略 android:fitsSystemWindows 属性的基本布局。我建议您使用 CoordinatorLayout 作为标题布局的根元素。使用 CoordinatorLayout 并将其 android:fitsSystemWindows 属性设置为 true 将自动调整其内部填充,以防止其子元素被状态栏等系统窗口重叠。

这些信息来自 Ian Lake 的这篇文章,"我为什么要适应 SystemWindows?"。

While the basic layouts (FrameLayout, LinearLayout, etc) use the default behavior, there are a number of layouts that already customize how they react to fitsSystemWindows to fit specific use cases.

...

CoordinatorLayout also takes advantage of overriding how it handles window insets, allowing the Behavior set on child Views to intercept and change how Views react to window insets, before calling dispatchApplyWindowInsets() on each child themselves. It also uses the fitsSystemWindows flag to know if it needs to paint the status bar background.

要实现您想要的,请确保遵循此布局结构。

布局/activity_xxxxx.xml

1
2
3
4
5
6
7
8
9
10
11
12
<android.support.v4.widget.DrawerLayout
    ...
    android:fitsSystemWindows="true">

    <!-- content -->
    ...

    <!-- drawer -->
    <android.support.design.widget.NavigationView
        ...
        android:fitsSystemWindows="true"
        app:headerLayout="@layout/nav_header" />

布局/nav_header.xml

1
2
3
4
5
6
<android.support.design.widget.CoordinatorLayout
    ...
    android:fitsSystemWindows="true">

    <!-- content -->
    ...