Full width Navigation Drawer

47,947

Solution 1

Yes, you have to extend DrawerLayout and override some methods because MIN_DRAWER_MARGIN is private

Here is a possible solution:

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);
    }

}

Solution 2

If you want simpler solution you can just set negative margin

android:layout_marginLeft="-64dp"

for your 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"/>

Solution 3

Because all these answers did not work on OS 6.0.1, I'll post here the solution that worked for me in combination with DrawerLayout + NavigationView.

So all what I do is change the width of the NavigationView programatically:

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

This works for all screen sizes.

Solution 4

Based on the Robert's Answer, you can use the layout_marginLeft=-64dp to solve this problem easily.

However it doesn't seems to work anymore on Android 5.0 and above. So here's my solution that worked for me.

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

Basically, Add android:layout_marginRight="-64dp" to the root DrawerLayout so all the layout will go to the right for 64dp.

Then I add the layout_marginRight=64dp to the content so it goes back to the original position. Then you can have a full drawer there.

Solution 5

A variant on Grogory's solution:

Instead of subclassing I call the following utility method right after I grab a reference to the drawer layout:

/**
 * 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();
  }
}
Share:
47,947
Martin
Author by

Martin

JavaScript, Angular, Node. I hope that one day I will answer more questions than ask!

Updated on July 09, 2022

Comments

  • Martin
    Martin almost 2 years

    I'd like to create a full width navigation drawer. Setting layout_width to match_parent on @+id/left_drawer yields in width of about 80% of screen space. This seems to be the standard behavior. Do I have to override onMeasure() of DrawerLayout?

    My current code:

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

    Thanks.

  • tasomaniac
    tasomaniac over 10 years
    I think this should have been the accepted answer. I have looked up the source code. It does not take the 80% percent of the space. It just put minimum of 64dp margin. Putting negative margin is wise.
  • Chris
    Chris over 10 years
    Works great. I noticed that there was still 1 pixel of a gap so 65dp works for me
  • leegor
    leegor about 9 years
    simple without any coding, 65dp is best
  • yiati
    yiati about 9 years
    For anyone with little knowledge of the source code of DrawerLayout, this answer may seem like demon magic conjured in the head of a wizard, and make you want to give up on programming. Fret not! This is simply the source code of DrawerLayout with the only methods necessary taken out. The only thing that really changes here is that "MIN_DRAWER_MARGIN = 64;" was changed to "MIN_DRAWER_MARGIN = 0;". Another idea to extend upon this would be to instead make MIN_DRAWER_MARGIN an extendable xml attribute.
  • yiati
    yiati about 9 years
    Now that I think about it, removing this MIN_DRAWER_MARGIN field entirely already allows users to modify this through the layout_margin* fields, or to not specify it at all which will default to 0dp.
  • nguoitotkhomaisao
    nguoitotkhomaisao over 8 years
    This solution not working with Android 5.0 and above :(
  • Lion789
    Lion789 about 8 years
    i do not see where half the screen is being set to the width (also I tried params.width = metrics.widthPixels/2) but it is not doing anything no matter what I change the width to
  • Rafay Ali
    Rafay Ali about 8 years
    I am not sure if its the right way but very clever solution
  • JaydeepW
    JaydeepW over 7 years
    Worked on 5.0 and above.
  • Makalele
    Makalele over 7 years
    Replace getLayoutDirection with ViewCompat.getLayoutDirection to get more compatibility. Anyway this doesn't work for me on android 7.0. Margin isn't changed and actionbar now covers status bar.
  • Adnan Bin Mustafa
    Adnan Bin Mustafa over 7 years
    You saved my Life bro ! Thank you
  • Nikhil
    Nikhil about 7 years
    Getting Fatal Exception: java.lang.IllegalArgumentException: Scrapped or attached views may not be recycled. isScrap:false isAttached:true in onMeasure()
  • Madona wambua
    Madona wambua about 6 years
    Is that on the sw600dp? for instance ? I want to be able to set the navigation drawer full on my tablet screen. So far it appears half and I can't see where to alter and change the setting of it. @Robert
  • Eugen Pechanec
    Eugen Pechanec over 5 years
    It's not 100% correct. Ideally you would never call printStackTrace in production and you would cache f in a static field so you do the reflection lookup just once.
  • Taufik Nur Rahmanda
    Taufik Nur Rahmanda over 5 years
    I've tried this and working, but now my whole activity layout gets over under status bar. I set fitsSystemWindows to true but not working. Help
  • Juan Mendez
    Juan Mendez almost 5 years
    this solution working in 2019. I tested in Android versions 22, 26, 28 I like it because is precise to set the navigation view to the full width of the window. I don't like setting a fixed negative right padding.. odd this is not the top rated answer.
  • Juan Mendez
    Juan Mendez almost 5 years
    To me the best answer is the third one most rated. It's very precise setting the navigation-view width the same as the window in pixels, and doesn't require a lot of code.
  • Pietrek
    Pietrek over 4 years
    this is a nice workaround! I would suggest to mark it as correct answer
  • Ramkesh Yadav
    Ramkesh Yadav almost 4 years
    i set it to 10 dp not -10dp and gives me expected result. Thanks.
  • alierdogan7
    alierdogan7 almost 3 years
    This is the working solution rather than setting negative margins. Thanks.
  • Sugoi Reed
    Sugoi Reed over 2 years
    Worked like a charm!