Como impedir que a barra de status e a barra de navegação sejam animadas durante uma transição de animação da cena de atividade?

127

Em primeiro lugar, o fundo da minha barra de status está definido como marrom escuro e o fundo da minha barra de navegação é preto padrão. Estou usando o tema da luz Material.

Estou iniciando uma nova atividade usando ActivityOptions.makeSceneTransitionAnimationcom transições padrão e noto que as barras de status e de navegação desaparecem brevemente para branco e depois voltam para as cores corretas.

De acordo com a documentação :

Para obter o efeito completo de uma transição, você deve habilitar as transições de conteúdo da janela nas atividades de chamada e chamada. Caso contrário, a atividade de chamada iniciará a transição de saída, mas você verá uma transição de janela (como dimensionar ou desaparecer)

Estou usando getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS);as atividades de chamada e chamada.

Da mesma forma, se eu alterar a transição de entrada para um slide, as barras de status e de navegação terão uma transição de slide brevemente com fundo branco.

Como impedir que a barra de status e a barra de navegação sejam animadas durante uma transição de animação da cena de atividade?

rlay3
fonte

Respostas:

217

Existem duas abordagens que você pode usar para impedir que a barra de navegação / status seja animada durante a transição:

Abordagem 1: excluir a barra de status e a barra de navegação da transição padrão de saída / desvanecimento da janela

A razão pela qual a barra de status / navegação está diminuindo e diminuindo durante a transição é porque, por padrão, todas as visualizações não compartilhadas (incluindo os fundos da barra de status / navegação) diminuem / diminuem em suas atividades de chamada / chamada, respectivamente, assim que a transição começar . No entanto, você pode contornar isso facilmente excluindo os planos de fundo da barra de navegação / status da Fadetransição padrão de saída / entrada da janela . Basta adicionar o seguinte código aos onCreate()métodos das suas atividades :

Transition fade = new Fade();
fade.excludeTarget(android.R.id.statusBarBackground, true);
fade.excludeTarget(android.R.id.navigationBarBackground, true);
getWindow().setExitTransition(fade);
getWindow().setEnterTransition(fade);

Essa transição também pode ser declarada no tema da atividade usando XML (ou seja, em seu próprio res/transition/window_fade.xmlarquivo):

<?xml version="1.0" encoding="utf-8"?>
<fade xmlns:android="http://schemas.android.com/apk/res/android">
    <targets>
        <target android:excludeId="@android:id/statusBarBackground"/>
        <target android:excludeId="@android:id/navigationBarBackground"/>
    </targets>
</fade>

Abordagem nº 2: adicione a barra de status e a barra de navegação como elementos compartilhados

Essa abordagem baseia-se na resposta do klmprt, que quase funcionou para mim ... embora eu ainda precisasse fazer algumas modificações.

Na minha atividade chamada, usei o seguinte código para iniciar a atividade:

View statusBar = findViewById(android.R.id.statusBarBackground);
View navigationBar = findViewById(android.R.id.navigationBarBackground);

List<Pair<View, String>> pairs = new ArrayList<>();
if (statusBar != null) {
  pairs.add(Pair.create(statusBar, Window.STATUS_BAR_BACKGROUND_TRANSITION_NAME));
}
if (navigationBar != null) {
  pairs.add(Pair.create(navigationBar, Window.NAVIGATION_BAR_BACKGROUND_TRANSITION_NAME));
}
pairs.add(Pair.create(mSharedElement, mSharedElement.getTransitionName()));

Bundle options = ActivityOptions.makeSceneTransitionAnimation(activity, 
        pairs.toArray(new Pair[pairs.size()])).toBundle();
startActivity(new Intent(context, NextActivity.class), options);

Até agora, isso é essencialmente a mesma coisa que klmprt sugeriu em sua resposta. No entanto, eu também precisei adicionar o código a seguir no onCreate()método chamado Activity para impedir que a barra de status e a barra de navegação "piscassem" durante a transição:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_next);

    // Postpone the transition until the window's decor view has
    // finished its layout.
    postponeEnterTransition();

    final View decor = getWindow().getDecorView();
    decor.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
        @Override
        public boolean onPreDraw() {
            decor.getViewTreeObserver().removeOnPreDrawListener(this);
            startPostponedEnterTransition();
            return true;
        }
    });
}

A adição dos planos de fundo da barra de status e da barra de navegação como elementos compartilhados os forçará a serem desenhados em cima da transição padrão de saída / desvanecimento da janela, o que significa que eles não desaparecerão durante a transição. Mais discussões sobre essa abordagem podem ser encontradas nesta postagem do Google+ .

