Как мне поддерживать режим погружения в диалогах?
Как мне поддерживать новый режим погружения, когда мои действия отображают пользовательский диалог?
Я использую этот код для поддержки режима погружения в диалогах, но с этим решением NavBar появляется менее чем на секунду, когда я запускаю свой собственный диалог, затем он исчезает.
Вот видео, которое лучше объясняет проблему (посмотрите на нижнюю часть экрана, когда появится NavBar): http://youtu.be/epnd5ghey8g
Как мне избежать этого поведения?
КОД
Отец всех видов деятельности в моем приложении:
public abstract class ImmersiveActivity extends Activity {
@SuppressLint("NewApi")
private void disableImmersiveMode() {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {
getWindow().getDecorView().setSystemUiVisibility(
View.SYSTEM_UI_FLAG_FULLSCREEN);
}
}
@SuppressLint("NewApi")
private void enableImmersiveMode() {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {
getWindow().getDecorView().setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
View.SYSTEM_UI_FLAG_FULLSCREEN |
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY |
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION);
}
}
/**
* Set the Immersive mode or not according to its state: enabled or not.
*/
protected void updateSystemUiVisibility() {
// Retrieve if the Immersive mode is enabled or not.
boolean enabled = getSharedPreferences(Util.PREF_NAME, 0).getBoolean(
"immersive_mode_enabled", true);
if (enabled) enableImmersiveMode();
else disableImmersiveMode();
}
@Override
public void onResume() {
super.onResume();
updateSystemUiVisibility();
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
updateSystemUiVisibility();
}
}
Все мои собственные диалоги вызывают этот метод в своих onCreate(. . .)
метод:
/**
* Copy the visibility of the Activity that has started the dialog {@link mActivity}. If the
* activity is in Immersive mode the dialog will be in Immersive mode too and vice versa.
*/
@SuppressLint("NewApi")
private void copySystemUiVisibility() {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {
getWindow().getDecorView().setSystemUiVisibility(
mActivity.getWindow().getDecorView().getSystemUiVisibility());
}
}
РЕДАКТИРОВАТЬ - РЕШЕНИЕ (благодаря Beaver6813, посмотрите его ответ для более подробной информации):
Все мои пользовательские диалоги переопределяют метод show следующим образом:
/**
* An hack used to show the dialogs in Immersive Mode (that is with the NavBar hidden). To
* obtain this, the method makes the dialog not focusable before showing it, change the UI
* visibility of the window like the owner activity of the dialog and then (after showing it)
* makes the dialog focusable again.
*/
@Override
public void show() {
// Set the dialog to not focusable.
getWindow().setFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);
copySystemUiVisibility();
// Show the dialog with NavBar hidden.
super.show();
// Set the dialog to focusable again.
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);
}
8 ответов
После долгих исследований этой проблемы есть хакерское исправление, которое заключается в разрыве класса Dialog. Панель навигации отображается, когда диалоговое окно добавляется в диспетчер окон, даже если вы устанавливаете видимость пользовательского интерфейса перед добавлением его в диспетчер. В примере Android Immersive отмечается, что:
// * Uses semi-transparent bars for the nav and status bars
// * This UI flag will *not* be cleared when the user interacts with the UI.
// When the user swipes, the bars will temporarily appear for a few seconds and then
// disappear again.
Я полагаю, что это то, что мы видим здесь (что взаимодействие с пользователем запускается, когда в диспетчер добавляется новый, фокусируемый вид окна).
Как мы можем обойти это? Сделайте диалог не сфокусированным при его создании (чтобы мы не инициировали взаимодействие с пользователем), а затем сделайте его фокусируемым после его отображения.
//Here's the magic..
//Set the dialog to not focusable (makes navigation ignore us adding the window)
dialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);
//Show the dialog!
dialog.show();
//Set the dialog to immersive
dialog.getWindow().getDecorView().setSystemUiVisibility(
context.getWindow().getDecorView().getSystemUiVisibility());
//Clear the not focusable flag from the window
dialog.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);
Понятно, что это не идеально, но, похоже, это ошибка Android, они должны проверить, есть ли в Window установленный режим погружения.
Я обновил свой рабочий тестовый код (простите за хакерский беспорядок) на Github. Я тестировал на эмуляторе Nexus 5, он, вероятно, взорвется чем-то меньшим, чем KitKat, но только для проверки концепции.
К вашему сведению, благодаря ответу @Beaver6813, я смог заставить это работать с помощью DialogFragment.
в методе onCreateView моего DialogFragment я просто добавил следующее:
getDialog().getWindow().setFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);
getDialog().getWindow().getDecorView().setSystemUiVisibility(getActivity().getWindow().getDecorView().getSystemUiVisibility());
getDialog().setOnShowListener(new DialogInterface.OnShowListener() {
@Override
public void onShow(DialogInterface dialog) {
//Clear the not focusable flag from the window
getDialog().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);
//Update the WindowManager with the new attributes (no nicer way I know of to do this)..
WindowManager wm = (WindowManager) getActivity().getSystemService(Context.WINDOW_SERVICE);
wm.updateViewLayout(getDialog().getWindow().getDecorView(), getDialog().getWindow().getAttributes());
}
});
Если вы хотите использовать onCreateDialog (), попробуйте этот класс. Это работает довольно хорошо для меня...
public class ImmersiveDialogFragment extends DialogFragment {
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog alertDialog = new AlertDialog.Builder(getActivity())
.setTitle("Example Dialog")
.setMessage("Some text.")
.create();
// Temporarily set the dialogs window to not focusable to prevent the short
// popup of the navigation bar.
alertDialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);
return alertDialog;
}
public void showImmersive(Activity activity) {
// Show the dialog.
show(activity.getFragmentManager(), null);
// It is necessary to call executePendingTransactions() on the FragmentManager
// before hiding the navigation bar, because otherwise getWindow() would raise a
// NullPointerException since the window was not yet created.
getFragmentManager().executePendingTransactions();
// Hide the navigation bar. It is important to do this after show() was called.
// If we would do this in onCreateDialog(), we would get a requestFeature()
// error.
getDialog().getWindow().getDecorView().setSystemUiVisibility(
getActivity().getWindow().getDecorView().getSystemUiVisibility()
);
// Make the dialogs window focusable again.
getDialog().getWindow().clearFlags(
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
);
}
}
Чтобы показать диалоговое окно, выполните следующие действия в своей деятельности...
new ImmersiveDialogFragment().showImmersive(this);
Комбинируя ответы здесь, я создал абстрактный класс, который работает во всех случаях:
public abstract class ImmersiveDialogFragment extends DialogFragment {
@Override
public void setupDialog(Dialog dialog, int style) {
super.setupDialog(dialog, style);
// Make the dialog non-focusable before showing it
dialog.getWindow().setFlags(
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);
}
@Override
public void show(FragmentManager manager, String tag) {
super.show(manager, tag);
showImmersive(manager);
}
@Override
public int show(FragmentTransaction transaction, String tag) {
int result = super.show(transaction, tag);
showImmersive(getFragmentManager());
return result;
}
private void showImmersive(FragmentManager manager) {
// It is necessary to call executePendingTransactions() on the FragmentManager
// before hiding the navigation bar, because otherwise getWindow() would raise a
// NullPointerException since the window was not yet created.
manager.executePendingTransactions();
// Copy flags from the activity, assuming it's fullscreen.
// It is important to do this after show() was called. If we would do this in onCreateDialog(),
// we would get a requestFeature() error.
getDialog().getWindow().getDecorView().setSystemUiVisibility(
getActivity().getWindow().getDecorView().getSystemUiVisibility()
);
// Make the dialogs window focusable again
getDialog().getWindow().clearFlags(
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
);
}
}
Это также работает над методом onDismiss вашего фрагмента диалога. И в этом методе вызовите метод действия, к которому он привязан, чтобы снова установить полноэкранные флаги.
@Override
public void onDismiss(DialogInterface dialog) {
super.onDismiss(dialog);
Logger.e(TAG, "onDismiss");
Log.e("CallBack", "CallBack");
if (getActivity() != null &&
getActivity() instanceof LiveStreamingActivity) {
((YourActivity) getActivity()).hideSystemUI();
}
}
И в вашей деятельности добавьте этот метод:
public void hideSystemUI() {
// Set the IMMERSIVE flag.
// Set the content to appear under the system bars so that the content
// doesn't resize when the system bars hide and show.
getWindow().getDecorView().setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION // hide nav bar
| View.SYSTEM_UI_FLAG_FULLSCREEN // hide status bar
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
}
Когда вы создаете свой собственный DialogFragment, вам нужно только переопределить этот метод.
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
Dialog dialog = super.onCreateDialog(savedInstanceState);
dialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);
return dialog;
}
Я знаю, что это старый пост, но мой ответ может помочь другим.
Ниже приведено хакерское исправление эффекта погружения в диалогах:
public static void showImmersiveDialog(final Dialog mDialog, final Activity mActivity) {
//Set the dialog to not focusable
mDialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);
mDialog.getWindow().getDecorView().setSystemUiVisibility(setSystemUiVisibility());
mDialog.setOnShowListener(new DialogInterface.OnShowListener() {
@Override
public void onShow(DialogInterface dialog) {
//Clear the not focusable flag from the window
mDialog.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);
//Update the WindowManager with the new attributes
WindowManager wm = (WindowManager) mActivity.getSystemService(Context.WINDOW_SERVICE);
wm.updateViewLayout(mDialog.getWindow().getDecorView(), mDialog.getWindow().getAttributes());
}
});
mDialog.getWindow().getDecorView().setOnSystemUiVisibilityChangeListener(new View.OnSystemUiVisibilityChangeListener() {
@Override
public void onSystemUiVisibilityChange(int visibility) {
if ((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0) {
mDialog.getWindow().getDecorView().setSystemUiVisibility(setSystemUiVisibility());
}
}
});
}
public static int setSystemUiVisibility() {
return View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_FULLSCREEN
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
}
Как
dialog.window.decorView.setSystemUiVisibility(context.window.decorView.getSystemUiVisibility())
устарело в API 30.
Поэтому нам нужно использовать новые
WindowInsetController
Вот новое решение.
Эти шаги будут применяться ко всем типам диалогов и всплывающих окон для достижения иммерсивного режима.
- Сначала вам нужно сделать диалог не фокусируемым.
- Покажи тогда диалог.
- Скрыть панель навигации и системные панели.
- Сделайте диалоговое окно снова доступным для фокуса, чтобы добиться сфокусированного поведения диалогового окна.
Вот фрагмент кода в случае диалога Материал.
val alertDialogBox = MaterialAlertDialogBuilder(context, R.style.MaterialAlertDialogTheme)
val dialog = alertDialogBox.create()
dialog.window?.setFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE)
dialog.show()
dialog.window?.insetsController?.hide(WindowInsetsCompat.Type.statusBars() or WindowInsetsCompat.Type.navigationBars())
dialog.window?.clearFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE)