Навигационный ящик: всегда открыт на планшетах

Я использую шаблон Navigation Drawer из библиотеки поддержки: http://developer.android.com/training/implementing-navigation/nav-drawer.html

Я пытался настроить его так, чтобы он всегда открывался на планшете (как боковое меню)

введите здесь описание изображения

Это возможно с текущей реализацией, или нам нужно создать новый макет и новую структуру с Listview вместо повторного использования одного и того же кода?


person Waza_Be    schedule 16.06.2013    source источник


Ответы (5)


Основываясь на идее, что большие устройства могут иметь разные файлы макета, я создал следующий проект.

https://github.com/jiahaoliuliu/ABSherlockSlides

Основные моменты:

Поскольку ящик большого устройства всегда виден, нет необходимости иметь ящик. Вместо этого LinearLayout с двумя элементами с одинаковыми именами будет достаточно.

<LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:orientation="horizontal">
     <ListView
             android:id="@+id/listview_drawer"
             android:layout_width="@dimen/drawer_size"
             android:layout_height="match_parent"
             android:layout_gravity="start"
             android:choiceMode="singleChoice"
             android:divider="@android:color/transparent"
             android:dividerHeight="0dp"
             android:background="@color/drawer_background"/>
    <FrameLayout
            android:id="@+id/content_frame"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_marginLeft="@dimen/drawer_content_padding"
            />
</LinearLayout>

Поскольку у нас нет ящика в файле макета, когда приложение попытается найти элемент в макете, оно вернет значение null. Таким образом, нет необходимости иметь дополнительное логическое значение, чтобы увидеть, какой макет используется.

DrawerLayout mDrawerLayout = (DrawerLayout)findViewById(R.id.drawer_layout);

if (mDrawerLayout != null) {
    // Set a custom shadow that overlays the main content when the drawer opens
    mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START);
    // Enable ActionBar app icon to behave as action to toggle nav drawer
    getSupportActionBar().setHomeButtonEnabled(true);
    getSupportActionBar().setDisplayHomeAsUpEnabled(true);

    // ActionBarDrawerToggle ties together the proper interactions
    // between the sliding drawer and the action bar app icon
    mDrawerToggle = new ActionBarDrawerToggle(
            this,
            mDrawerLayout,
            R.drawable.ic_drawer,
            R.string.drawer_open,
            R.string.drawer_close) {

        public void onDrawerClosed(View view) {
            super.onDrawerClosed(view);
        }

        public void onDrawerOpened(View drawerView) {
            // Set the title on the action when drawer open
            getSupportActionBar().setTitle(mDrawerTitle);
            super.onDrawerOpened(drawerView);
        }
    };

    mDrawerLayout.setDrawerListener(mDrawerToggle);
}

Вот пример использования его как логического.

@Override
protected void onPostCreate(Bundle savedInstanceState) {
    super.onPostCreate(savedInstanceState);
    if (mDrawerLayout != null) {
        mDrawerToggle.syncState();
    }
}

@Override
public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);
    if (mDrawerLayout != null) {
        // Pass any configuration change to the drawer toggles
        mDrawerToggle.onConfigurationChanged(newConfig);
    }
}
person jiahao    schedule 25.09.2013
comment
Как точно узнать, что это планшет? Достаточно ли использовать res/layout-sw600dp-land? - person Alexander Farber; 10.08.2015
comment
Это старый, но интересный вопрос. Вы можете найти ответ здесь: stackoverflow.com/questions/11330363/ Обратите внимание, что простое создание этой папки и размещение в ней макета не означает, что этот макет будет использоваться. Если вы хотите, вы можете определить, планшет это или нет, а затем программно принудительно перевести его в ландшафтный режим. Тогда это сработает. Удачи! - person jiahao; 10.08.2015

Основываясь на ответе CommonsWare, вы можете сделать это с помощью нескольких корректировок. Первый устанавливает следующие три строки:

drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_OPEN);
drawerLayout.setScrimColor(getResources().getColor(R.color.drawerNoShadow));
isDrawerLocked = true;

