全宽导航抽屉

42

我想创建一个宽度全屏的导航抽屉。在@+id/left_drawer上设置layout_widthmatch_parent会使其宽度占据大约80%的屏幕空间。这似乎是标准行为。我需要重写DrawerLayoutonMeasure()吗?

我的当前代码:

<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/drawer_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/black"
        android:id="@+id/mainFragmentContainer">
    </FrameLayout>

    <include
        android:id="@+id/left_drawer"
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        layout="@layout/drawer"/>
</android.support.v4.widget.DrawerLayout>

谢谢。

16个回答

132

如果您想要更简单的解决方案,您可以只需设置负边距

android:layout_marginLeft="-64dp"

对于您的left_drawer:

<include
        android:id="@+id/left_drawer"
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        layout="@layout/drawer"
        android:layout_marginLeft="-64dp"/>

6
我认为这应该是被采纳的答案。我查阅了源代码,它并没有占据80%的空间,只是设置了最小64dp的边距。使用负边距是明智的。 - tasomaniac
6
表现良好。我注意到还有一个像素的间隙,所以65dp对我很有效。 - Chris
简单而不含任何编码,65dp是最佳选择。 - leegor
5
这个解决方案在Android 5.0及以上版本上无法使用 :( - nguoitotkhomaisao
1
在5.0及以上版本上工作过。 - JaydeepW
显示剩余2条评论

31

因为所有这些答案在 6.0.1 操作系统上都不起作用,所以我将在此发布我的解决方案并与 DrawerLayout + NavigationView 结合使用。

所以我所做的就是编程方式更改 NavigationView 的宽度:

mNavigationView = (NavigationView) findViewById(R.id.nv_navigation);
DisplayMetrics metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);
DrawerLayout.LayoutParams params = (DrawerLayout.LayoutParams) mNavigationView.getLayoutParams();
params.width = metrics.widthPixels;
mNavigationView.setLayoutParams(params);

这适用于所有屏幕尺寸。


我看不到屏幕的一半被设置为宽度(我也尝试了params.width = metrics.widthPixels/2),但无论我将宽度更改为什么,它都没有任何作用。 - Lion789
这个解决方案在2019年可行。我已经测试过在Android版本22、26和28上运行。 我喜欢它,因为可以精确地将导航视图设置为窗口的全宽度。我不喜欢设置固定的负右填充..奇怪的是这不是最受欢迎的答案。 - Juan Mendez
这是可行的解决方案,而不是设置负边距。谢谢。 - alierdogan7

30

是的,您需要扩展DrawerLayout并覆盖一些方法,因为MIN_DRAWER_MARGINprivate

这里有一个可能的解决方案:

public class FullDrawerLayout extends DrawerLayout {

    private static final int MIN_DRAWER_MARGIN = 0; // dp

    public FullDrawerLayout(Context context) {
        super(context);
    }

    public FullDrawerLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public FullDrawerLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        final int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        if (widthMode != MeasureSpec.EXACTLY || heightMode != MeasureSpec.EXACTLY) {
            throw new IllegalArgumentException(
                    "DrawerLayout must be measured with MeasureSpec.EXACTLY.");
        }

        setMeasuredDimension(widthSize, heightSize);

        // Gravity value for each drawer we've seen. Only one of each permitted.
        int foundDrawers = 0;
        final int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            final View child = getChildAt(i);

            if (child.getVisibility() == GONE) {
                continue;
            }

            final LayoutParams lp = (LayoutParams) child.getLayoutParams();

