Comment empêcher la barre d'état et la barre de navigation de s'animer pendant une transition d'animation de scène d'activité?

127

Tout d'abord, l'arrière-plan de ma barre d'état est défini sur marron foncé et l'arrière-plan de ma barre de navigation est noir par défaut. J'utilise le thème Light Material.

Je commence une nouvelle activité en utilisant ActivityOptions.makeSceneTransitionAnimationdes transitions par défaut, et je remarque que les barres d'état et de navigation passent brièvement au blanc, puis reviennent aux couleurs correctes.

Selon la documentation :

Pour obtenir le plein effet d'une transition, vous devez activer les transitions de contenu de fenêtre sur les activités appelante et appelée. Sinon, l'activité d'appel commencera la transition de sortie, mais vous verrez alors une transition de fenêtre (comme l'échelle ou le fondu)

J'utilise getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS);à la fois les activités d'appel et les activités appelées.

De même, si je change la transition d'entrée en diapositive, les barres d'état et de navigation ont brièvement une transition de diapositive avec un arrière-plan blanc.

Comment empêcher la barre d'état et la barre de navigation de s'animer pendant une transition d'animation de scène d'activité?

rlay3
la source

Réponses:

217

À ma connaissance, vous pouvez utiliser deux approches pour empêcher l'animation de la barre de navigation / d'état pendant la transition:

Approche n ° 1: exclure la barre d'état et la barre de navigation de la transition par défaut de sortie / entrée de fondu de la fenêtre

La raison pour laquelle la barre de navigation / d'état s'estompe pendant la transition est que, par défaut, toutes les vues non partagées (y compris les arrière-plans de la barre de navigation / d'état) disparaîtront dans vos activités d'appel / appelées respectivement une fois la transition commencée . Cependant, vous pouvez facilement contourner ce problème en excluant les arrière-plans de la barre de navigation / d'état de la Fadetransition sortie / entrée par défaut de la fenêtre . Ajoutez simplement le code suivant aux onCreate()méthodes de vos activités :

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

Cette transition peut également être déclarée dans le thème de l'activité en utilisant XML (c'est-à-dire dans votre propre res/transition/window_fade.xmlfichier):

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

Approche n ° 2: ajouter la barre d'état et la barre de navigation en tant qu'éléments partagés

Cette approche s'appuie sur la réponse de klmprt, qui a presque fonctionné pour moi ... même si j'avais encore besoin d'apporter quelques modifications.

Dans mon activité d'appel, j'ai utilisé le code suivant pour démarrer l'activité:

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

Jusqu'à présent, c'est essentiellement la même chose que celle suggérée par klmprt dans sa réponse. Cependant, j'avais également besoin d'ajouter le code suivant dans la onCreate()méthode de mon activité appelée afin d'empêcher la barre d'état et la barre de navigation de "clignoter" pendant la transition:

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

L'ajout de la barre d'état et des arrière-plans de la barre de navigation en tant qu'éléments partagés les forcera à être dessinés au-dessus de la transition de fondu sortie / entrée par défaut de la fenêtre, ce qui signifie qu'ils ne se faneront pas pendant la transition. Vous trouverez plus d'informations sur cette approche dans ce post Google+ .

Alex Lockwood
la source
14
Des idées pour lesquelles findViewById (android.R.id.navigationBarBackground) renvoie null sur Lollipop? J'utilise appcompat-v7
radzio
3
L'approche n ° 2 fonctionne, mais il y a toujours un scintillement (dans l'activité) lorsque la transition se produit. la barre d'état et la barre de navigation ne flickr plus.
Akshat
16
@alex Cela a fonctionné presque parfaitement pour moi jusqu'à ce que nous passions à la nouvelle bibliothèque de support avec android.support.design.widget.TabLayout et android.support.design.widget.AppBarLayout - maintenant le scintillement est de retour
Noa Drach
6
android.R.id.navigationBarBackground peut vous donner un NPE sur les appareils Samsung ou HTC car ils n'ont pas de barre de navigation à l'écran. Faites une vérification nulle avant de les ajouter en tant qu'éléments partagés
Garçon
8
J'essaye cette solution sur le Nexus 6 avec le nougat Android. Mais les deux approches ne fonctionnent pas pour moi.
Pardeep Kr
4

Empêchez complètement les transitions d'activité d'interférer avec les transitions d'éléments partagés:

Sur l'activité en cours, appelez getWindow (). SetExitTransition (null);

Sur l'activité d'entrée, appelez getWindow (). SetEnterTransition (null);

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

Je soupçonne que cela peut avoir des effets secondaires, mais je ne sais pas avec certitude. C'est très simple et fonctionne bien.

Empêcher des éléments spécifiques de clignoter:

J'ai commencé avec la réponse d'Alex Lockwood et j'ai fait pas mal d'expérimentation pour essayer de la faire fonctionner. Le cœur de celui-ci est correct, même si je n'avais pas besoin du code qu'il suggère pour l'activité de réception, mais j'ai rencontré des problèmes en l'appelant dans un fragment (au lieu d'une activité) et en définissant une barre d'outils comme barre d'action.

