Полноразмерный навигационный ящик

Я хотел бы создать полноразмерный навигационный ящик. Установка layout_width на match_parent на @+id/left_drawer дает по ширине около 80% экранного пространства. Кажется, это стандартное поведение. Нужно ли переопределять onMeasure() из DrawerLayout?

Мой текущий код:

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

Спасибо.


person Martin    schedule 25.05.2013    source источник


Ответы (14)


Да, вам нужно расширить DrawerLayout и переопределить некоторые методы, потому что MIN_DRAWER_MARGIN это private

Вот возможное решение:

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

}
person Nipper    schedule 05.08.2013
comment
Для тех, кто мало знаком с исходным кодом DrawerLayout, этот ответ может показаться демонической магией, созданной в голове волшебника, и заставить вас отказаться от программирования. Не волнуйтесь! Это просто исходный код DrawerLayout, из которого удалены только необходимые методы. Единственное, что здесь действительно меняется, это то, что MIN_DRAWER_MARGIN = 64; было изменено на MIN_DRAWER_MARGIN = 0;. Другой идеей для расширения этого было бы вместо этого сделать MIN_DRAWER_MARGIN расширяемым атрибутом xml. - person yiati; 03.05.2015
comment
Теперь, когда я думаю об этом, полное удаление этого поля MIN_DRAWER_MARGIN уже позволяет пользователям изменять его через поля layout_margin * или вообще не указывать его, что по умолчанию будет равно 0dp. - person yiati; 03.05.2015
comment
Замените getLayoutDirection на ViewCompat.getLayoutDirection, чтобы добиться большей совместимости. Во всяком случае, это не работает для меня на Android 7.0. Маржа не изменилась, и панель действий теперь закрывает строку состояния. - person Makalele; 11.11.2016
comment
Спасибо за ответ. - person Jinal Patel; 31.01.2017
comment
Получение Fatal Exception: java.lang.IllegalArgumentException: Scrapped or attached views may not be recycled. isScrap:false isAttached:true в onMeasure() - person Nikhil; 03.05.2017
comment
Я пробовал это и работал, но теперь весь мой макет активности переходит в строку состояния. Я установил для fitsSystemWindows значение true, но не работает. Помощь - person Taufik Nur Rahmanda; 25.12.2018
comment
Для меня лучший ответ - третий по рейтингу. Это очень точная установка ширины навигационного представления, равной ширине окна в пикселях, и не требует большого количества кода. - person Juan Mendez; 20.07.2019

Если вам нужно более простое решение, вы можете просто установить отрицательную маржу

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"/>
person Robert    schedule 30.10.2013
comment
Я думаю, что это должен был быть принятый ответ. Я посмотрел исходный код. Он не занимает 80% процентов пространства. Он просто поставил минимальную маржу 64dp. Разумно использовать отрицательную маржу. - person tasomaniac; 26.11.2013
comment
Работает отлично. Я заметил, что все еще был зазор в 1 пиксель, поэтому у меня работает 65dp. - person Chris; 08.01.2014
comment
просто без кода, лучше всего 65dp - person leegor; 21.03.2015
comment
Это решение не работает с Android 5.0 и выше :( - person nguoitotkhomaisao; 07.10.2015
comment
Работал на 5.0 и выше. - person JaydeepW; 04.11.2016
comment
Ты спас мне жизнь братан! Спасибо - person Adnan Bin Mustafa; 16.11.2016
comment
Это на sw600dp? например ? Я хочу, чтобы панель навигации была заполнена на экране планшета. Пока он отображается наполовину, и я не вижу, где его изменить и изменить настройку. @Роберт - person Madona Syombua; 06.05.2018
comment
Я установил его на 10 dp, а не на -10 dp и дал ожидаемый результат. Спасибо. - person Ramkesh Yadav; 05.08.2020

Поскольку все эти ответы не работали на ОС 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);

Это работает для всех размеров экрана.

person Aksiom    schedule 22.12.2015
comment
я не вижу, где половина экрана устанавливается на ширину (также я пробовал params.width = metrics.widthPixels/2), но он ничего не делает, независимо от того, что я меняю ширину на - person Lion789; 21.04.2016
comment
это решение работает в 2019 году. Я тестировал в версиях Android 22, 26, 28, мне это нравится, потому что точно установить вид навигации на всю ширину окна. Мне не нравится устанавливать фиксированное отрицательное правое дополнение ... странно, что это не самый популярный ответ. - person Juan Mendez; 20.07.2019
comment
Это рабочее решение, а не установка отрицательных полей. Спасибо. - person alierdogan7; 31.05.2021

Основываясь на ответе Роберта, вы можете легко решить эту проблему с помощью 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>