            if (isContentView(child)) {
                // Content views get measured at exactly the layout's size.
                final int contentWidthSpec = MeasureSpec.makeMeasureSpec(
                        widthSize - lp.leftMargin - lp.rightMargin, MeasureSpec.EXACTLY);
                final int contentHeightSpec = MeasureSpec.makeMeasureSpec(
                        heightSize - lp.topMargin - lp.bottomMargin, MeasureSpec.EXACTLY);
                child.measure(contentWidthSpec, contentHeightSpec);
            } else if (isDrawerView(child)) {
                final int childGravity =
                        getDrawerViewGravity(child) & Gravity.HORIZONTAL_GRAVITY_MASK;
                if ((foundDrawers & childGravity) != 0) {
                    throw new IllegalStateException("Child drawer has absolute gravity " +
                            gravityToString(childGravity) + " but this already has a " +
                            "drawer view along that edge");
                }
                final int drawerWidthSpec = getChildMeasureSpec(widthMeasureSpec,
                        MIN_DRAWER_MARGIN + lp.leftMargin + lp.rightMargin,
                        lp.width);
                final int drawerHeightSpec = getChildMeasureSpec(heightMeasureSpec,
                        lp.topMargin + lp.bottomMargin,
                        lp.height);
                child.measure(drawerWidthSpec, drawerHeightSpec);
            } else {
                throw new IllegalStateException("Child " + child + " at index " + i +
                        " does not have a valid layout_gravity - must be Gravity.LEFT, " +
                        "Gravity.RIGHT or Gravity.NO_GRAVITY");
            }
        }
    }

    boolean isContentView(View child) {
        return ((LayoutParams) child.getLayoutParams()).gravity == Gravity.NO_GRAVITY;
    }

    boolean isDrawerView(View child) {
        final int gravity = ((LayoutParams) child.getLayoutParams()).gravity;
        final int absGravity = Gravity.getAbsoluteGravity(gravity,
                child.getLayoutDirection());
        return (absGravity & (Gravity.LEFT | Gravity.RIGHT)) != 0;
    }

    int getDrawerViewGravity(View drawerView) {
        final int gravity = ((LayoutParams) drawerView.getLayoutParams()).gravity;
        return Gravity.getAbsoluteGravity(gravity, drawerView.getLayoutDirection());
    }

    static String gravityToString(int gravity) {
        if ((gravity & Gravity.LEFT) == Gravity.LEFT) {
            return "LEFT";
        }
        if ((gravity & Gravity.RIGHT) == Gravity.RIGHT) {
            return "RIGHT";
        }
        return Integer.toHexString(gravity);
    }

}

8
对于那些对DrawerLayout源代码知之甚少的人,这个回答可能看起来像是巫师脑海中魔法般的产物,让你想放弃编程。不用担心!这只是从DrawerLayout源代码中仅取出必要的方法后的简化版。真正改动的唯一一件事是将“MIN_DRAWER_MARGIN = 64;”更改为“MIN_DRAWER_MARGIN = 0;”。另一个可扩展的想法是将MIN_DRAWER_MARGIN改为可扩展的XML属性。 - yiati
1
现在我想起来了,完全删除这个MIN_DRAWER_MARGIN字段已经允许用户通过layout_margin*字段进行修改,或者根本不指定它,这将默认为0dp。 - yiati
1
使用ViewCompat.getLayoutDirection替换getLayoutDirection以获得更好的兼容性。但是在Android 7.0上对我不起作用。边距没有改变,操作栏现在覆盖状态栏。 - Makalele
我已经尝试过这个方法并且运行良好,但现在我的整个Activity布局被状态栏所覆盖。我将fitsSystemWindows设置为true但是仍然不起作用。请帮忙解决。 - Taufik Nur Rahmanda
@Taufik Nur Rahmanda 刚刚将 fitsSystemWindows 设置为 false。它不会再显示在状态栏下面了。 - chisom emmanuel
显示剩余2条评论

16

根据Robert的答案,你可以使用layout_marginLeft=-64dp来轻松解决这个问题。

然而它在Android 5.0及以上版本中似乎不再起作用。所以这是我的解决方案,对我很有效。

<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout
    android:id="@+id/drawer_layout"
    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"
    android:layout_marginRight="-64dp"
    android:fitsSystemWindows="true"
    tools:openDrawer="start">

    <include
        layout="@layout/content"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginRight="64dp"/>

    <include
        android:id="@+id/left_drawer"
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        layout="@layout/drawer"/>

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

