¿Cómo evito que la barra de estado y la barra de navegación se animen durante una transición de animación de escena de actividad?

127

En primer lugar, el fondo de mi barra de estado está configurado en marrón oscuro y el fondo de mi barra de navegación es negro por defecto. Estoy usando el tema Material ligero.

Estoy comenzando una nueva actividad usando las ActivityOptions.makeSceneTransitionAnimationtransiciones predeterminadas, y noto que tanto el estado como las barras de navegación se desvanecen brevemente en blanco y luego vuelven a los colores correctos.

De acuerdo con la documentación :

Para obtener el efecto completo de una transición, debe habilitar las transiciones de contenido de la ventana en las actividades de llamada y llamadas. De lo contrario, la actividad de llamada comenzará la transición de salida, pero luego verá una transición de ventana (como escala o desvanecimiento)

Estoy usando getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS);tanto las actividades de llamada como las llamadas.

Del mismo modo, si cambio la transición de entrada a una diapositiva, tanto el estado como las barras de navegación tienen brevemente una transición de diapositiva con un fondo blanco.

¿Cómo evito que la barra de estado y la barra de navegación se animen durante una transición de animación de escena de actividad?

rlay3
fuente

Respuestas:

217

Hay dos enfoques que puede usar que conozco para evitar que la barra de navegación / estado se anime durante la transición:

Enfoque n. ° 1: excluir la barra de estado y la barra de navegación de la transición predeterminada de salida / entrada de transición de la ventana

La razón por la que la barra de navegación / estado se desvanece durante la transición es porque, de manera predeterminada, todas las vistas no compartidas (incluidos los fondos de la barra de navegación / estado) se desvanecerán / se desvanecerán en sus llamadas / Actividades llamadas respectivamente una vez que comience la transición . Sin embargo, puede evitar esto fácilmente excluyendo los fondos de la barra de navegación / estado de la Fadetransición predeterminada de salida / entrada de la ventana . Simplemente agregue el siguiente código a los onCreate()métodos de sus Actividades :

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

Esta transición también podría declararse en el tema de la actividad utilizando XML (es decir, en su propio res/transition/window_fade.xmlarchivo):

<?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>

Enfoque n. ° 2: agregue la barra de estado y la barra de navegación como elementos compartidos

Este enfoque se basa en la respuesta de klmprt, que casi funcionó para mí ... aunque aún necesitaba hacer un par de modificaciones.

En mi actividad de llamadas, utilicé el siguiente código para iniciar la actividad:

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

Hasta ahora, esto es esencialmente lo mismo que Klmprt sugirió en su respuesta. Sin embargo, también necesitaba agregar el siguiente código en el onCreate()método de mi actividad llamada para evitar que la barra de estado y la barra de navegación "parpadeen" durante la transición:

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

Agregar la barra de estado y los fondos de la barra de navegación como elementos compartidos los forzará a dibujar sobre la transición predeterminada de salida / entrada de la ventana, lo que significa que no se desvanecerán durante la transición. Se puede encontrar más discusión sobre este enfoque en esta publicación de Google+ .

Alex Lockwood
fuente
14
¿Alguna idea de por qué findViewById (android.R.id.navigationBarBackground) está volviendo nulo en Lollipop? Estoy usando appcompat-v7
radzio
3
El enfoque n. ° 2 funciona, pero todavía hay un parpadeo (en la actividad) cuando ocurre la transición. la barra de estado y la barra de navegación ya no parpadean.
Akshat
16
@alex Esto funcionó casi perfectamente para mí hasta que cambiamos a la nueva biblioteca de soporte con android.support.design.widget.TabLayout y android.support.design.widget.AppBarLayout - ahora el parpadeo ha vuelto
Noa Drach
66
android.R.id.navigationBarBackground puede darle un NPE en dispositivos Samsung o HTC, ya que no tienen una barra de navegación en pantalla. Haga una comprobación nula antes de agregarlos como elementos compartidos
Boy
8
Estoy probando esta solución en nexus 6 con turrón de Android. Pero ambos enfoques no están funcionando para mí.
Pardeep Kr
4

Evite completamente que las transiciones de actividad interfieran con las transiciones de elementos compartidos:

En la actividad existente, llame a getWindow (). SetExitTransition (null);

En la actividad de entrada, llame a getWindow (). SetEnterTransition (null);

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

Sospecho que esto puede tener efectos secundarios, pero no estoy seguro. Es muy simple y funciona sin embargo.

Evitar que elementos específicos parpadeen:

Comencé con la respuesta de Alex Lockwood e hice un poco de experimentación para intentar que funcionara. El núcleo es correcto, aunque no necesitaba el código que sugiere para la Actividad receptora, pero encontré algunos problemas al llamarlo en un Fragmento (en lugar de una Actividad) y al establecer una barra de herramientas como la barra de acción.

