Закрытие навигационного ящика после открытия другого действия

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

drawerLayout.closeDrawer(drawerListView); 

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

Так что я мог бы закрыть ящик после создания второго действия? Я имею в виду от второго activtiy's onCreate или где-то еще?

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

 View view = drawerLayout.getChildAt(drawerLayout.getChildCount() - 1); ViewTreeObserver vto = view.getViewTreeObserver(); vto.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { @Override public boolean onPreDraw() { final DrawerLayout.LayoutParams lp = new DrawerLayout.LayoutParams(view.getWidth(), view.getHeight()); lp.gravity = Gravity.LEFT; view.setLayoutParams(lp); view.setLeft(-view.getMeasuredWidth()); view.getViewTreeObserver().removeOnPreDrawListener(this); return true; } }); 

объяснение

Сначала найдите представление, соответствующее представлению навигации, это будет последний дочерний элемент drawerLayout. Установите левое положение до минус его ширины (для левого выдвижного ящика).

Как упоминал Лорн Лалиберте, вам также необходимо изменить LayoutParams.knownOpen на false, чтобы это работало, однако нет доступа к этому, чтобы не создавать локальную копию appcompat-v4 и редактировать ее, поскольку это частное поле. Вот где мой трюк входит. В java значение по умолчанию для boolean равно false. Создание новых LayoutParams со старой шириной и высотой приведет к тому, что значение параметра knownOpen будет установлено на false. Затем мы можем установить это на вид, перезаписывающий старые LayoutParams. Это должно быть помещено внутри приемника предра, в случае, если представление еще не было выложено, например, после того, как экран был повернут.

Пожалуйста, спросите меня, есть ли у кого-нибудь проблемы с тем, чтобы это работало.

Вы можете использовать новые DrawerLayout.closeDrawer(int/View, bool) в v24 библиотеки поддержки, чтобы мгновенно закрыть ящик:

 drawerLayout.closeDrawer(Gravity.LEFT, false); 

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

Закройте ящик перед вызовом намерения.

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

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

Тем не менее, вам понадобится способ закрыть ящик без анимации при навигации по исходному действию с помощью кнопки «Назад».


Детали

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

Чтобы выполнить эту работу, вам понадобится способ закрыть ящик без какой-либо близкой анимации, когда вы переходите к действию с помощью кнопки «Назад». (Не вызывая closeDrawer() вы оставите ящик открытым в этом экземпляре активности, относительно расточительным обходным путем будет просто заставить активность 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); /* EDIT: as of v23.2.1 this direct approach no longer works because the LayoutParam fields have been made private... // set internal state so DrawerLayout knows it's closed final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams(); lp.onScreen = 0.f; lp.knownOpen = false; invalidate(); /*/ // ...however, calling closeDrawer will set those LayoutParams // and invalidate the view. closeDrawer(drawerView); /**/ 

Примечание: если вы просто вызываете 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); /* EDIT: as of v23.2.1 this no longer works because the LayoutParam fields have been made private, but calling closeDrawer will achieve the same result. // 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(); /*/ // Calling closeDrawer updates the internal state so DrawerLayout knows it's closed // and invalidates the view for us. drawerLayout.closeDrawer(drawerView); /**/ } } 

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

 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); /* EDIT: as of v23.2.1 this no longer works because the LayoutParam fields have been made private, but calling closeDrawer will achieve the same result. // set internal state so DrawerLayout knows it's closed final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams(); lp.onScreen = 0.f; lp.knownOpen = false; invalidate(); /*/ // Calling closeDrawer updates the internal state so DrawerLayout knows it's closed // and invalidates the view for us. closeDrawer(drawerView); /**/ } 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); /* EDIT: as of v23.2.1 this no longer works because the LayoutParam fields have been made private, but calling closeDrawer will achieve the same result. // set internal state so DrawerLayout knows it's closed final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams(); lp.onScreen = 0.f; lp.knownOpen = false; invalidate(); /*/ // Calling closeDrawer updates the internal state so DrawerLayout knows it's closed // and invalidates the view for us. closeDrawer(drawerView); /**/ } } 

Используйте 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; } }