Bagaimana cara mempertahankan Mode Immersive dalam Dialog?

104

Bagaimana cara mempertahankan Mode Imersif baru saat aktivitas saya menampilkan Dialog kustom?

Saya menggunakan kode di bawah ini untuk mempertahankan Mode Imersif dalam Dialog, tetapi dengan solusi itu, NavBar muncul kurang dari satu detik ketika saya memulai Dialog kustom saya, lalu menghilang.

Video berikut menjelaskan masalah dengan lebih baik (lihat bagian bawah layar saat NavBar muncul): http://youtu.be/epnd5ghey8g

Bagaimana cara menghindari perilaku ini?

KODE

Bapak dari semua aktivitas di aplikasi saya:

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

}


Semua Dialog kustom saya memanggil metode ini dalam onCreate(. . .)metode mereka :

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


EDIT - SOLUSI (terima kasih kepada Beaver6813, lihat jawabannya untuk lebih jelasnya):

Semua dialog khusus saya menggantikan metode pertunjukan dengan cara ini:

/**
 * 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);
}
VanDir
sumber
Bagaimana Anda menampilkan dialog? Apakah Anda menggunakan DialogFragments?
Tadeas Kriz
Saya tidak menggunakan DialogFragments tetapi subclass Dialog kustom. developer.android.com/reference/android/app/Dialog.html Saya menampilkan dialog hanya dengan memanggil metode show () mereka.
VanDir
Saat dialog muncul, onWindowFocusChanged dipanggil. Berapakah nilai yang diaktifkan saat dialog muncul? Apakah itu benar atau apakah ada yang tidak beres dan apakah itu salah?
Kevin van Mierlo
Apakah yang Anda maksud adalah metode onWindowFocusChanged (boolean hasFocus) dari kelas Dialog (dan bukan dari kelas Aktivitas)? Dalam kasus ini, tanda "hasFocus" adalah benar.
VanDir
5
Apakah ada yang menggunakan mode imersif dengan dialogfragments?
gbero

Jawaban:

167

Setelah banyak penelitian tentang masalah ini, ada perbaikan hacky untuk ini, yang melibatkan merobek kelas Dialog untuk menemukan. Bilah navigasi ditampilkan saat jendela dialog ditambahkan ke Pengelola Jendela meskipun Anda menyetel visibilitas UI sebelum menambahkannya ke pengelola. Dalam contoh Android Immersive dikomentari bahwa:

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

Saya yakin itulah yang kami lihat di sini (bahwa interaksi pengguna dipicu saat tampilan jendela baru yang dapat difokuskan ditambahkan ke pengelola).

Bagaimana kita bisa mengatasi ini? Jadikan Dialog tidak dapat difokuskan saat kita membuatnya (agar kita tidak memicu interaksi pengguna), lalu membuatnya dapat difokuskan setelah ditampilkan.

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

Jelas ini tidak ideal tetapi tampaknya merupakan bug Android, mereka harus memeriksa apakah Jendela memiliki set yang imersif.

Saya telah memperbarui kode uji kerja saya (maafkan kekacauan yang diretas) ke Github . Saya telah menguji emulator Nexus 5, itu mungkin akan meledak dengan sesuatu yang kurang dari KitKat tetapi hanya untuk pembuktian konsep.

Berang-berang 6813
sumber
3
Terima kasih untuk proyek sampelnya tetapi tidak berhasil. Lihat apa yang terjadi di Nexus 5 saya: youtu.be/-abj3V_0zcU
VanDir
Saya telah memperbarui jawaban saya setelah beberapa penyelidikan masalah, itu sulit! Diuji pada emulator Nexus 5, semoga yang ini berfungsi.
Beaver6813
2
Saya mendapatkan 'requestFeature () harus dipanggil sebelum menambahkan konten' (menurut saya ini tergantung pada fitur aktif pada aktivitas). Solusi: Pindahkan dialog.show () satu baris ke atas sehingga show () dipanggil sebelum menyalin SystemUiVisibility (tetapi setelah menyetel non-focusable). Kemudian masih berfungsi tanpa melompat nav-bar.
arberg
5
@ Beaver6813 tidak berfungsi saat EditText digunakan dan softkeyboard muncul di DialogFragment. Apakah Anda tidak punya saran untuk menanganinya.
androidcodehunter
1
Hai Beaver6813. Ini tidak berfungsi ketika saya memiliki teks edit dalam dialog. Apakah ada solusi ??
Navin Gupta
33

Untuk informasi Anda, terima kasih atas jawaban @ Beaver6813, saya bisa membuatnya bekerja menggunakan DialogFragment.

dalam metode onCreateView dari DialogFragment saya, saya baru saja menambahkan yang berikut ini:

    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());
        }
    });
gbero
sumber
5
Apakah ini berfungsi dengan pola Builder di onCreateDialog ()? Saya belum mendapatkan peretasan ini untuk bekerja dengan DialogFragments saya, dialog merusak aplikasi saya dengan "requestFeature () harus dipanggil sebelum menambahkan konten"
IAmKale
1
Ini brilian dan merupakan satu-satunya solusi untuk penggunaan DialogFragments saya. Terima kasih banyak.
se22as
Bagus! Ini bekerja dengan sempurna. Mencoba banyak hal, sekarang mode sempurna terus imersif.
Peterdk
wow, bekerja dengan sangat baik. terima kasih
Abdul
16

Jika Anda ingin menggunakan onCreateDialog () , coba kelas ini. Ini bekerja dengan cukup baik untuk saya ...

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

    }

}

Untuk menampilkan dialog, lakukan hal berikut dalam aktivitas Anda ...

new ImmersiveDialogFragment().showImmersive(this);
2r4
sumber
Ada ide tentang cara menghentikan tampilan NavBar saat menu ActionBar ditampilkan?
William
Saya masih mendapatkan NPE, karena getDialog () mengembalikan null. Bagaimana Anda mengaturnya?
Anton Shkurenko
8

Menggabungkan jawaban di sini saya membuat kelas abstrak yang berfungsi di semua kasus:

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
        );
    }
}
4emodan
sumber
karena androidx, metode setupDialog menjadi api yang dibatasi, bagaimana kita bisa mengatasinya kecuali mengabaikannya?
Mingkang Pan
5

Ini juga bekerja pada metode onDismiss dari fragmen dialog Anda. Dan di dalam metode itu panggil metode aktivitas tempat ia dilampirkan lagi setel tanda layar penuh.

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

Dan dalam aktivitas Anda tambahkan metode ini:

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);
    }
Salil Kaul
sumber
1
Komentar yang paling berguna, karena berfungsi baik dengan AlertDialog.Builderdan.setOnDismissListener()
tomash
4

Saat Anda membuat DialogFragment Anda sendiri, Anda hanya perlu mengganti metode ini.

@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;
}
Dawid Drozd
sumber
Tidak terlalu ideal karena Anda tidak dapat menekan apa pun di dalam dialog :(
slott
Dengan menggunakan ini, dialog saya menjadi tidak responsif. Tetapi jika Anda bersikeras bahwa itu berhasil, saya akan mencobanya lagi.
slott
@ Slott Anda perlu menghapus WindowManager.LayoutParams.FLAG_NOT_FOCUSABLEdialog setelah yang ditampilkan
4emodan
2

Saya tahu ini posting lama, tetapi jawaban saya mungkin membantu orang lain.

Di bawah ini adalah perbaikan hacky untuk efek Immersive dalam 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;
    }
Rahul Parihar
sumber