Як підтримувати режим занурення в діалогах?


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


EDIT - РІШЕННЯ (завдяки Beaver6813, подивіться його відповідь для отримання більш детальної інформації):

Усі мої користувальницькі діалоги переглядають метод показу таким чином:

/**
 * 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?
Тадеас Криз

Я не використовую DialogFragments, а спеціальні підкласи Dialog. developer.android.com/reference/android/app/Dialog.html Я показую діалоги просто за допомогою виклику методу show ().
VanDir

Коли з'явиться діалогове вікно, викликWindowFocusChanged. Яке значення увімкнено, коли з'явиться діалогове вікно? Це правда чи щось пішло не так і чи це помилково?
Кевін ван Мерло

Ви маєте на увазі метод onWindowFocusChanged (boolean hasFocus) класу Dialog (а не класу Activity)? У цьому випадку прапор "hasFocus" є істинним.
VanDir

5
Хтось використовував іммерсивний режим із діалоговими фрагментами?
gbero

Відповіді:


167

Після численних досліджень цього питання, для цього є вигадливе виправлення, яке передбачає розрив класу Dialog, щоб знайти. Панель навігації відображається, коли діалогове вікно додається до Менеджера вікон, навіть якщо ви встановите видимість інтерфейсу користувача перед тим, як додати його до менеджера. У прикладі Android Immersive зауважено, що:

// * 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, вони повинні перевірити, чи є у вікна занурений набір.

Я оновив свій робочий тестовий код (пробачте хакі безладдя) до Github . Я протестував на емуляторі Nexus 5, він, ймовірно, підірватиме що-небудь менше, ніж KitKat, але його лише для підтвердження концепції.


3
Дякуємо за зразок проекту, але він не спрацював. Подивіться, що відбувається в моєму Nexus 5: youtu.be/-abj3V_0zcU
VanDir

Я оновив свою відповідь після деякого розслідування питання, це було важко! Протестовано на емуляторі Nexus 5, сподіваємось, це працює.
Beaver6813

2
Я отримую "requestFeature () потрібно викликати перед додаванням вмісту" (я думаю, це залежить від активних функцій щодо активності). Рішення: Перемістіть на dialog.show () один рядок вгору, щоб показати () викликається перед копіюванням SystemUiVisibility (але після встановлення нефокусованого). Тоді воно все ще працює, не стрибаючи нав-бар.
Арберг

5
@ Beaver6813 не працює, коли використовується EditText, а в діалоговому фрагменті з’являється програмна клавіша. Чи є у вас найактивніші пропозиції впоратися з цим.
androidcodehunter

1
Привіт Бівер6813. Це не працює, коли у мене є текст редагування у діалоговому вікні. Чи є рішення ??
Навін Гупта

33

Для вашої інформації, завдяки відповіді @ Beaver6813, мені вдалося налагодити цю роботу за допомогою DialogFragment.

у методі onCreateView мого DialogFragment я тільки що додав:

    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
Чи працює це з шаблоном Builder в 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);

Будь-яка ідея про те, як зупинити показ NavBar, коли відображається меню ActionBar?
Вільям

Я все ще отримую NPE, тому що getDialog () повертає null. Як вам це вдалося?
Антон Шкуренко

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 з обмеженим доступом, як ми могли впоратися з цим, окрім iгнорування?
Мінгканг Пан

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 потрібно очистити 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;
    }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.