基本上,在根DrawerLayout中添加android:layout_marginRight="-64dp",这样所有的布局都会向右移动64dp。

然后我将layout_marginRight=64dp添加到内容中,使其返回原始位置。然后你就可以有一个完整的抽屉了。


1
这是一个不错的解决方法!我建议将其标记为正确答案。 - Pietrek

10

格罗戈里解决方案的变体:

我不使用子类化,而是在获取抽屉布局的引用后立即调用以下实用方法:

/**
 * The specs tell that
 * <ol>
 * <li>Navigation Drawer should be at most 5*56dp wide on phones and 5*64dp wide on tablets.</li>
 * <li>Navigation Drawer should have right margin of 56dp on phones and 64dp on tablets.</li>
 * </ol>
 * yet the minimum margin is hardcoded to be 64dp instead of 56dp. This fixes it.
 */
public static void fixMinDrawerMargin(DrawerLayout drawerLayout) {
  try {
    Field f = DrawerLayout.class.getDeclaredField("mMinDrawerMargin");
    f.setAccessible(true);
    f.set(drawerLayout, 0);

    drawerLayout.requestLayout();
  } catch (Exception e) {
    e.printStackTrace();
  }
}

我不确定这是否是正确的方法,但这是非常聪明的解决方案。 - Rafay Ali
这并不是100%正确的。理想情况下,您在生产环境中永远不会调用printStackTrace,并且您将f缓存到静态字段中,以便只需进行一次反射查找。 - Eugen Pechanec

4
另一种解决问题的可能方法而不需过多覆盖是:
public class FullScreenDrawerLayout extends DrawerLayout {

... //List of constructors calling
... //super(...);
... //init();

/** Make DrawerLayout to take the whole screen. */
protected void init() {
    try {

        Field field = getClass().getSuperclass().getDeclaredField("mMinDrawerMargin");
        field.setAccessible(true);
        field.set(this, Integer.valueOf(0));

    } catch (Exception e) {
        throw new IllegalStateException("android.support.v4.widget.DrawerLayout has changed and you have to fix this class.", e);
    }
}

如果在某个时刻,支持库被更新了而mMinDrawerMargin不再存在,你将会遇到异常并需要在发布下一个更新之前解决此问题。

我没有进行测量,但是假设没有太多的反射来影响性能。此外,它只在每个视图创建时执行一次。

PS 奇怪的是,为什么DrawerLayout在这一点上如此不灵活(我指的是私有的最小边距)...


4
尼珀的FullDrawerLayout类真是太棒了,而且性能比默认抽屉还要快。然而,你不能在没有view.getLayoutDirection() API的设备上使用它(即:该类无法在所有姜饼设备上运行)。
所以我做的是
替换了所有的
view.getLayoutDirection();

使用以下代码:
GravityCompat.getAbsoluteGravity(gravity,ViewCompat.getLayoutDirection(this));

我已经将我的支持库更新到最新版本,还将完整的DrawerLayout扩展到了支持导航抽屉。现在它也可以在Gingerbread设备上正常运行。


4

我试过这个方法,很有效:

<include
    android:id="@+id/left_drawer"
    android:orientation="vertical"
    android:layout_width="320dp"
    android:layout_height="match_parent"
    android:layout_gravity="start"
    layout="@layout/drawer"/>

设置包含布局的宽度:android:layout_width="320dp"。对于不同屏幕尺寸的设备,您可以动态地设置此包含布局的宽度。


3
你可以使用这个。灵感来自于这篇帖子,我已经升级到第五版了。因为在5及以上版本中存在StatusBar的问题。
你需要扩展DrawerLayout并覆盖一些方法,因为MIN_DRAWER_MARGIN是私有的。
public class FullDrawerLayout extends DrawerLayout {

    private static final int MIN_DRAWER_MARGIN = 0; // dp

    public FullDrawerLayout(Context context) {
        super(context);
    }