Oh, lo del fragmento? Vi muchos comentarios que tratar de recuperar referencias a la barra de estado y la barra de navegación eran nulos. A mí también me pasó lo mismo, hasta que me di cuenta de que no los encontraría en el diseño del Fragmento ... estaban por encima de ese nivel. Por lo tanto, el siguiente código para obtener la vista de decoración de la Actividad y buscar eso. Luego los encontré sin problema.

Al final, desarrollé este método de utilidad:

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

Tenga en cuenta R.string.transition_appbarlayout y R.id.appbarlayout . Estas ID son arbitrarias, siempre que coincidan con lo que usa su código. En mi XML, diseño la barra de acción personalizada de la siguiente manera (editado a lo esencial):

<?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>

Si no usa una barra de herramientas como esta, esa parte se puede eliminar del método de utilidad.

Entonces lo llamarías en tu Fragmento así:

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

Usando los valores que desee, siempre que coincida con su XML.

Esto evita que la barra de estado, la barra de herramientas y la barra de navegación (botones atrás / inicio / aplicaciones recientes) parpadeen durante la transición. El resto de la transición de la actividad es normal.

En mi caso, el tema de nuestra aplicación tiene un android:windowBackgroundcolor azul. Esto provoca un destello azul en la transición, lo cual es bastante frustrante. Pero en lugar de hacer un cambio que afecte a toda la aplicación de esa manera, por ahora voy con la primera opción, rápida y sucia.

Chad Schultz
fuente
3

Necesitas compartirlos en ActivityOptions.makeSceneTransitionAnimation.

P.ej:

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

(disculpe el psuedo; no tengo el valor exacto de android.R.id en la mano)

Puede ejecutar una transición apropiada después de haber compartido las vistas.

klmprt
fuente
¿Funcionó esto para ti? Intenté esto y la barra de estado y la barra de navegación aún parpadean en blanco durante la transición.
rlay3
Sí, funcionó para mí. ¿Estás seguro de que no hay otra vista que pueda superponerse temporalmente? ¿Has intentado configurar una transición de actividad simple en un entorno 'sandboxed' para aislar el problema?
klmprt
@klmprt Creo que también necesita posponer la transición de entrada para que funcione ... Tampoco pude evitar que las barras de estado / navegación se animaran simplemente compartiendo las vistas. Supongo que debe esperar a que la vista de decoración de la ventana termine su diseño antes de permitir que comience la transición de entrada.
Alex Lockwood
@klmprt Lo que mi respuesta aún no aborda es cómo evitar que el color de fondo de la barra de acción se anime durante la transición. Si ambas actividades comparten el mismo color de fondo de la barra de acción, entonces el color de fondo de la barra de acción parecerá animarse a medida que la barra de acción de la actividad que llama se desvanezca gradualmente y la barra de acción de la actividad llamada se desvanezca suavemente. ¿Tiene alguna idea de cómo evitar esto? ¿problema?
Alex Lockwood
@klmprt En realidad, creo que hay una solución aún mejor. Simplemente puede excluir los fondos de la barra de navegación / estado como objetivos en la transición predeterminada de salida / entrada de la ventana. Vea mi respuesta actualizada para más detalles.
Alex Lockwood
3

Hasta donde entiendo, esto es causado por la superposición de la transición de actividad. Para superar este problema, he utilizado los siguientes valores en los onCreate()métodos de ambas actividades:

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

Acabo de tener este mismo problema, y ​​parece que a las respuestas les falta una pieza crítica del rompecabezas. Recuerde que en una transición de elemento compartido, todo sucede en la Actividad de destino .

Para eliminar el efecto intermitente, simplemente agregue lo siguiente a la actividad que se llama:

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

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

¡Esto debería solucionar tu problema!

LukeWaggoner
fuente
Hola. ¿No es esto lo mismo que el Enfoque n. ° 1 en la respuesta principal?
rlay3
@ rlay3 No del todo. Por lo que puedo decir, él nunca menciona el hecho de que esto solo debe establecerse en la actividad de destino.
LukeWaggoner
Hola @LukeWaggoner, ¿podrías ayudarme ?: stackoverflow.com/questions/50189286/…
blackHawk
Debe excluir la barra de estado y la barra de navegación en ambas actividades, la persona que llama y la llamada.
Giovanni Di Gregorio
1

getWindow().setEnterTransition(null); en la transición de entrada eliminó la superposición blanca para mí.

Dominic Davies
fuente
0

Así es como lo hice. Comparto tanto el Status Barcomo Navigation Barel SharedElementTransitionjunto con un 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
fuente