Jak zachować tryb immersyjny w oknach dialogowych?

104

Jak zachować nowy tryb immersyjny, gdy moje działania wyświetlają niestandardowe okno dialogowe?

Używam poniższego kodu, aby utrzymać tryb immersyjny w oknach dialogowych, ale dzięki temu rozwiązaniu pasek nawigacyjny pojawia się na mniej niż sekundę po uruchomieniu mojego niestandardowego okna dialogowego, a następnie znika.

Poniższy film wyjaśnia problem lepiej (spójrz na dół ekranu, gdy pojawi się pasek NavBar): http://youtu.be/epnd5ghey8g

Jak uniknąć tego zachowania?

KOD

Ojciec wszystkich działań w mojej aplikacji:

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

}


Wszystkie moje niestandardowe okna dialogowe wywołują tę metodę w swojej onCreate(. . .)metodzie:

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


EDYCJA - ROZWIĄZANIE (podziękowania dla Beaver6813, spójrz na jego odpowiedź po więcej szczegółów):

Wszystkie moje niestandardowe okna dialogowe zastępują metodę show w ten sposób:

/**
 * 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
źródło
Jak pokazujesz dialogi? Czy używasz DialogFragments?
Tadeas Kriz,
Nie używam DialogFragments, ale niestandardowych podklas Dialog. developer.android.com/reference/android/app/Dialog.html Wyświetlam okna dialogowe po prostu wywołując ich metodę show ().
VanDir,
Gdy pojawi się okno dialogowe, jest wywoływana usługaWindowFocusChanged. Jaka jest wartość włączenia, gdy pojawi się okno dialogowe? Czy to prawda, czy coś poszło nie tak i czy to fałsz?
Kevin van Mierlo,
Czy masz na myśli metodę onWindowFocusChanged (boolean hasFocus) klasy Dialog (a nie klasy Activity)? W tym przypadku flaga „hasFocus” ma wartość true.
VanDir,
5
Czy ktoś używał trybu immersyjnego z fragmentami dialogowymi?
gbero

Odpowiedzi:

167

Po wielu badaniach tego problemu można było znaleźć hackerską poprawkę, która polegała na rozerwaniu klasy Dialog w celu znalezienia. Pasek nawigacji jest wyświetlany po dodaniu okna dialogowego do Menedżera okien, nawet jeśli ustawiono widoczność interfejsu użytkownika przed dodaniem go do menedżera. W przykładzie immersyjnym dla systemu Android stwierdzono, że:

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

Uważam, że właśnie to widzimy tutaj (że interakcja użytkownika jest wyzwalana, gdy do menedżera zostanie dodany nowy, możliwy do zaznaczenia widok okna).

Jak możemy to obejść? Spraw, aby okno dialogowe nie było fokusem, kiedy go tworzymy (abyśmy nie wyzwalali interakcji użytkownika), a następnie ustaw je jako aktywne po wyświetleniu.

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

Oczywiście nie jest to idealne rozwiązanie, ale wydaje się, że jest to błąd Androida, powinni sprawdzić, czy okno ma zestaw immersyjny.

Zaktualizowałem mój działający kod testowy (wybacz hacky bałagan) do Github . Przetestowałem na emulatorze Nexusa 5, prawdopodobnie wybuchnie z czymś mniejszym niż KitKat, ale służy tylko do weryfikacji koncepcji.

Beaver6813
źródło
3
Dzięki za przykładowy projekt, ale nie zadziałał. Spójrz, co się dzieje w moim Nexusie 5: youtu.be/-abj3V_0zcU
VanDir
Zaktualizowałem swoją odpowiedź po pewnym dochodzeniu w tej sprawie, to było trudne! Testowany na emulatorze Nexusa 5, mam nadzieję, że ten działa.
Beaver6813
2
Dostaję „requestFeature () musi zostać wywołana przed dodaniem treści” (myślę, że zależy to od aktywnych funkcji w działaniu). Rozwiązanie: Przenieś okno dialogowe dialog.show () o jeden wiersz w górę, aby wywołać metodę show () przed skopiowaniem SystemUiVisibility (ale po ustawieniu braku fokusu). Wtedy nadal działa bez przeskakiwania paska nawigacyjnego.
arberg
5
@ Beaver6813 nie działa, gdy używany jest EditText, a klawiatura programowa pojawia się w DialogFragment. Czy masz jakieś sugestie, jak sobie z tym poradzić.
androidcodehunter
1
Cześć Beaver6813. Nie działa, gdy mam tekst edycji w oknie dialogowym. Czy jest jakieś rozwiązanie?
Navin Gupta,
33

Dla twojej informacji, dzięki odpowiedzi @ Beaver6813, udało mi się to uruchomić za pomocą DialogFragment.

w metodzie onCreateView mojego DialogFragment, właśnie dodałem:

    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
źródło
5
Czy to działa z wzorcem Builder w onCreateDialog ()? Nie udało mi się jeszcze sprawić, aby ten hack działał z moimi DialogFragments, okno dialogowe powoduje awarię mojej aplikacji z „requestFeature () musi zostać wywołane przed dodaniem zawartości”
IAmKale
1
To jest genialne i było to jedyne rozwiązanie dla mojego użycia DialogFragments. Dziękuję bardzo.
se22 od
Wspaniały! Działa doskonale. Próbowałem tak wielu rzeczy, teraz doskonały kontynuuje tryb immersyjny.
Peterdk
wow, działa jak urok. dzięki
Abdul
16

Jeśli chcesz użyć onCreateDialog () , wypróbuj tę klasę. U mnie działa całkiem nieźle ...

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

    }

}

Aby wyświetlić okno dialogowe, wykonaj następujące czynności w swoim ćwiczeniu ...

new ImmersiveDialogFragment().showImmersive(this);
2r4
źródło
Masz jakiś pomysł, jak zatrzymać wyświetlanie NavBar, gdy wyświetlane jest menu ActionBar?
William
Nadal otrzymuję NPE, ponieważ getDialog () zwraca null. Jak sobie z tym poradziłeś?
Anton Shkurenko
8

Łącząc odpowiedzi tutaj, stworzyłem abstrakcyjną klasę, która działa we wszystkich przypadkach:

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
źródło
od czasu, gdy metoda setupDialog w systemie Androidx stała się ograniczonym interfejsem API, jak możemy sobie z tym poradzić, jeśli nie ignorujemy go?
Mingkang Pan
5

Działa to również w przypadku metody jazdy onDismiss fragmentu okna dialogowego. W ramach tej metody wywołaj metodę działania, do którego jest dołączona, ponownie ustaw flagi pełnego ekranu.

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

A w swojej aktywności dodaj tę metodę:

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
źródło
1
Najbardziej przydatny komentarz, ponieważ działa świetnie z AlertDialog.Builderi.setOnDismissListener()
tomasz
4

Gdy tworzysz swój własny DialogFragment, musisz tylko zastąpić tę metodę.

@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
źródło
Niezbyt idealne, ponieważ nie będziesz w stanie wcisnąć niczego w oknie dialogowym :(
slott
Używając tego, moje okna dialogowe przestają odpowiadać. Ale jeśli upierasz się, że to działa, spróbuję jeszcze raz.
szczelina
@slott, musisz wyczyścić wyświetlone WindowManager.LayoutParams.FLAG_NOT_FOCUSABLEokno dialogowe po
4emodan
2

Wiem, że to stary post, ale moja odpowiedź może pomóc innym.

Poniżej znajduje się hacky poprawka dla efektu immersyjnego w dialogach:

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
źródło