По сути, добавьте android:layout_marginRight="-64dp" к корню DrawerLayout, чтобы весь макет был справа для 64dp.

Затем я добавляю layout_marginRight=64dp к содержимому, чтобы оно вернулось в исходное положение. Тогда у вас может быть полный ящик там.

person gentra    schedule 08.02.2016
comment
это хороший обходной путь! Я предлагаю отметить это как правильный ответ - person Pietrek; 28.11.2019

Вариант решения Григория:

Вместо подкласса я вызываю следующий служебный метод сразу после получения ссылки на макет ящика:

/**
 * 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();
  }
}
person Eugen Pechanec    schedule 17.12.2014
comment
Я не уверен, что это правильный путь, но очень умное решение - person Rafay Ali; 18.05.2016
comment
Это не на 100% правильно. В идеале вы бы никогда не вызывали printStackTrace в рабочей среде и кэшировали бы f в статическом поле, чтобы выполнять поиск отражения только один раз. - person Eugen Pechanec; 25.10.2018

Класс Nipper FullDrawerLayout просто потрясающий. Его производительность также выше, чем у ящика по умолчанию, но вы не можете использовать его на устройствах с API, у которых нет view.getLayoutDirection(); (т.е. класс не работает на всех пряничных устройствах)

так что я сделал было

заменил все

view.getLayoutDirection();

с приведенным ниже кодом

GravityCompat.getAbsoluteGravity(gravity,ViewCompat.getLayoutDirection(this));

Я обновил свою библиотеку поддержки до последней версии, а также расширил fullDrawerlayout до навигационного ящика поддержки. Теперь он отлично работает и на устройствах Gingerbread.

person Aman Satija    schedule 22.10.2013

Другой возможный способ решить проблему, не переопределяя слишком много:

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 сделан таким негибким (я говорю о частной минимальной марже) в этот момент...

person GregoryK    schedule 27.12.2013

Попробуйте, это сработало для меня:

<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". Для устройств с разным размером экрана вы можете динамически устанавливать ширину включенного макета.

person GOLDEE    schedule 28.11.2013

Вы можете использовать это. Вдохновленный этим сообщением, я обновился до 5-го издания. Потому что у него были проблемы с StatusBar в версиях 5 и выше.

вам нужно расширить 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;
    }

}
person Rasoul Miri    schedule 12.08.2017

вы можете по коду ниже

 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);
person Kirtikumar A.    schedule 24.12.2014

<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.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">

    <FrameLayout
        android:id="@+id/container"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <include
            layout="@layout/app_bar_dashboard"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    </FrameLayout>


    <android.support.design.widget.NavigationView
        android:id="@+id/nav_view"
        android:layout_width="match_parent"
        android:layout_marginRight="32dp"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        android:fitsSystemWindows="true">

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

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

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

Это отлично работает для меня. Надеюсь помочь другим.

person Jamariya R    schedule 03.11.2017

Google рекомендует использовать максимальную ширину провала 320 в соответствии с рекомендациями по пользовательскому интерфейсу здесь. . Кроме того, ширину можно установить, указав layout_width для left_drawer ListView.

person bogdan    schedule 22.01.2014

Все думают, что создание макета боковой панели на всю ширину очень сложно, но это очень просто, если вы следуете этому шаблону макета, вам не нужно устанавливать минусовое значение.

Это мой MainActivity.xml:

<androidx.drawerlayout.widget.DrawerLayout
    android:id="@+id/drawerLayout"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:background="@color/white"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <!-- Main Activity -->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <include
            android:id="@+id/toolbarMain"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            layout="@layout/layout_profile_toolbar"/>

        <androidx.fragment.app.FragmentContainerView
            android:id="@+id/fragment"
            android:name="androidx.navigation.fragment.NavHostFragment"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:defaultNavHost="true"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:navGraph="@navigation/app_navigation" />

    </LinearLayout>
    <!-- Main Activity End -->

    <!-- Custom Navigation Drawer Start -->
    <com.google.android.material.navigation.NavigationView
        android:id="@+id/nav_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        android:fitsSystemWindows="true">

        <include
            android:id="@+id/custom_nav"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            layout="@layout/fragment_profile"/>

    </com.google.android.material.navigation.NavigationView>
    <!-- Custom Navigation Drawer End -->

</androidx.drawerlayout.widget.DrawerLayout>
person Siru malayil    schedule 03.06.2021

Вы также можете взглянуть на класс SlidingDrawer. Это устаревший класс, но, как сказано в документации, вы можете написать свою собственную реализацию на основе его исходного кода.

person Erik Ghonyan    schedule 13.06.2013