Alex Lockwood
fonte
14
Alguma idéia de por que findViewById (android.R.id.navigationBarBackground) está retornando nulo no Lollipop? Estou usando appcompat-v7
radzio
3
A abordagem nº 2 funciona, mas ainda há um tremor (na atividade) quando a transição ocorre. a barra de status e a barra de navegação não ficam mais no Flickr.
Akshat
16
@alex Isso funcionou quase perfeitamente para mim até que nós mudamos para a nova biblioteca de apoio com android.support.design.widget.TabLayout e android.support.design.widget.AppBarLayout - agora a cintilação está de volta
Noa Drach
6
android.R.id.navigationBarBackground pode fornecer um NPE em dispositivos Samsung ou HTC, pois eles não têm uma barra de navegação na tela. Faça uma verificação nulo antes de adicioná-los como elementos compartilhados
Boy
8
Estou tentando esta solução no nexus 6 com nougat android. Mas ambas as abordagens não estão funcionando para mim.
Pardeep Kr
4

Impedir completamente que as transições de atividades interfiram nas transições de elementos compartilhados:

Na atividade de saída, chame getWindow (). SetExitTransition (null);

Na atividade de entrada, chame getWindow (). SetEnterTransition (null);

De https://stackoverflow.com/a/34907685/967131

Eu suspeito que isso possa ter efeitos colaterais, mas não tenho certeza. É absolutamente simples e funciona embora.

Evite que elementos específicos pisquem:

Comecei com a resposta de Alex Lockwood e fiz várias experiências para tentar fazê-lo funcionar. O núcleo está correto, embora eu não precise do código que ele sugere para a Atividade receptora, mas tive alguns problemas chamando-o em um Fragmento (em vez de uma Atividade) e definindo uma barra de ferramentas como barra de ação.

Oh, a coisa do fragmento? Vi muitos comentários de que tentar recuperar referências à barra de status e à barra de navegação era nulo. O mesmo aconteceu comigo também, até que percebi que não encontraria aqueles no layout do Fragmento ... eles estavam acima desse nível. Portanto, o código abaixo para obter a visualização de decoração da Atividade e pesquisar isso. Então eu os encontrei sem nenhum problema.

No final, desenvolvi este método utilitário:

public static Bundle transitionOptions(Activity activity, int transitionViewResId, int transitionNameResId) {
   if (VERSION.SDK_INT < VERSION_CODES.LOLLIPOP) {
       return null;
   }

   View decorView = activity.getWindow().getDecorView();
   View statusBar = decorView.findViewById(android.R.id.statusBarBackground);
   View navigationBar = decorView.findViewById(android.R.id.navigationBarBackground);
   View appBarLayout = decorView.findViewById(**R.id.appbarlayout**);
   View transitionView = decorView.findViewById(transitionViewResId);
   String transitionName = activity.getString(transitionNameResId);

   List<Pair<View, String>> pairs = new ArrayList<>();
   pairs.add(Pair.create(statusBar, Window.STATUS_BAR_BACKGROUND_TRANSITION_NAME));
   pairs.add(Pair.create(navigationBar, Window.NAVIGATION_BAR_BACKGROUND_TRANSITION_NAME));
   if (appBarLayout != null) {
       pairs.add(Pair.create(appBarLayout, activity.getString(**R.string.transition_appbarlayout**)));
   }
   pairs.add(Pair.create(transitionView, transitionName));
   //noinspection unchecked - we're not worried about the "unchecked" conversion of List<Pair> to Pair[] here
   return ActivityOptionsCompat.makeSceneTransitionAnimation(activity, pairs.toArray(new Pair[pairs.size()]))
           .toBundle();
}

Observe R.string.transition_appbarlayout e R.id.appbarlayout . Esses IDs são arbitrários, desde que correspondam ao que seu código usa. No meu XML, eu layout a barra de ação personalizada da seguinte maneira (editada até o essencial):

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.AppBarLayout
    android:id="**@+id/appbarlayout**"
    android:transitionName="**@string/transition_appbarlayout**">

    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"/>
</android.support.design.widget.AppBarLayout>

Se você não usar uma barra de ferramentas como essa, essa parte poderá ser removida do método utilitário.

Então você chamaria isso no seu fragmento da seguinte forma:

startActivity(intent, UIUtils.transitionOptions(getActivity(),
                        R.id.**my_view**,
                        R.string.**transition_my_view**));

Usando os valores desejados, desde que corresponda ao seu XML.

Isso evita que a barra de status, a barra de ferramentas e a barra de navegação (botões voltar / página inicial / aplicativos recentes) pisquem durante a transição. O restante da transição de atividade é normal.

No meu caso, nosso tema do aplicativo é android:windowBackgroundazul. Isso causa um flash azul na transição, o que é bastante frustrante. Mas, em vez de fazer uma alteração que afeta todo o aplicativo dessa maneira, por enquanto vou com a primeira opção, rápida e suja.

