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


104

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

我正在使用下面的代码来维护“对话框”中的“沉浸模式”,但是使用该解决方案,当启动自定义对话框时,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 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();
    }

}


我所有的自定义对话框都在其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);
}

您如何显示对话框?您是否使用DialogFragments?
Tadeas Kriz 2014年

我不使用DialogFragments,而是使用自定义Dialog子类。developer.android.com/reference/android/app/Dialog.html我仅通过调用它们的show()方法来显示对话框。
VanDir 2014年

当对话框出现时,正在调用WindowFocusChanged。出现对话框时,启用的值是什么?是真的还是出了什么问题?是假的吗?
凯文·范·米洛

您是指Dialog类(而不是Activity类)的onWindowFocusChanged(boolean hasFocus)方法吗?在这种情况下,“ hasFocus”标志为true。
VanDir 2014年

5
是否有人将沉浸模式与dialogfragments一起使用?
gbero

Answers:


167

在对该问题进行了大量研究之后,针对此问题进行了修正,其中涉及拆开Dialog类进行查找。将对话框窗口添加到“窗口管理器”时,即使在设置UI可见性之前将其添加到窗口管理器中,也会显示导航栏。在Android沉浸式示例中,它的注释是:

// * 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是否具有沉浸式设置。

我已经将我的工作测试代码(原谅hacky的麻烦)更新为Github。我已经在Nexus 5模拟器上进行了测试,它可能会爆破除KitKat之外的任何东西,但仅用于概念验证。


3
感谢您的示例项目,但是没有用。看看我的Nexus 5发生了什么:youtu.be/-abj3V_0zcU
VanDir 2014年

经过对该问题的一些调查后,我已经更新了答案,这是一个艰难的尝试!在Nexus 5模拟器上进行了测试,希望这一方法能奏效。
Beaver6813 2014年

2
我得到“添加内容之前必须先调用requestFeature()”(我认为这取决于活动中的活动功能)。解决方案:将dialog.show()向上移动一行,以便在复制SystemUiVisibility之前(但在设置不可聚焦之后)调用show()。然后它仍然可以正常工作,而无需跳过导航栏。
arberg 2014年

5
@ Beaver6813在使用EditText且DialogFragment中显示软键盘时不起作用。您是否有处理建议。
androidcodehunter 2014年

1
嗨Beaver6813。当对话框中有编辑文本时,它不起作用。有什么解决办法吗?
纳文·古普塔

33

对于您的信息,多亏@ Beaver6813的回答,我已经能够使用DialogFragment使其正常工作。

在我的DialogFragment的onCreateView方法中,我刚刚添加了以下内容:

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

5
这是否适用于onCreateDialog()中的Builder模式?我尚未获得与DialogFragments一起使用的技巧,该对话框使我的应用程序崩溃,“添加内容之前必须先调用requestFeature()”
IAmKale 2014年

1
这很棒,是我使用DialogFragments的唯一解决方案。非常感谢。
se22as '18

大!完美运作。尝试了很多事情,现在完美继续沉浸式模式。
Peterdk

哇,像魅力一样工作。谢谢
阿卜杜勒

16

如果要使用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);

关于显示ActionBar菜单时如何停止显示NavBar的任何想法?
威廉

我仍然得到NPE,因为getDialog()返回null。您是如何处理的?
安东·史库连科'16

8

结合这里的答案,我得到了一个在所有情况下都有效的抽象类:

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

自androidx以来,setupDialog方法已成为受限制的api,除了忽略它外,我们如何处理呢?
潘明康

5

这也可以克服对话框片段的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);
    }

1
最有用的评论,因为它与AlertDialog.Builder和配合使用非常好.setOnDismissListener()
捣毁了

4

当您创建自己的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;
}

不太理想,因为您将无法在对话框中按任何内容:(
时隙

使用此对话框,我的对话框变得无响应。但是,如果您坚持认为可行,那么我会再给它一次。
老虎机

@slott,您需要清除显示的WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE之后对话框
4emodan

2

我知道这是旧帖子,但我的回答可能会对他人有所帮助。

以下是对话框中沉浸式效果的hacky修复:

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;
    }
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.