How do I maintain the Immersive Mode in Dialogs?
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.
Maintain Immersive mode when DialogFragment is Shown
1. Explanation for solution.
I have taken the following quotes from the android api docs.
Using Immersive Full-Screen Mode
When immersive full-screen mode is enabled, your activity continues to
receive all touch events. The user can reveal the system bars with an
inward swipe along the region where the system bars normally appear.
This clears the SYSTEM_UI_FLAG_HIDE_NAVIGATION flag (and the
SYSTEM_UI_FLAG_FULLSCREEN flag, if applied) so the system bars become
visible. This also triggers your
View.OnSystemUiVisibilityChangeListener, if set.
Firstly, you don't need an OnSystemUiVisibilityChangeListener
when using sticky immersion.
However, if you'd like the system bars to automatically hide again
after a few moments, you can instead use the
SYSTEM_UI_FLAG_IMMERSIVE_STICKY flag. Note that the "sticky" version
of the flag doesn't trigger any listeners, as system bars temporarily
shown in this mode are in a transient state.
The recommendations for using sticky/non sticky immersion:
If you're building a truly immersive app, where you expect users to
interact near the edges of the screen and you don't expect them to
need frequent access to the system UI, use the IMMERSIVE_STICKY flag
in conjunction with SYSTEM_UI_FLAG_FULLSCREEN and
SYSTEM_UI_FLAG_HIDE_NAVIGATION. For example, this approach might be
suitable for a game or a drawing app.
However, you mention users needing the keyboard, so I suggest this:
Use Non-Sticky Immersion
If you're building a book reader, news reader, or a magazine, use the
IMMERSIVE flag in conjunction with SYSTEM_UI_FLAG_FULLSCREEN and
SYSTEM_UI_FLAG_HIDE_NAVIGATION. Because users may want to access the
action bar and other UI controls somewhat frequently, but not be
bothered with any UI elements while flipping through content,
IMMERSIVE is a good option for this use case.
2. Solution
My solution is to set up your view ui in the onActivityCreated of your fragments.
My example taken from ImmersiveModeFragment.java sample.
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
final View decorView = getActivity().getWindow().getDecorView();
decorView.setOnSystemUiVisibilityChangeListener(
new View.OnSystemUiVisibilityChangeListener() {
@Override
public void onSystemUiVisibilityChange(int i) {
hideSystemUI();
}
});
}
Create a separate method to manage the ui that you call from your OnSystemUiVisibilityChangeListener()
Taken from here non sticky immersion
private 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.
mDecorView.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);
}
You can then call this method again onResume
.
onResume(){
hideSystemUI();
}
3. Alternate solution.
If sticky immersion is what you really want you need to try a different approach.
For sticky immersion
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus) {
decorView.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
| View.SYSTEM_UI_FLAG_FULLSCREEN
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);}
}
I hope this solves your problems.
Showing BottomSheetDialogFragment in immersive mode
Add this:
override fun setupDialog(dialog: Dialog?, style: Int) {
super.setupDialog(dialog, style)
dialog?.window?.setFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE)
}
(from)
Also add this:
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
var viewParent = view
while (viewParent is View) {
viewParent.fitsSystemWindows = false
viewParent.setOnApplyWindowInsetsListener { _, insets -> insets }
viewParent = viewParent.parent as View?
}
}
What does this do? DialogFragment#onActivityCreated()
calls Dialog#setContentView()
, which wraps the Dialog's view in a private 'wrapInBottomSheet'. In order to set the proper flags of those wrapper views, we want to set the flags after they are wrapped, e.g. after super.onActivityCreated()
Also watch this talk for info on fitsSystemWindows
and window insets.
Hide system navigation bar when show DialogFragment
Followed this answer https://stackoverflow.com/a/23207365/8531215 ,I have tested and it works fine! modify onCreateDialog()
method a little bit
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
AlertDialog alertD = builder.create();
alertD.setView(view);
Dialog dialog = alertD.create();
dialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);
//Show the dialog!
dialog.show();
//Set the dialog to immersive sticky mode
dialog.getWindow().getDecorView().setSystemUiVisibility(
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION);
//Clear the not focusable flag from the window
dialog.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);
return alertD;
}
Related Topics
Android Background Image Memory Usage
Actionbar Logo Centered and Action Items on Sides
Android Viewmodel Additional Arguments
Disable Orange Outline Highlight on Focus
Android - Extracting Cookies After Login in Webview
How to Display Image in Android's Textview
Navigationview and Custom Layout
How to Filter Android Logcat by Application
Can't Include C++ Headers Like Vector in Android Ndk
How to Add New Column to Android SQLite Database
Android + Pair Devices via Bluetooth Programmatically
Causing Outofmemoryerror in Frame by Frame Animation in Android
Is 'Shouldoverrideurlloading' Really Deprecated? What How to Use Instead
How to Get Hosting Activity from a View
Disable Edittext Blinking Cursor
How to Use the Paid Version of My App as a "Key" to the Free Version
How to Add Shortcut to Home Screen in Android Programmatically