如何在对话框中保持沉浸模式?

当活动显示自定义对话框时,如何维护新的沉浸模式?

我使用下面的代码来维护对话框中的沉浸模式,但是使用这个解决方案,当我启动自定义对话框时,导航栏出现不到一秒钟,然后它就消失了。

下面的视频更好地解释了这个问题(当 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 in the settings:
* 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();
}


}


我所有的自定义 Dialogs 都在它们的 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);
}
34741 次浏览

After a lot of research into the issue there is a hacky fix for this, which involved tearing apart the Dialog class to find. The navigation bar is shown when the dialog window is added to the Window Manager even if you set the UI visibility before adding it to the manager. In the Android Immersive example it's commented that:

// * 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.

I believe that's what we're seeing here (that a user-interaction is being triggered when a new, focusable, window view is added to the manager).

How can we work around this? Make the Dialog non-focusable when we create it (so we don't trigger a user-interaction) and then make it focusable after it's displayed.

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

Clearly this is not ideal but it seems to be an Android bug, they should check if the Window has immersive set.

I've updated my working test code (forgive the hacky messiness) to Github. I've tested on the Nexus 5 emulator, it will probably blow up with anything less than KitKat but its for proof-of-concept only.

For your information, thanks to @Beaver6813's answer, I've been able to get this working using DialogFragment.

in the onCreateView method of my DialogFragment, I've just added the following :

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

Whean you are creating your own DialogFragment you need only override this method.

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

If you want to use onCreateDialog(), try this class. It works pretty well for me...

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


}


}

To show the dialog, do the following in your activity...

new ImmersiveDialogFragment().showImmersive(this);

I know this is old post, but my answer may help others.

Below is the hacky fix for Immersive effect in Dialogs:

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

Combining the answers here I made an abstract class that works in all cases:

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

This also works over ride the onDismiss method of your dialog fragment . And within that method call the method of the activity to which it is attached to again set the full screen flags .

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

And in your activity add this method :

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

As

dialog.window.decorView.setSystemUiVisibility(context.window.decorView.getSystemUiVisibility())

is deprecated in Api 30.

So We need to use new WindowInsetController

Here is new Solution.

These steps will apply for all type of Dialogs and popups to achieve the immersive mode.

  1. You need to make dialog not focusable first.
  2. Show the dialog then.
  3. Hide the navigation and system bars.
  4. Make the dialog focusable again to achieve the focused dialog behavior.

Here is the code snippet in case of Material dialog.

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)