Chad Schultz
fonte
3

Você precisa compartilhá-los ActivityOptions.makeSceneTransitionAnimation.

Por exemplo:

ActivityOptions.makeSceneTransitionAnimation(... Pair.create(activity.findViewById(android.R.id.window_status_bar), Window.STATUS_BAR_BACKGROUND_TRANSITION_NAME) 

(desculpe o psuedo; eu não tenho o valor android.R.id exato na mão)

Você pode executar uma transição apropriada depois de compartilhar as visualizações.

klmprt
fonte
Isso funcionou para você? Eu tentei isso e a barra de status e a barra de navegação ainda piscam em branco durante a transição.
rlay3
Sim, funcionou para mim. Tem certeza de que não há outra visão que possa estar sobrepondo-as temporariamente? Você já tentou configurar uma transição simples de atividade em um ambiente 'sandboxed' para isolar o problema?
Klmprt 31/10/2014
@klmprt Acho que você também precisa adiar a transição de entrada para fazê-la funcionar ... Também não consegui impedir que as barras de status / navegação fossem animadas simplesmente compartilhando as visualizações. Eu acho que você precisa aguardar a exibição da decoração da janela para terminar seu layout antes de deixar a transição de entrada começar.
Alex Lockwood
@klmprt O que minha resposta ainda não aborda é como impedir que a cor de fundo da Barra de Ação seja animada durante a transição. Se as duas atividades compartilharem a mesma cor de fundo da barra de ação, a cor de fundo da barra de ação parecerá animar quando a barra de ação da atividade de chamada diminuir gradualmente e a barra de ação da atividade de chamada diminuir gradualmente. Você tem alguma idéia de como contornar isso? questão?
Alex Lockwood
@klmprt Na verdade, acho que existe uma solução ainda melhor. Você pode simplesmente excluir os planos de fundo da barra de navegação / status como destinos na transição padrão de saída / desvanecimento da janela. Veja minha resposta atualizada para obter detalhes.
Alex Lockwood
3

Pelo que entendi, isso é causado pela sobreposição da transição de atividades. Para superar esse problema, usei os seguintes valores nos onCreate()métodos de ambas as atividades:

getWindow().setAllowEnterTransitionOverlap(false);
getWindow().setAllowReturnTransitionOverlap(false);
Randeep
fonte
3

Acabei de ter esse mesmo problema, e as respostas parecem estar faltando uma peça crítica para o quebra-cabeça. Lembre-se de que em uma transição de elemento compartilhado, tudo acontece na Atividade de destino .

Para remover o efeito intermitente, basta adicionar o seguinte à atividade que está sendo chamada:

Fade fade = new Fade();
fade.excludeTarget(android.R.id.statusBarBackground, true);
fade.excludeTarget(android.R.id.navigationBarBackground, true);

getWindow().setEnterTransition(fade);
getWindow().setExitTransition(fade);

Isso deve resolver seu problema!

LukeWaggoner
fonte
Oi. Não é o mesmo que a abordagem nº 1 na resposta principal?
precisa saber é
@ rlay3 Não totalmente. Pelo que sei, ele nunca menciona o fato de que isso só precisa ser definido na atividade de destino.
LukeWaggoner
hey @LukeWaggoner você poderia me ajudar com isto: stackoverflow.com/questions/50189286/...
Blackhawk
Você deve excluir a barra de status e a barra de navegação nas atividades, no chamador e no chamado.
Giovanni Di Gregorio
1

getWindow().setEnterTransition(null); na transição de entrada removeu a sobreposição branca para mim.

Dominic Davies
fonte
0

Aqui está como eu fiz isso. Eu compartilho o Status Bare Navigation Baro SharedElementTransitionjunto com um ImageView:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
  View imageView = view.findViewById(R.id.iv);
  Resources resources = view.getResources();
  imageView.setTransitionName(resources.getString(R.string.transition_image_thumbnail));

  Pair<View, String> p1 = Pair.create(imageView, resources.getString(R.string.transition_image_thumbnail));

  Window window = getActivity().getWindow();

  View navigationBar = getActivity().findViewById(android.R.id.navigationBarBackground);
  View statusBar = getActivity().findViewById(android.R.id.statusBarBackground);

  Pair<View, String> p2 = Pair.create(statusBar, statusBar.getTransitionName());
  Pair<View, String> p3 = Pair.create(navigationBar, navigationBar.getTransitionName());

  ActivityOptionsCompat options = ActivityOptionsCompat.makeSceneTransitionAnimation(getActivity(),
          p1, p2, p3);

  ActivityCompat.startActivity(getActivity(), intent, options.toBundle());
} else {
  startActivity(intent);
}
toobsco42
fonte