Como mantenho o modo imersivo em caixas de diálogo?

104

Como mantenho o novo Modo Imersivo quando minhas atividades exibem uma caixa de diálogo personalizada?

Estou usando o código abaixo para manter o Modo Imersivo em Diálogos, mas com essa solução, a NavBar aparece por menos de um segundo quando eu inicio minha caixa de diálogo personalizada e depois desaparece.

O vídeo a seguir explica melhor o problema (observe a parte inferior da tela quando a NavBar aparecer): http://youtu.be/epnd5ghey8g

Como posso evitar esse comportamento?

CÓDIGO

O pai de todas as atividades em meu aplicativo:

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

}


Todos os meus Diálogos personalizados chamam este método em seus onCreate(. . .)métodos:

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


EDITAR - A SOLUÇÃO (obrigado a Beaver6813, veja a resposta dele para mais detalhes):

Todas as minhas caixas de diálogo personalizadas substituem o método show desta maneira:

/**
 * 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
fonte
Como você mostra os diálogos? Você usa DialogFragments?
Tadeas Kriz,
Eu não uso DialogFragments, mas subclasses de Dialog personalizadas. developer.android.com/reference/android/app/Dialog.html Eu mostro os diálogos simplesmente chamando seu método show ().
VanDir
Quando a caixa de diálogo aparece, onWindowFocusChanged está sendo chamado. Qual é o valor de ativado quando a caixa de diálogo aparece? É verdade ou algo deu errado e é falso?
Kevin van Mierlo de
Você quer dizer o método onWindowFocusChanged (boolean hasFocus) da classe Dialog (e não da classe Activity)? Nesse caso, o sinalizador "hasFocus" é verdadeiro.
VanDir
5
Alguém usou o modo imersivo com fragmentos de diálogo?
gbero 01 de

Respostas:

167

Depois de muita pesquisa sobre o problema, há uma solução hacky para isso, que envolveu destruir a classe Dialog para encontrar. A barra de navegação é mostrada quando a janela de diálogo é adicionada ao Gerenciador de janelas, mesmo se você definir a visibilidade da IU antes de adicioná-la ao gerenciador. No exemplo do Android Immersive , é comentado 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.

Acredito que é isso que estamos vendo aqui (que uma interação do usuário está sendo acionada quando uma nova visualização de janela focável é adicionada ao gerenciador).

Como podemos contornar isso? Torne a caixa de diálogo sem foco ao criá-la (para não acionar uma interação do usuário) e, em seguida, torne-a focalizável depois de exibida.

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

Claramente isso não é o ideal, mas parece ser um bug do Android, eles devem verificar se a janela tem um conjunto imersivo.

Eu atualizei meu código de teste de trabalho (perdoe a bagunça hacky) para Github . Eu testei no emulador Nexus 5, ele provavelmente irá explodir com qualquer coisa menos do que KitKat, mas é apenas para prova de conceito.

Beaver6813
fonte
3
Obrigado pelo projeto de amostra, mas não funcionou. Veja o que acontece no meu Nexus 5: youtu.be/-abj3V_0zcU
VanDir
Atualizei minha resposta após alguma investigação sobre o problema, foi difícil! Testado em um emulador Nexus 5, espero que este funcione.
Beaver6813
2
Recebo 'requestFeature () deve ser chamado antes de adicionar conteúdo' (acho que depende dos recursos ativos na atividade). Solução: Mova o dialog.show () uma linha para cima para que show () seja invocado antes de copiar SystemUiVisibility (mas após definir não focalizável). Então ele ainda funciona sem pular a barra de navegação.
arberg
5
@ Beaver6813 não funciona quando EditText é usado e o teclado virtual é exibido em DialogFragment. Você não tem sugestões para lidar com isso.
androidcodehunter
1
Olá, Beaver6813. Não funciona quando tenho um texto de edição na caixa de diálogo. Existe alguma solução ??
Navin Gupta
33

Para sua informação, graças à resposta de @ Beaver6813, consegui fazer isso funcionar usando DialogFragment.

no método onCreateView do meu DialogFragment, acabei de adicionar o seguinte:

    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
fonte
5
Isso funciona com o padrão Builder em onCreateDialog ()? Ainda estou para fazer este hack funcionar com meu DialogFragments, a caixa de diálogo trava meu aplicativo com "requestFeature () must be called before
add
1
Isso é brilhante e foi a única solução para o meu uso de DialogFragments. Muito obrigado.
se22 de
Ótimo! Funciona perfeitamente. Tentei tantas coisas, agora o modo perfeito continua imersivo.
Peterdk
uau, funcionando como um encanto. obrigado
Abdul
16

Se você quiser usar onCreateDialog () , tente esta classe. Funciona muito bem para mim ...

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

    }

}

Para mostrar a caixa de diálogo, faça o seguinte em sua atividade ...

new ImmersiveDialogFragment().showImmersive(this);
2r4
fonte
Alguma ideia de como impedir que a NavBar seja exibida quando um menu da ActionBar for mostrado?
William
Ainda recebo NPE, porque getDialog () retorna nulo. Como você conseguiu isso?
Anton Shkurenko
8

Combinando as respostas aqui, fiz uma classe abstrata que funciona em todos os casos:

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
fonte
como androidx, o método setupDialog se tornou uma API restrita, como poderíamos lidar com isso, exceto ignorá-lo?
Mingkang Pan
5

Isso também funciona sobre o método onDismiss de seu fragmento de diálogo. E dentro desse método, chame o método da atividade à qual ele está anexado novamente, defina os sinalizadores de tela inteira.

@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 em sua atividade adicione este método:

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
fonte
1
Comentário muito útil, pois funciona muito bem com AlertDialog.Buildere.setOnDismissListener()
tomash 01 de
4

Enquanto você está criando seu próprio DialogFragment, você só precisa substituir este método.

@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
fonte
Não é realmente ideal, pois você não conseguirá pressionar nada dentro da caixa de diálogo :(
slott
Usando isso, minhas caixas de diálogo não respondem. Mas se você insiste que funciona, vou tentar novamente.
Slott
@slott, você precisa limpar a WindowManager.LayoutParams.FLAG_NOT_FOCUSABLEcaixa de diálogo exibida depois
4emodan
2

Eu sei que esta é uma postagem antiga, mas minha resposta pode ajudar outras pessoas.

Abaixo está a correção de hacky para efeito imersivo em Diálogos:

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
fonte