Цвет drawerNoShadow может быть просто цветом без альфа-канала (например, 0x00000000). Это дает вам открытый ящик без наложения фона.

Второе, что вам нужно сделать, это настроить значение padding_left вашего FrameLayout. Для этого вы можете настроить размер для управления этим (по умолчанию 0dp) — в этом примере R.dimen.drawerContentPadding. Вам также понадобится значение R.dimen.drawerSize, которое будет шириной DrawerLayout.

Это позволяет вам проверить значение paddingLeft FrameLayout для вызова этих строк.

FrameLayout frameLayout = (FrameLayout)findViewById(R.id.content_frame);
if(frameLayout.getPaddingLeft() == (int)getResources().getDimension(R.dimen.drawerSize) {
    drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_OPEN);
    drawerLayout.setScrimColor(getResources().getColor(R.color.drawerNoShadow));
    isDrawerLocked = true;
}

Затем вы можете обернуть все функции, которые вы не хотите включать, в оператор if(!isDrawerLocked). Это будет включать:

  • drawerLayout.setDrawerListener(drawerToggle);
  • getActionBar().setDisplayHomeAsUpEnabled(true);

Наконец, вам нужно настроить альтернативные макеты для представлений со статическим ящиком. Пример:

<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v4.widget.DrawerLayout

    android:id="@+id/drawer_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <!-- The navigation drawer -->
    <ListView
        android:id="@+id/left_drawer"
        android:layout_width="@dimen/drawerSize"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        android:choiceMode="singleChoice"
        android:divider="@android:color/transparent"
        android:dividerHeight="0dp"
        android:background="#111"/>

</android.support.v4.widget.DrawerLayout>
<FrameLayout
    android:id="@+id/content_frame"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_marginLeft="@dimen/drawerContentPadding"/>

The beauty here is you can then control all of the logic by setting up alternate dimen.xml files for the devices you want to target and the only thing you need to change is the value of drawerContentPadding and offer the modified layouts.

ПРИМЕЧАНИЕ. В итоге я использовал margin_left вместо padding_left, так как в новом макете он перекрывает ящик. См. более подробную публикацию в блоге об этой технике по адресу http://derekrwoods.com/2013/09/creating-a-static-navigation-drawer-in-android/

