在Android中将状态栏填充设置为NavigationView

22

我有一个活动,其中托管了来自支持库的DrawerLayout和NavigationView。我正在为NavigationView设置一个头部布局,并希望导航头部高度为“wrap_content”。因此,当我将高度设置为“wrap_content”时,头部布局会出现在状态栏后面。

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

以下是我得到的截图。请注意,“SIGN IN”按钮会出现在状态栏后面。

screenshot

活动布局

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

导航视图头部布局

<?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 上搜索解决方案,但是找不到。所以请有人给予指导。先谢谢了。

3个回答

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


嗨,如果您能在解决方案周围提供更多的解释,那就太好了。(提示:人们也更有可能接受您的解决方案;) - bobylito
15
这将导致抽屉在状态栏下方绘制,而这并不是所期望的行为。 - SpaceBison
@SpaceBison 这 期望的行为。这也是 标准 行为。关键是,op 希望 按钮 不要显示在状态栏以下。例如,通过在其顶部添加填充。 - Marc Plano-Lesay

7

首先,需要记住在Lollipop版本之前无法在状态栏后面绘制。

解决方法

对于Lollipop及以上版本,您应该为DrawerLayout设置android:fitsSystemWindows="true",并保留NavigationView的默认设置。

接着,在设置标题后,在您的活动或片段中执行以下操作:

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 属性的子视图不会对这些插图进行任何处理,可能会将其转发给其子视图。而具有 fitSystemWindows=false 属性的视图可以使用这些值来添加额外的填充或边距。

NavigationViewfitSystemWindows=false 属性设置为无效,因为它正在使用插图来为自己应用额外的边距。你想要的是你的标题视图 LinearLayout 应用此边距。但在 NavigationView 中有两个中间容器视图不会将插图转发到你的 LinearLayout,所以你必须在这些容器之前拦截窗口插图,并自行应用标题视图的边距或填充。

永远不要做的事情

永远不要假设窗口插图是固定值,也不要从资源中获取它们,无论 Android 版本如何。特定设备的窗口插图具有特定的值,某些设备的“刘海”更厚以适应大型相机,某些制造商决定将其变得比 Android 推荐规格更薄。


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

这些信息来自Ian Lake的此文章,“为什么我想要fitsSystemWindows?”。

虽然基本布局(FrameLayout、LinearLayout等)使用默认行为,但有许多布局已经定制了它们如何响应fitsSystemWindows以适应特定用例。

...

CoordinatorLayout还利用重写如何处理窗口插入的方式,允许在调用每个孩子自己的dispatchApplyWindowInsets()之前,Behavior设置在子视图上拦截并更改视图对窗口插入的反应。 它还使用fitsSystemWindows标志来知道是否需要绘制状态栏背景。

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

layout/activity_xxxxx.xml

<android.support.v4.widget.DrawerLayout
    ...
    android:fitsSystemWindows="true">

    <!-- content -->
    ...

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

<android.support.v4.widget.DrawerLayout>

layout/nav_header.xml

<android.support.design.widget.CoordinatorLayout
    ...
    android:fitsSystemWindows="true">

    <!-- content -->
    ...

<android.support.design.widget.CoordinatorLayout>

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接