Oh, la chose Fragment? J'ai vu beaucoup de commentaires qui essayaient de récupérer des références à la barre d'état et à la barre de navigation étaient nulles. La même chose m'est arrivée aussi, jusqu'à ce que je réalise que je ne les trouverais pas dans la mise en page du Fragment ... elles étaient au-dessus de ce niveau. Par conséquent, le code ci-dessous pour obtenir la vue décorative de l'activité et la rechercher. Ensuite, je les ai trouvés sans problème.

Au final, j'ai développé cette méthode utilitaire:

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

Remarque R.string.transition_appbarlayout et R.id.appbarlayout . Ces ID sont arbitraires, tant qu'ils correspondent à ce que votre code utilise. Dans mon XML, je mets en page la barre d'actions personnalisée comme ceci (éditée jusqu'à l'essentiel):

<?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 vous n'utilisez pas une barre d'outils comme celle-ci, cette partie peut être supprimée de la méthode utilitaire.

Ensuite, vous l'appelleriez dans votre Fragment comme ceci:

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

En utilisant les valeurs que vous voulez, tant qu'elles correspondent à votre XML.

Cela empêche la barre d'état, la barre d'outils et la barre de navigation (boutons retour / accueil / applications récentes) de clignoter pendant la transition. Le reste de la transition d'activité est normal.

Dans mon cas, notre thème d'application a un android:windowBackgroundbleu. Cela provoque un flash bleu dans la transition, ce qui est assez frustrant. Mais plutôt que de faire un changement qui affecte toute l'application comme ça, pour l'instant je vais avec la première option, rapide et sale.

Chad Schultz
la source
3

Vous devez les partager dans ActivityOptions.makeSceneTransitionAnimation.

Par exemple:

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

(excusez le psuedo; je n'ai pas la valeur exacte android.R.id sous la main)

Vous pouvez exécuter une transition appropriée après avoir partagé les vues.

klmprt
la source
Cela a-t-il fonctionné pour vous? J'ai essayé cela et la barre d'état et la barre de navigation clignotent toujours en blanc pendant la transition.
rlay3
Oui, cela a fonctionné pour moi. Êtes-vous sûr qu'il n'y a pas d'autre vue qui les chevauche temporairement? Avez-vous essayé de configurer une transition d'activité simple dans un environnement «bac à sable» pour isoler le problème?
klmprt
@klmprt Je pense que vous devez également reporter la transition d'entrée afin de la faire fonctionner ... Je n'ai pas non plus été en mesure d'empêcher les barres d'état / de navigation de s'animer simplement en partageant les vues. Je suppose que vous devez attendre que la vue du décor de la fenêtre termine sa disposition avant de laisser la transition d'entrée commencer.
Alex Lockwood
@klmprt Ce que ma réponse ne traite toujours pas, c'est comment empêcher la couleur d'arrière-plan de la barre d'action de s'animer pendant la transition. Si les deux activités partagent la même couleur d'arrière-plan de la barre d'action, la couleur d'arrière-plan de la barre d'action semblera s'animer au fur et à mesure que la barre d'action de l'activité appelante disparaîtra progressivement et que la barre d'action de l'activité appelée disparaîtra doucement. Savez-vous comment contourner ce problème problème?
Alex Lockwood
@klmprt En fait, je pense qu'il existe une solution encore meilleure. Vous pouvez simplement exclure les arrière-plans de la barre de navigation / d'état comme cibles dans la transition de fondu sortie / entrée par défaut de la fenêtre. Voir ma réponse mise à jour pour plus de détails.
Alex Lockwood
3

Autant que je sache, cela est dû au chevauchement des transitions d'activités. Pour surmonter ce problème, j'ai utilisé les valeurs suivantes dans les onCreate()méthodes des deux activités:

getWindow().setAllowEnterTransitionOverlap(false);
getWindow().setAllowReturnTransitionOverlap(false);
Randeep
la source
3

J'ai juste eu ce même problème, et les réponses semblent manquer une pièce essentielle du puzzle. N'oubliez pas que lors d'une transition d'élément partagé, tout se passe dans l' activité de destination .

Pour supprimer l'effet de clignotement, ajoutez simplement ce qui suit à l'activité appelée:

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

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

Cela devrait résoudre votre problème!

LukeWaggoner
la source
Salut. N'est-ce pas la même chose que l'approche n ° 1 dans la première réponse?
rlay3
@ rlay3 Pas entièrement. Pour autant que je sache, il ne mentionne jamais le fait que cela ne doit être défini que dans l'activité de destination.
LukeWaggoner
hey @LukeWaggoner pourriez-vous m'aider ceci: stackoverflow.com/questions/50189286/…
blackHawk
Vous devez exclure la barre d'état et la barre de navigation dans les deux activités, l'appelant et l'appelé.
Giovanni Di Gregorio
1

getWindow().setEnterTransition(null); sur la transition d'entrée a supprimé la superposition blanche pour moi.

Dominic Davies
la source
0

Voici comment je l'ai fait. Je partage à la fois le Status Baret le Navigation Bardans le SharedElementTransitionavec 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
la source