Скрыть навигационный ящик при нажатии кнопки «Назад»

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

На данный момент все работает нормально, за исключением случаев, когда пользователь использует встроенную кнопку обратной связи, которую Android поддерживает в нижней части экрана (наряду с домашними и последними кнопками приложения). Если пользователь перейдет обратно с помощью этой кнопки «назад», навигационный ящик по-прежнему будет открыт. Если пользователь вместо этого перемещается назад с помощью ActionBar, навигационный ящик будет закрыт, как я хочу.

Мой код почти идентичен официальным учебным пособиям, за исключением того, как я обрабатываю пользователя, выбрав элемент в ящике:

mDrawerList.setOnItemClickListener(new ListView.OnItemClickListener() { @Override public void onItemClick(AdapterView parent, View view, int position, long id) { switch(position) { case 0: { Intent intent = new Intent(MainActivity.this, NextActivity.class); startActivity(intent); } } } }); 

Как я могу закрыть ящик навигации, когда пользователь перейдет обратно с помощью кнопки «Назад»? Любые советы приветствуются. Благодаря!

    Вы должны переопределить onBackPressed () . Из документов:

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

    Таким образом, вы можете иметь такой код:

     @Override public void onBackPressed() { if (this.drawerLayout.isDrawerOpen(GravityCompat.START)) { this.drawerLayout.closeDrawer(GravityCompat.START); } else { super.onBackPressed(); } } 

    Если это открыто, этот метод закрывает его, а затем возвращается к поведению по умолчанию.

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

     @Override public void onBackPressed(){ if(drawer.isDrawerOpen()){ //replace this with actual function which returns if the drawer is open drawer.close(); // replace this with actual function which closes drawer } else{ super.onBackPressed(); } } 

    Чтобы заменить просмотр псевдокода в документации для ящика. Я знаю, что оба эти метода существуют.

    Используя реализацию ответа, предоставленного @James Cross, работала, но анимация, чтобы закрыть ящик, была нежелательной и неустранимой без особых хлопот, например .

     @Override public void onResume() { super.onResume(); mDrawerLayout.closeDrawers(); } 

    Обход – это перезапуск активности при нажатии кнопки возврата устройства. Для меня это не кажется идеальным, но оно работает. Переопределение onBackPressed() , как это было предложено @ mt0s и @Qazi Ahmed, и передачу дополнительного для определения вызывающей активности:

      mDrawerList.setOnItemClickListener(new ListView.OnItemClickListener() { @Override public void onItemClick(AdapterView parent, View view, int position, long id) { switch(position) { case 0: { Intent intent = new Intent(MainActivity.this, NextActivity.class); //pass int extra to determine calling activity intent.putExtra(EXTRA_CALLING_ACTIVITY, CallingActivityInterface.MAIN_ACTIVITY); startActivity(intent); } } } }); 

    В NextActivity.class проверьте активность вызова:

     @Override public void onBackPressed() { int callingActivity = getIntent().getIntExtra(EXTRA_CALLING_ACTIVITY, CallingActivityInterface.MAIN_ACTIVITY); switch(callingActivity) { case CallingActivityInterface.MAIN_ACTIVITY: { Intent intent = new Intent(this, MainActivity.class); startActivity(intent); finish(); } ... } } 

    Таким образом, ящик закрывается без анимации, когда я возвращаюсь в MainActivity независимо от того, использую ли я кнопку «вверх» или кнопку «Назад». Вероятно, есть лучшие способы сделать это. Мое приложение относительно просто на данный момент, и это работает, но я жду более эффективного метода, если у кого-то есть.

    ОБНОВИТЬ:

    С поддержкой библиотеки 24.0.0 это возможно без каких-либо обходных решений. В DrawerLayout добавлены два новых метода openDrawer и closeDrawer , которые позволяют открывать или закрывать ящик без анимации.

    Теперь вы можете использовать openDrawer(drawerView, false) и closeDrawer(drawerView, false) чтобы открывать и закрывать ящик без задержки.


    Если вы вызываете startActivity() без вызова closeDrawer() , ящик будет оставлен открытым в этом экземпляре активности, когда вы перейдете к нему с помощью кнопки «Назад». Вызов функции closeDrawer() при вызове функции startActivity() имеет несколько проблем, начиная от прерывистой анимации и заканчивая длительной задержкой восприятия, в зависимости от того, какой метод обхода вы используете. Поэтому я согласен, что лучший подход – просто вызвать startActivity() а затем закрыть ящик по возвращении.

    Чтобы сделать эту работу красивой, вам необходимо закрыть ящик без близкой анимации, когда вы перейдете к активности с помощью кнопки «Назад». (Относительно расточительным обходным путем было бы просто заставить активность recreate() при навигации назад, но это можно решить, не делая этого.)

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


    Детали

    (Вы можете пропустить это объяснение, если хотите просто посмотреть код.)

    Хотя вызов функции closeDrawer() из onCreate() приведет к тому, что ящик начнет закрываться без какой-либо анимации, то же самое не относится к onResume() . Вызов функции closeDrawer() из onResume() закроет ящик с анимацией, которая мгновенно отображается пользователю. DrawerLayout не предоставляет никакого способа закрыть ящик без этой анимации, но его можно расширить, чтобы добавить его.

    Закрытие ящика фактически просто сдвигает его с экрана, поэтому вы можете эффективно пропускать анимацию, перемещая ящик непосредственно в «закрытое» положение. Направление перевода будет меняться в зависимости от силы тяжести (будь то левый или правый ящик), и точное положение зависит от размера выдвижного ящика, когда оно выложено всеми его детьми.

    Однако просто перемещать его недостаточно, поскольку DrawerLayout сохраняет некоторое внутреннее состояние в расширенных LayoutParams которое оно использует, чтобы узнать, открыт ли ящик. Если вы просто переместите ящик с экрана, он не будет знать, что он закрыт, и это вызовет другие проблемы. (Например, ящик снова появится при следующем изменении ориентации.)

    Поскольку вы компилируете библиотеку поддержки в свое приложение, вы можете создать класс в пакете android.support.v4.widget , чтобы получить доступ к его частям по умолчанию (пакет-частный) или расширить DrawerLayout без копирования через любой другой Классов. Это также уменьшит нагрузку на обновление кода с будущими изменениями в библиотеке поддержки. (Всегда лучше изолировать свой код от деталей реализации как можно больше.) Вы можете использовать moveDrawerToOffset() для перемещения ящика и установки LayoutParams чтобы он знал, что ящик закрыт.


    Код

    Это код, который пропустит анимацию:

      // move drawer directly to the closed position moveDrawerToOffset(drawerView, 0.f); // set internal state so DrawerLayout knows it's closed final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams(); lp.onScreen = 0.f; lp.knownOpen = false; invalidate(); 

    Примечание: если вы просто вызываете moveDrawerToOffset() без изменения LayoutParams , ящик вернется в открытое положение при следующем изменении ориентации.


    Вариант 1 (используйте существующий DrawerLayout)

    Этот подход добавляет класс утилиты в пакет support.v4, чтобы получить доступ к частным частям пакета, которые нам нужны внутри DrawerLayout.

    Поместите этот класс в / src / android / support / v4 / widget /:

     package android.support.v4.widget; import android.support.annotation.IntDef; import android.support.v4.view.GravityCompat; import android.view.Gravity; import android.view.View; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; public class Support4Widget { /** @hide */ @IntDef({Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END}) @Retention(RetentionPolicy.SOURCE) private @interface EdgeGravity {} public static void setDrawerClosed(DrawerLayout drawerLayout, @EdgeGravity int gravity) { final View drawerView = drawerLayout.findDrawerWithGravity(gravity); if (drawerView == null) { throw new IllegalArgumentException("No drawer view found with gravity " + DrawerLayout.gravityToString(gravity)); } // move drawer directly to the closed position drawerLayout.moveDrawerToOffset(drawerView, 0.f); // set internal state so DrawerLayout knows it's closed final DrawerLayout.LayoutParams lp = (DrawerLayout.LayoutParams) drawerView.getLayoutParams(); lp.onScreen = 0.f; lp.knownOpen = false; drawerLayout.invalidate(); } } 

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

     public static final String CLOSE_NAV_DRAWER = "CLOSE_NAV_DRAWER"; private boolean mCloseNavDrawer; @Override public void onCreate(Bundle savedInstanceState) { // ... if (savedInstanceState != null) { mCloseNavDrawer = savedInstanceState.getBoolean(CLOSE_NAV_DRAWER); } } @Override public boolean onNavigationItemSelected(MenuItem menuItem) { // ... startActivity(intent); mCloseNavDrawer = true; } @Override public void onSaveInstanceState(Bundle savedInstanceState) { savedInstanceState.putBoolean(CLOSE_NAV_DRAWER, mCloseNavDrawer); super.onSaveInstanceState(savedInstanceState); } 

    … и используйте метод setDrawerClosed() чтобы закрыть ящик в onResume() без анимации:

     @Overrid6e protected void onResume() { super.onResume(); if(mCloseNavDrawer && mDrawerLayout != null && mDrawerLayout.isDrawerOpen(GravityCompat.START)) { Support4Widget.setDrawerClosed(mDrawerLayout, GravityCompat.START); mCloseNavDrawer = false; } } 

    Вариант 2 (простирается от DrawerLayout)

    Этот подход расширяет DrawerLayout, чтобы добавить метод setDrawerClosed ().

    Поместите этот класс в / src / android / support / v4 / widget /:

     package android.support.v4.widget; import android.content.Context; import android.support.annotation.IntDef; import android.support.v4.view.GravityCompat; import android.util.AttributeSet; import android.view.Gravity; import android.view.View; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; public class CustomDrawerLayout extends DrawerLayout { /** @hide */ @IntDef({Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END}) @Retention(RetentionPolicy.SOURCE) private @interface EdgeGravity {} public CustomDrawerLayout(Context context) { super(context); } public CustomDrawerLayout(Context context, AttributeSet attrs) { super(context, attrs); } public CustomDrawerLayout(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } public void setDrawerClosed(View drawerView) { if (!isDrawerView(drawerView)) { throw new IllegalArgumentException("View " + drawerView + " is not a sliding drawer"); } // move drawer directly to the closed position moveDrawerToOffset(drawerView, 0.f); // set internal state so DrawerLayout knows it's closed final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams(); lp.onScreen = 0.f; lp.knownOpen = false; invalidate(); } public void setDrawerClosed(@EdgeGravity int gravity) { final View drawerView = findDrawerWithGravity(gravity); if (drawerView == null) { throw new IllegalArgumentException("No drawer view found with gravity " + gravityToString(gravity)); } // move drawer directly to the closed position moveDrawerToOffset(drawerView, 0.f); // set internal state so DrawerLayout knows it's closed final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams(); lp.onScreen = 0.f; lp.knownOpen = false; invalidate(); } } 

    Используйте CustomDrawerLayout вместо DrawerLayout в ваших макетах действий:

     <android.support.v4.widget.CustomDrawerLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/drawer_layout" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true" > 

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

     public static final String CLOSE_NAV_DRAWER = "CLOSE_NAV_DRAWER"; private boolean mCloseNavDrawer; @Override public void onCreate(Bundle savedInstanceState) { // ... if (savedInstanceState != null) { mCloseNavDrawer = savedInstanceState.getBoolean(CLOSE_NAV_DRAWER); } } @Override public boolean onNavigationItemSelected(MenuItem menuItem) { // ... startActivity(intent); mCloseNavDrawer = true; } @Override public void onSaveInstanceState(Bundle savedInstanceState) { savedInstanceState.putBoolean(CLOSE_NAV_DRAWER, mCloseNavDrawer); super.onSaveInstanceState(savedInstanceState); } 

    … и используйте метод setDrawerClosed() чтобы закрыть ящик в onResume() без анимации:

     @Overrid6e protected void onResume() { super.onResume(); if(mCloseNavDrawer && mDrawerLayout != null && mDrawerLayout.isDrawerOpen(GravityCompat.START)) { mDrawerLayout.setDrawerClosed(GravityCompat.START); mCloseNavDrawer = false; } } 

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

     @Override public void onResume(){ mDrawerList.closeDrawer(Gravity.LEFT); } 

    Вот альтернативное решение вашей проблемы.

     @Override public void onBackPressed(){ if(drawerLayout.isDrawerOpen(navigationView)){ drawerLayout.closeDrawer(navigationView); }else { finish(); } } 

    Почему хлопот? Просто закройте ящик при нажатии на элемент ящика. Вот как это делается в официальном приложении Google Play.

     private class DrawerItemClickListener implements ListView.OnItemClickListener { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { drawerLayout.closeDrawer(GravityCompat.START, false); selectItem(position); } }