Wie pflege ich den Immersive-Modus in Dialogen?

104

Wie pflege ich den neuen Immersive-Modus, wenn meine Aktivitäten einen benutzerdefinierten Dialog anzeigen?

Ich verwende den folgenden Code, um den Immersive-Modus in Dialogen beizubehalten. Bei dieser Lösung wird die Navigationsleiste jedoch weniger als eine Sekunde lang angezeigt, wenn ich meinen benutzerdefinierten Dialog starte. Dann verschwindet sie.

Das folgende Video erklärt das Problem besser (sehen Sie am unteren Bildschirmrand nach, wenn die NavBar angezeigt wird): http://youtu.be/epnd5ghey8g

Wie vermeide ich dieses Verhalten?

CODE

Der Vater aller Aktivitäten in meiner Bewerbung:

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

}


Alle meine benutzerdefinierten Dialoge rufen diese Methode in ihrer onCreate(. . .)Methode auf:

/**
 * 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 - THE SOLUTION (dank Beaver6813 finden Sie in seiner Antwort weitere Details):

Alle meine benutzerdefinierten Dialoge überschreiben die show-Methode folgendermaßen:

/**
 * 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
quelle
Wie zeigen Sie Dialoge? Verwenden Sie DialogFragments?
Tadeas Kriz
Ich verwende keine DialogFragments, sondern benutzerdefinierte Dialog-Unterklassen. developer.android.com/reference/android/app/Dialog.html Ich zeige die Dialoge einfach durch Aufrufen ihrer show () -Methode.
VanDir
Wenn das Dialogfeld auf WindowsFocusChanged angezeigt wird, wird es aufgerufen. Was ist der Wert von enabled, wenn der Dialog angezeigt wird? Ist es wahr oder ist etwas schief gelaufen und ist es falsch?
Kevin van Mierlo
Meinen Sie die onWindowFocusChanged-Methode (boolean hasFocus) der Dialog-Klasse (und nicht der Activity-Klasse)? In diesem Fall ist das Flag "hasFocus" wahr.
VanDir
5
Hat jemand den immersiven Modus mit Dialogfragments verwendet?
Gbero

Antworten:

167

Nach viel Forschung in der Frage gibt es eine hacky Lösung für dieses, die neben den beteiligten Zerreißen Dialog Klasse zu finden. Die Navigationsleiste wird angezeigt, wenn das Dialogfenster zum Fenstermanager hinzugefügt wird, auch wenn Sie die Sichtbarkeit der Benutzeroberfläche festlegen, bevor Sie sie dem Manager hinzufügen. Im Android Immersive-Beispiel heißt es:

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

Ich glaube, das ist es, was wir hier sehen (dass eine Benutzerinteraktion ausgelöst wird, wenn dem Manager eine neue, fokussierbare Fensteransicht hinzugefügt wird).

Wie können wir das umgehen? Machen Sie den Dialog beim Erstellen nicht fokussierbar (damit wir keine Benutzerinteraktion auslösen) und machen Sie ihn nach der Anzeige fokussierbar.

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

Natürlich ist dies nicht ideal, aber es scheint ein Android-Fehler zu sein. Sie sollten prüfen, ob das Fenster ein immersives Set hat.

Ich habe meinen Arbeitstestcode (verzeihen Sie die hackige Unordnung) auf Github aktualisiert . Ich habe auf dem Nexus 5-Emulator getestet, er wird wahrscheinlich mit weniger als KitKat in die Luft jagen, aber nur als Proof-of-Concept.

Beaver6813
quelle
3
Danke für das Beispielprojekt, aber es hat nicht funktioniert. Schauen Sie, was in meinem Nexus 5 passiert: youtu.be/-abj3V_0zcU
VanDir
Ich habe meine Antwort nach einigen Nachforschungen aktualisiert, es war eine schwierige Frage! Getestet auf einem Nexus 5-Emulator, hoffentlich funktioniert dieser.
Beaver6813
2
Ich erhalte die Meldung 'requestFeature () muss vor dem Hinzufügen von Inhalten aufgerufen werden' (ich denke, dies hängt von den aktiven Funktionen der Aktivität ab). Lösung: Verschieben Sie die Datei dialog.show () um eine Zeile nach oben, sodass show () vor dem Kopieren von SystemUiVisibility (jedoch nach dem Festlegen der nicht fokussierbaren Einstellung) aufgerufen wird. Dann funktioniert es immer noch ohne springende Navigationsleiste.
Arberg
5
@ Beaver6813 funktioniert nicht, wenn EditText verwendet wird und Softkeyboard in DialogFragment angezeigt wird. Haben Sie keine Vorschläge, um damit umzugehen?
Android Codehunter
1
Hallo Beaver6813. Es funktioniert nicht, wenn ich einen Bearbeitungstext im Dialog habe. Gibt es eine Lösung?
Navin Gupta
33

Zu Ihrer Information, dank der Antwort von @ Beaver6813 konnte ich dies mit DialogFragment zum Laufen bringen.

In der onCreateView-Methode meines DialogFragments habe ich gerade Folgendes hinzugefügt:

    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
quelle
5
Funktioniert dies mit dem Builder-Muster in onCreateDialog ()? Ich habe diesen Hack noch nicht mit meinen DialogFragments zum Laufen gebracht. Der Dialog stürzt meine App mit "requestFeature () muss vor dem Hinzufügen von Inhalten aufgerufen werden" ab
IAmKale
1
Dies ist brillant und war die einzige Lösung für meine Verwendung von DialogFragments. Ich danke dir sehr.
se22as
Toll! Es funktioniert perfekt. Versucht so viele Dinge, jetzt perfekt weiterhin immersiven Modus.
Peterdk
Wow, wie ein Zauber arbeiten. danke
Abdul
16

Wenn Sie onCreateDialog () verwenden möchten , versuchen Sie diese Klasse. Es funktioniert ziemlich gut für mich ...

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

    }

}

Gehen Sie in Ihrer Aktivität wie folgt vor, um den Dialog anzuzeigen ...

new ImmersiveDialogFragment().showImmersive(this);
2r4
quelle
Haben Sie eine Idee, wie Sie verhindern können, dass die NavBar angezeigt wird, wenn ein ActionBar-Menü angezeigt wird?
William
Ich bekomme immer noch NPE, weil getDialog () null zurückgibt. Wie hast du das geschafft?
Anton Shkurenko
8

Ich habe die Antworten hier kombiniert und eine abstrakte Klasse erstellt, die in allen Fällen funktioniert:

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
quelle
seit androidx wurde die setupDialog- Methode zu einer eingeschränkten API. Wie können wir damit umgehen, außer sie zu ignorieren?
Mingkang Pan
5

Dies funktioniert auch über die onDismiss-Methode Ihres Dialogfragments hinaus. Rufen Sie innerhalb dieser Methode die Methode der Aktivität auf, an die sie angehängt ist, um die Vollbild-Flags erneut zu setzen.

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

Und fügen Sie in Ihrer Aktivität diese Methode hinzu:

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
quelle
1
Der nützlichste Kommentar, da er mit AlertDialog.Builderand.setOnDismissListener()
tomash
4

Wenn Sie Ihr eigenes DialogFragment erstellen, müssen Sie diese Methode nur überschreiben.

@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
quelle
Nicht wirklich ideal, da Sie im Dialog nichts drücken können :(
Slott
Dadurch reagieren meine Dialoge nicht mehr. Aber wenn Sie darauf bestehen, dass es funktioniert, werde ich es noch einmal versuchen.
Slott
@ Slott müssen Sie den WindowManager.LayoutParams.FLAG_NOT_FOCUSABLEangezeigten Nach-Dialog
löschen
2

Ich weiß, dass dies ein alter Beitrag ist, aber meine Antwort kann anderen helfen.

Unten finden Sie die Hacky-Korrektur für den Immersive-Effekt in Dialogen:

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
quelle