person methodin    schedule 07.08.2013
comment
Отличный пример! Поскольку вы изменили его на padding_left вместо margin, вы больше не можете определить, какой макет в вашем примере. Однако, если вы установите тег для каждого из макетов, вы можете сравнить тег, чтобы определить, какой макет используется. *** Однако еще одна вещь: я обнаружил, что после того, как ящик заблокирован в открытом положении, когда я переключаюсь обратно в портретный режим, чтобы он открывал / закрывал действие смахивания, больше не открывал ящик. Есть идеи, почему? - person Tony Maro; 23.09.2013
comment
Я также только что понял, что после переключения из открытого ящика обратно в портретный режим мое событие onKeyDown перестает запускаться для активности! Я использую фрагменты совместимости, интересно, есть ли где-то ошибка... - person Tony Maro; 23.09.2013
comment
Я решил большинство своих проблем, переместив этот код в onPostCreate(), но я все еще получаю сбой, если вы пытаетесь закрыть ящик, когда он заблокирован. - person Tony Maro; 23.09.2013
comment
Итак, чтобы продолжить мое личное обсуждение - я исправил проблему с кнопкой «Назад» и сбои. Мне пришлось поместить фиктивный макет рамки внутри DrawerLayout для просмотра планшета, чтобы остановить сбой. Затем мне пришлось установить mDrawerLayout.setFocusableInTouchMode(false); чтобы он не перебивал сенсорный ввод. - person Tony Maro; 23.09.2013
comment
Посмотрим, смогу ли я раскопать этот код — он полностью интегрирован в два приложения. - person methodin; 24.09.2013
comment
Я добавил более подробное объяснение процесса, который я использовал, на странице derekrwoods.com/2013/09/ - person methodin; 25.09.2013
comment
Спасибо! Интересно, сколько проблем, с которыми я столкнулся, были связаны с различиями в библиотеке совместимости. Я ожидаю, что сбои при касании заставили меня переместить код в onPostCreate(). С помощью этого ответа я смог закончить свое приложение в прошлые выходные и выпустил его. Работал как шарм. - person Tony Maro; 25.09.2013
comment
Рад это слышать. Я предполагаю, что библиотеки Compat будут иметь разные отличия - возможно, вам стоит разместить статью где-нибудь и дать ссылку на нее для всех, кто работает с библиотекой Compat! - person methodin; 25.09.2013
comment
я получаю исключение нулевого указателя при касании content_frame. - person madan V; 10.03.2014
comment
@Tony Maro - я обнаружил, что после того, как ящик был заблокирован в открытом положении, когда я переключаюсь обратно в портретный режим, чтобы он открывал / закрывал, действие смахивания больше не открывает ящик. Есть идеи, почему? Как вы решили эту проблему? любые предложения, пожалуйста - person Akh; 11.02.2015
comment
Ящик кажется застрявшим в открытом положении при переключении обратно в портретный режим. Исправлена ​​эта проблема путем разблокировки ящика, который ранее был заблокирован в ландшафтном режиме. drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED); - person Akh; 11.02.2015
comment
@methodin Я был бы очень признателен, если бы вы добавили полный код в свой блог. - person eddy; 02.03.2015
comment
@SamerZuhair Да, я пытался, но слишком много частей отсутствует, поэтому, как новичок, я не могу запустить его :( - person eddy; 02.03.2015
comment
@eddy, если вы знаете, как запустить код stackoverflow.com/a/28809296/2148631 - person zaPlayer; 02.03.2015
comment
Этот код работает? Как мне вернуться к перетаскиваемому навигационному ящику? И что должно быть триггером (в коде), когда это использовать? - person android developer; 01.05.2015
comment
Чтобы решить проблему перетаскивания ящика альбомной/портретной ориентации, просто поместите android:saveEnabled="false" в тег DrawerLayout в обоих xml-файлах. Это отключает предыдущее состояние вида при изменении поворота, что приводит к сбросу ящика. - person waqaslam; 26.05.2015

Попробуйте setDrawerLockMode() заблокировать ящик открытым на больших экранах. экранные устройства.

Как я отметил в комментарии, я не думаю, что DrawerLayout предназначен для вашего сценария (хотя это неплохая идея, ИМХО). Либо используйте другой макет, в котором размещены те же ListView и содержимое, либо, возможно, загрузите и измените свою собственную копию DrawerLayout, которая на устройствах с большим экраном скользит поверх содержимого при открытии, а не перекрывает его.

person CommonsWare    schedule 16.06.2013
comment
Ящик открыт, но находится перед моей реальной планировкой. - person Waza_Be; 19.06.2013
comment
@Waza_Be: Ах да. Я не думаю, что DrawerLayout предназначен для вашего сценария. Либо используйте другой макет, в котором размещены те же ListView и содержимое, либо, возможно, загрузите и измените свою собственную копию DrawerLayout, которая на устройствах с большим экраном скользит поверх содержимого при открытии, а не перекрывает его. - person CommonsWare; 19.06.2013
comment
Я принял ваш ответ, но было бы полезно, если бы вы скопировали свой комментарий внутри ответа ;-) - person Waza_Be; 19.06.2013

Просто предоставьте альтернативный файл макета для планшетов. Таким образом, вы можете сохранить все варианты поведения NavigationView по умолчанию.


Шаг 1

Просто создайте альтернативный файл макета, подобный этому, для планшетных устройств и поместите его в каталог ресурсов layout-w600dp-land.

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

    <!--
    NavigationView and the content is placed in a horizontal LinearLayout
    rather than as the direct children of DrawerLayout.
    This makes the NavigationView always visible.
    -->

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <android.support.design.widget.NavigationView
            android:id="@+id/nav"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:fitsSystemWindows="true"
            app:headerLayout="@layout/nav_header_main"
            app:menu="@menu/activity_main_drawer"/>
        <include
            layout="@layout/app_bar_main"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    </LinearLayout>

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



