ダイアログで没入モードを維持するにはどうすればよいですか?


104

アクティビティがカスタムダイアログを表示するときに新しい没入モードを維持するにはどうすればよいですか?

ダイアログでイマーシブモードを維持するために以下のコードを使用していますが、そのソリューションでは、カスタムダイアログを開始するとNavBarが1秒未満で表示され、その後消えます。

次のビデオでは、より良い問題(ナビゲーションバーが表示され、画面の一番下を見て)説明して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が呼び出されます。ダイアログが表示されたときに有効にされる値は何ですか?それは本当ですか、それとも何か問題がありましたか?
Kevin van Mierlo、2014

Dialogクラスの(およびActivityクラスの)onWindowFocusChanged(boolean hasFocus)メソッドを意味しますか?この場合、「hasFocus」フラグはtrueです。
VanDir 2014

5
だれかがダイアログフラグメントで没入モードを使用しましたか?
gbero 14

回答:


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.

これが私たちがここで見ているものだと思います(新しいフォーカス可能なウィンドウビューがマネージャーに追加されると、ユーザーインタラクションがトリガーされます)。

どうすればこれを回避できますか?Dialogを作成するときにフォーカスしないようにし(ユーザーインタラクションをトリガーしないようにします)、ダイアログが表示された後にフォーカス可能にします。

//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のバグのようです。ウィンドウにイマーシブセットがあるかどうかを確認する必要があります。

私の作業中のテストコードを(ハッカの乱雑さを許して)Githubに更新しました。私はNexus 5エミュレータでテストしましたが、KitKat未満で爆発する可能性がありますが、それは概念実証のみです。


3
サンプルプロジェクトに感謝​​しますが、機能しませんでした。:私のネクサス5で何が起こるか見てyoutu.be/-abj3V_0zcU
VanDir

問題を調査した結果、回答を更新しました。難しい問題でした。Nexus 5エミュレータでテストしました。うまくいけば、これでうまくいきます。
Beaver6813 14

2
'requestFeature()はコンテンツを追加する前に呼び出す必要があります'と表示されます(アクティビティのアクティブな機能に依存すると思います)。解決策:dialog.show()を1行上に移動して、SystemUiVisibilityをコピーする前に(ただし、フォーカス不可能に設定した後で)show()を呼び出します。その後、ナビゲーションバーをジャンプせずに引き続き機能します。
arberg 2014年

5
@ Beaver6813 EditTextを使用し、DialogFragmentにソフトキーボードが表示されている場合は機能しません。それを処理するための提案はありませんか?
androidcodehunter 2014

1
こんにちはBeaver6813。ダイアログにエディットテキストがある場合は機能しません。何か解決策はありますか?
Navin Gupta

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()のビルダーパターンで動作しますか?私はまだこのハックをDialogFragmentsで機能させる必要があります。ダイアログは「コンテンツを追加する前にrequestFeature()を呼び出す必要があります」でアプリをクラッシュさせます
IAmKale

1
これは素晴らしく、私のDialogFragmentsの使用法に対する唯一の解決策でした。どうもありがとうございます。
-se22as

すごい!それは完全に動作します。多くのことを試しましたが、今では完璧な没入型モードが続きます。
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の表示を停止する方法についてのアイデアはありますか?
William

getDialog()がnullを返すため、私はまだNPEを取得しています。それをどのように管理しましたか?
アントン・シュクレンコ2016

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になったので、無視する以外にどのように対処できますか?
Mingkang Pan

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()
tomash

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

これを使用すると、ダイアログが応答しなくなります。しかし、あなたがそれがうまくいくと主張するなら、私はそれをもう一度やります。
スロット、2015年

@slott WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE表示された後ダイアログをクリアする必要がある
4emodan

2

私はこれが古い記事であることを知っていますが、私の答えは他の人を助けるかもしれません。

以下は、ダイアログのイマーシブエフェクトのハック修正です。

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;
    }
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.