    public FullDrawerLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public FullDrawerLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        final int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        if (widthMode != MeasureSpec.EXACTLY || heightMode != MeasureSpec.EXACTLY) {
            throw new IllegalArgumentException(
                    "DrawerLayout must be measured with MeasureSpec.EXACTLY.");
        }

        setMeasuredDimension(widthSize, heightSize);

        //for support Android 5+
        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) {
            FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) getLayoutParams();
            params.topMargin = getStatusBarHeight();
            setLayoutParams(params);
        }

        // Gravity value for each drawer we've seen. Only one of each permitted.
        int foundDrawers = 0;
        final int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            final View child = getChildAt(i);

            if (child.getVisibility() == GONE) {
                continue;
            }

            final LayoutParams lp = (LayoutParams) child.getLayoutParams();

            if (isContentView(child)) {
                // Content views get measured at exactly the layout's size.
                final int contentWidthSpec = MeasureSpec.makeMeasureSpec(
                        widthSize - lp.leftMargin - lp.rightMargin, MeasureSpec.EXACTLY);
                final int contentHeightSpec = MeasureSpec.makeMeasureSpec(
                        heightSize - lp.topMargin - lp.bottomMargin, MeasureSpec.EXACTLY);
                child.measure(contentWidthSpec, contentHeightSpec);
            } else if (isDrawerView(child)) {
                final int childGravity =
                        getDrawerViewGravity(child) & Gravity.HORIZONTAL_GRAVITY_MASK;
                if ((foundDrawers & childGravity) != 0) {
                    throw new IllegalStateException("Child drawer has absolute gravity " +
                            gravityToString(childGravity) + " but this already has a " +
                            "drawer view along that edge");
                }
                final int drawerWidthSpec = getChildMeasureSpec(widthMeasureSpec,
                        MIN_DRAWER_MARGIN + lp.leftMargin + lp.rightMargin,
                        lp.width);
                final int drawerHeightSpec = getChildMeasureSpec(heightMeasureSpec,
                        lp.topMargin + lp.bottomMargin,
                        lp.height);
                child.measure(drawerWidthSpec, drawerHeightSpec);
            } else {
                throw new IllegalStateException("Child " + child + " at index " + i +
                        " does not have a valid layout_gravity - must be Gravity.LEFT, " +
                        "Gravity.RIGHT or Gravity.NO_GRAVITY");
            }
        }
    }

    boolean isContentView(View child) {
        return ((LayoutParams) child.getLayoutParams()).gravity == Gravity.NO_GRAVITY;
    }

    boolean isDrawerView(View child) {
        final int gravity = ((LayoutParams) child.getLayoutParams()).gravity;
        final int absGravity = Gravity.getAbsoluteGravity(gravity,
                child.getLayoutDirection());
        return (absGravity & (Gravity.LEFT | Gravity.RIGHT)) != 0;
    }

    int getDrawerViewGravity(View drawerView) {
        final int gravity = ((LayoutParams) drawerView.getLayoutParams()).gravity;
        return Gravity.getAbsoluteGravity(gravity, drawerView.getLayoutDirection());
    }

    static String gravityToString(int gravity) {
        if ((gravity & Gravity.LEFT) == Gravity.LEFT) {
            return "LEFT";
        }
        if ((gravity & Gravity.RIGHT) == Gravity.RIGHT) {
            return "RIGHT";
        }
        return Integer.toHexString(gravity);
    }


    public int getStatusBarHeight() {
        int result = 0;
        int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
        if (resourceId > 0) {
            result = getResources().getDimensionPixelSize(resourceId);
        }
        return result;
    }

}

1
您可以使用以下代码。
 int width = getResources().getDisplayMetrics().widthPixels/2;
        DrawerLayout.LayoutParams params = (android.support.v4.widget.DrawerLayout.LayoutParams) drawer_Linear_layout.getLayoutParams();
        params.width = width;
        drawer_Linear_layout.setLayoutParams(params);

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