Comment conserver le mode immersif dans les boîtes de dialogue?

104

Comment conserver le nouveau mode immersif lorsque mes activités affichent une boîte de dialogue personnalisée?

J'utilise le code ci-dessous pour maintenir le mode immersif dans les dialogues, mais avec cette solution, la NavBar apparaît pendant moins d'une seconde lorsque je démarre ma boîte de dialogue personnalisée, puis elle disparaît.

La vidéo suivante explique mieux le problème (regardez en bas de l'écran lorsque la NavBar apparaît): http://youtu.be/epnd5ghey8g

Comment éviter ce comportement?

CODE

Le père de toutes les activités de ma candidature:

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

}


Tous mes dialogues personnalisés appellent cette méthode dans leur onCreate(. . .)méthode:

/**
 * 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 SOLUTION (merci à Beaver6813, regardez sa réponse pour plus de détails):

Toutes mes boîtes de dialogue personnalisées remplacent la méthode show de cette manière:

/**
 * 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
la source
Comment affichez-vous les dialogues? Utilisez-vous DialogFragments?
Tadeas Kriz
Je n'utilise pas DialogFragments mais des sous-classes de dialogue personnalisées. developer.android.com/reference/android/app/Dialog.html Je montre les boîtes de dialogue simplement en appelant leur méthode show ().
VanDir
Lorsque la boîte de dialogue apparaît onWindowFocusChanged est appelée. Quelle est la valeur de enabled lorsque la boîte de dialogue apparaît? Est-ce vrai ou est-ce que quelque chose s'est mal passé et est-ce faux?
Kevin van Mierlo
Voulez-vous dire la méthode onWindowFocusChanged (boolean hasFocus) de la classe Dialog (et non de la classe Activity)? Dans ce cas, l'indicateur "hasFocus" est vrai.
VanDir
5
Quelqu'un a-t-il utilisé le mode immersif avec des fragments de dialogue?
gbero

Réponses:

167

Après de nombreuses recherches sur le problème, il existe une solution de piratage à ce problème, qui impliquait de déchirer la classe Dialog pour la trouver. La barre de navigation s'affiche lorsque la boîte de dialogue est ajoutée au gestionnaire de fenêtres, même si vous définissez la visibilité de l'interface utilisateur avant de l'ajouter au gestionnaire. Dans l' exemple Android Immersive, il est commenté que:

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

Je crois que c'est ce que nous voyons ici (qu'une interaction utilisateur est déclenchée lorsqu'une nouvelle vue de fenêtre focalisable est ajoutée au gestionnaire).

Comment pouvons-nous contourner cela? Rendre la boîte de dialogue non focalisable lorsque nous la créons (afin de ne pas déclencher d'interaction utilisateur), puis la rendre focusable après son affichage.

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

Ce n'est clairement pas l'idéal mais cela semble être un bogue Android, ils devraient vérifier si la fenêtre a un ensemble immersif.

J'ai mis à jour mon code de test de travail (pardonnez le désordre hacky) sur Github . J'ai testé sur l'émulateur Nexus 5, il explosera probablement avec rien de moins que KitKat, mais c'est uniquement pour une preuve de concept.

Castor6813
la source
3
Merci pour l'exemple de projet mais cela n'a pas fonctionné. Regardez ce qui se passe dans mon Nexus 5: youtu.be/-abj3V_0zcU
VanDir
J'ai mis à jour ma réponse après une enquête sur le problème, c'était difficile! Testé sur un émulateur Nexus 5, j'espère que celui-ci fonctionne.
Beaver6813
2
J'obtiens 'requestFeature () doit être appelé avant d'ajouter du contenu' (je pense que cela dépend des fonctionnalités actives sur l'activité). Solution: déplacez le dialog.show () d'une ligne vers le haut pour que show () soit appelé avant de copier SystemUiVisibility (mais après avoir défini non focusable). Ensuite, cela fonctionne toujours sans sauter la barre de navigation.
arberg le
5
@ Beaver6813 cela ne fonctionne pas lorsque EditText est utilisé et que le clavier logiciel apparaît dans DialogFragment. Avez-vous des suggestions pour le gérer.
androidcodehunter
1
Salut Beaver6813. Cela ne fonctionne pas lorsque j'ai un texte d'édition dans la boîte de dialogue. Y-a-t'il une solution??
Navin Gupta
33

Pour votre information, grâce à la réponse de @ Beaver6813, j'ai pu faire fonctionner cela en utilisant DialogFragment.

dans la méthode onCreateView de mon DialogFragment, je viens d'ajouter ce qui suit:

    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
la source
5
Cela fonctionne-t-il avec le modèle Builder dans onCreateDialog ()? Je n'ai pas encore réussi à faire fonctionner ce hack avec mes DialogFragments, la boîte de dialogue plante mon application avec "requestFeature () doit être appelée avant d'ajouter du contenu"
IAmKale
1
C'est génial et c'était la seule solution à mon utilisation de DialogFragments. Merci beaucoup.
se22 le
Génial! Cela fonctionne parfaitement. J'ai essayé tellement de choses, maintenant parfait continue le mode immersif.
Peterdk le
wow, fonctionnant comme un charme. merci
Abdul
16

Si vous souhaitez utiliser onCreateDialog () , essayez cette classe. Cela marche plutôt bien pour moi...

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

    }

}

Pour afficher la boîte de dialogue, procédez comme suit dans votre activité ...

new ImmersiveDialogFragment().showImmersive(this);
2r4
la source
Une idée sur la façon d'arrêter l'affichage de la NavBar lorsqu'un menu ActionBar est affiché?
William
Je reçois toujours NPE, car getDialog () renvoie null. Comment avez-vous géré cela?
Anton Shkurenko
8

En combinant les réponses ici, j'ai créé une classe abstraite qui fonctionne dans tous les cas:

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
        );
    }
}
4émodan
la source
depuis androidx, la méthode setupDialog est devenue une API restreinte, comment pourrions-nous gérer cela si ce n'est l'ignorer?
Mingkang Pan le
5

Cela fonctionne également sur la méthode onDismiss de votre fragment de dialogue. Et dans cette méthode, appelez la méthode de l'activité à laquelle elle est attachée pour définir à nouveau les indicateurs de plein écran.

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

Et dans votre activité ajoutez cette méthode:

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
la source
1
Commentaire le plus utile, car il fonctionne très bien avec AlertDialog.Builderet.setOnDismissListener()
tomash
4

Lorsque vous créez votre propre DialogFragment, il vous suffit de remplacer cette méthode.

@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
la source
Pas vraiment idéal car vous ne pourrez pas appuyer sur quoi que ce soit dans la boîte de dialogue: (
slott
En utilisant cela, mes dialogues ne répondent plus. Mais si vous insistez pour que cela fonctionne, je vais essayer de nouveau.
slott
@slott vous devez effacer la WindowManager.LayoutParams.FLAG_NOT_FOCUSABLEboîte de dialogue après affichée
4emodan
2

Je sais que c'est un ancien message, mais ma réponse peut aider les autres.

Voici le correctif hacky pour l'effet immersif dans les dialogues:

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
la source