Come si mantiene la modalità immersiva nelle finestre di dialogo?


104

Come mantengo la nuova modalità immersiva quando le mie attività visualizzano una finestra di dialogo personalizzata?

Sto usando il codice seguente per mantenere la modalità immersiva nelle finestre di dialogo, ma con quella soluzione, la NavBar appare per meno di un secondo quando avvio la mia finestra di dialogo personalizzata, quindi scompare.

Il video seguente spiega meglio il problema (guarda la parte inferiore dello schermo quando appare la NavBar): http://youtu.be/epnd5ghey8g

Come evito questo comportamento?

CODICE

Il padre di tutte le attività nella mia applicazione:

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

}


Tutti i miei dialoghi personalizzati chiamano questo metodo nei loro file onCreate(. . .) metodo:

/**
 * 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 - LA SOLUZIONE (grazie a Beaver6813, guarda la sua risposta per maggiori dettagli):

Tutte le mie finestre di dialogo personalizzate sovrascrivono il metodo show in questo modo:

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

Come mostri i dialoghi? Usi DialogFragments?
Tadeas Kriz

Non uso DialogFragments ma sottoclassi Dialog personalizzate. developer.android.com/reference/android/app/Dialog.html Mostro le finestre di dialogo semplicemente chiamando il loro metodo show ().
VanDir

Quando viene visualizzata la finestra di dialogo suWindowFocusChanged viene chiamato. Qual è il valore di abilitato quando viene visualizzata la finestra di dialogo? È vero o qualcosa è andato storto ed è falso?
Kevin van Mierlo

Intendi il metodo onWindowFocusChanged (boolean hasFocus) della classe Dialog (e non della classe Activity)? In questo caso il flag "hasFocus" è vero.
VanDir

5
Qualcuno ha usato la modalità immersiva con i dialogframment?
gbero

Risposte:


167

Dopo molte ricerche sul problema, c'è una soluzione hacky per questo, che ha comportato lo smantellamento della classe Dialog per trovarla. La barra di navigazione viene visualizzata quando la finestra di dialogo viene aggiunta al Window Manager anche se si imposta la visibilità dell'interfaccia utente prima di aggiungerla al manager. Nell'esempio di Android Immersive si commenta che:

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

Credo che questo sia ciò che stiamo vedendo qui (che un'interazione dell'utente viene attivata quando una nuova visualizzazione della finestra attivabile viene aggiunta al gestore).

Come possiamo aggirare questo problema? Rendi la finestra di dialogo non attivabile quando la creiamo (in modo da non attivare un'interazione con l'utente) e quindi rendila attivabile dopo che è stata visualizzata.

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

Chiaramente questo non è l'ideale ma sembra essere un bug di Android, dovrebbero controllare se la finestra ha un set immersivo.

Ho aggiornato il mio codice di prova funzionante (perdona il disordine hacky) a Github . Ho testato l'emulatore Nexus 5, probabilmente esploderà con qualcosa di meno di KitKat ma è solo per prova di concetto.


3
Grazie per il progetto di esempio ma non ha funzionato. Guarda cosa succede nel mio Nexus 5: youtu.be/-abj3V_0zcU
VanDir

Ho aggiornato la mia risposta dopo alcune indagini sul problema, è stata dura! Testato su un emulatore Nexus 5, si spera che questo funzioni.
Beaver6813

2
Ottengo 'requestFeature () deve essere chiamato prima di aggiungere contenuto' (penso che dipenda dalle funzionalità attive sull'attività). Soluzione: spostare il dialog.show () di una riga in alto in modo che show () venga richiamato prima di copiare SystemUiVisibility (ma dopo aver impostato non attivabile). Quindi funziona ancora senza saltare la barra di navigazione.
arberg

5
@ Beaver6813 non funziona quando viene utilizzato EditText e la softkeyboard viene visualizzata in DialogFragment. Hai anzi suggerimenti per gestirlo.
androidcodehunter

1
Ciao Beaver6813. Non funziona quando ho un testo di modifica nella finestra di dialogo. C'è qualche soluzione?
Navin Gupta

33

Per tua informazione, grazie alla risposta di @ Beaver6813, sono riuscito a farlo funzionare utilizzando DialogFragment.

nel metodo onCreateView del mio DialogFragment, ho appena aggiunto quanto segue:

    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
Funziona con il pattern Builder in onCreateDialog ()? Devo ancora far funzionare questo hack con i miei DialogFragments, la finestra di dialogo blocca la mia app con "requestFeature () deve essere chiamata prima di aggiungere contenuto"
IAmKale

1
Questo è brillante ed è stata l'unica soluzione al mio utilizzo di DialogFragments. Grazie mille.
se22as

Grande! Funziona perfettamente. Ho provato tante cose, ora perfetta continua modalità immersiva.
Peterdk

wow, funziona come un fascino. grazie
Abdul

16

Se vuoi usare onCreateDialog () , prova questa classe. Funziona abbastanza bene per me ...

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

    }

}

Per mostrare la finestra di dialogo, procedi come segue nella tua attività ...

new ImmersiveDialogFragment().showImmersive(this);

Qualche idea su come interrompere la visualizzazione della NavBar quando viene visualizzato un menu ActionBar?
William

Ottengo ancora NPE, perché getDialog () restituisce null. Come ci sei riuscito?
Anton Shkurenko

8

Combinando le risposte qui ho creato una classe astratta che funziona in tutti i casi:

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

dal momento che androidx, il metodo setupDialog è diventato un'API con restrizioni, come potremmo affrontarlo se non ignorarlo?
Mingkang Pan,

5

Funziona anche con il metodo onDismiss del frammento di dialogo. E all'interno di quel metodo chiama il metodo dell'attività a cui è collegato, imposta nuovamente i flag a schermo intero.

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

E nella tua attività aggiungi questo metodo:

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
Commento molto utile, poiché funziona benissimo con AlertDialog.Buildere.setOnDismissListener()
tomash

4

Quando stai creando il tuo DialogFragment, devi solo sovrascrivere questo metodo.

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

Non proprio l'ideale in quanto non sarai in grado di premere nulla all'interno della finestra di dialogo :(
slott

Usando questo, i miei dialoghi non rispondono. Ma se insisti che funziona, ci proverò di nuovo.
Slott

@slott devi cancellare la WindowManager.LayoutParams.FLAG_NOT_FOCUSABLEfinestra di dialogo dopo mostrata
4emodan

2

So che questo è un vecchio post, ma la mia risposta potrebbe aiutare gli altri.

Di seguito è riportata la correzione hacky per l'effetto immersivo nelle finestre di dialogo:

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;
    }
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.