Шаг 2

На этом шаге мы внесем достаточно изменений, чтобы открытие и закрытие ящика работало только на устройствах, отличных от планшетов.

Шаг 2.1

Добавьте следующее содержимое в новый файл ресурсов значений в каталоге значений и назовите его config_ui.xml.

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <bool name="isDrawerFixed">false</bool>
</resources>

Это было для непланшетных устройств. Для планшетных устройств создайте еще один с тем же именем и поместите его в values-w600dp-land.

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <bool name="isDrawerFixed">true</bool>
</resources>

Создайте новое поле в классе действия, которому принадлежит ящик, как
private boolean isDrawerFixed;
и инициализируйте его как
isDrawerFixed = getResources().getBoolean(R.bool.isDrawerFixed);.

Теперь мы можем проверить, является ли устройство планшетным или непланшетным, так же просто, как if (isDrawerFixed){}.

Шаг 2.2

Оберните код, устанавливающий кнопку-переключатель на панели действий, оператором if, подобным этому.

if (!isDrawerFixed) {
    ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
    drawer.addDrawerListener(toggle);
    toggle.syncState();
}

Оберните код, который закрывает ящик при нажатии на элемент, другим оператором if, подобным этому.

if (!isDrawerFixed) {
    drawer.closeDrawer(GravityCompat.START);
}




Окончательный результат будет выглядеть примерно так.

выход

person Bertram Gilfoyle    schedule 01.06.2018
comment
Это отличный ответ. Гораздо проще и разумнее, чем другие предусмотренные. Спасибо! Я использовал RelativeLayout, а не LinearLayout, я не мог отобразить фрагмент main/detail, используя только LinearLayout. Еще раз спасибо. Дополнительные баллы за предоставление скриншотов для доказательства и сравнения с моим устройством. - person Sakiboy; 11.08.2020

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

public class MyDrawerLayout extends DrawerLayout {
    private boolean m_disallowIntercept;

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

    @Override
    public boolean onInterceptTouchEvent(final MotionEvent ev) {
        // as the drawer intercepts all touches when it is opened
        // we need this to let the content beneath the drawer to be touchable
        return !m_disallowIntercept && super.onInterceptTouchEvent(ev);
    }

    @Override
    public void setDrawerLockMode(int lockMode) {
        super.setDrawerLockMode(lockMode);
        // if the drawer is locked, then disallow interception
        m_disallowIntercept = (lockMode == LOCK_MODE_LOCKED_OPEN);
    }
}

Затем мы помещаем его в базовый макет активности (без произвольных макетов из предыдущих ответов) следующим образом:

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

    <!--We must define left padding for content-->
    <FrameLayout
        android:id="@+id/content_frame"
        android:paddingStart="@dimen/content_padding"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

    <android.support.design.widget.NavigationView
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        android:fitsSystemWindows="true"
        app:menu="@menu/menu_nav" />

</MyDrawerLayout>

Заполнение содержимого здесь составляет 0 dp в портретной ориентации и около 300 dp в альбомной ориентации для NavigationView (выяснено эмпирическим путем). Определяем их в соответствующих values папках:

values/dimens.xml -

<dimen name="content_padding">0dp</dimen>

values-land/dimens.xml -

<dimen name="content_padding">300dp</dimen>

Наконец, мы блокируем ящик в действии:

    if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
        mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_OPEN);
        mDrawerLayout.setScrimColor(0x00000000); // or Color.TRANSPARENT
        isDrawerLocked = true;
    } else if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
        mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED);
        mDrawerLayout.setScrimColor(0x99000000); // default shadow
        isDrawerLocked = false;
    }
person mister_potato    schedule 29.05.2017
comment
Я думаю, что это, безусловно, лучший ответ, поскольку в нем нет дублирующегося кода, и он использует API-интерфейс NavigationView, уступая место навигационным графикам. Хорошая работа. - person John Shelley; 07.01.2019