web-dev-qa-db-fra.com

Transition d'activité d'élément partagé sur Android 5

Je voulais configurer une transition d'élément partagé lorsque je passais d'une activité à une autre. 

La première activité a un RecyclerView avec des éléments. Lorsqu'un élément est cliqué, cet élément doit s'animer à la nouvelle activité. 

J'ai donc défini un Android: transitionName = "item" sur les deux vues d'activité finales, ainsi que sur les vues d'élément vue de recycleur. 

J'utilise également ce code pour passer à l'activité suivante:

this.startActivity(intent, ActivityOptions.makeSceneTransitionAnimation(this, itemView, "boomrang_item").toBundle());

Lorsque vous cliquez sur un élément, il passe correctement et la nouvelle vue est affichée. C'est vraiment agréable. Cependant, lorsque je clique sur le bouton Précédent. Parfois cela fonctionne bien, mais la plupart du temps, mon activité se bloque avec le stacktrace suivant:

   Java.lang.NullPointerException: Attempt to invoke virtual method 'void Android.view.ViewGroup.transformMatrixToGlobal(Android.graphics.Matrix)' on a null object reference
            at Android.view.GhostView.calculateMatrix(GhostView.Java:95)
            at Android.app.ActivityTransitionCoordinator$GhostViewListeners.onPreDraw(ActivityTransitionCoordinator.Java:845)
            at Android.view.ViewTreeObserver.dispatchOnPreDraw(ViewTreeObserver.Java:847)
            at Android.view.ViewRootImpl.performTraversals(ViewRootImpl.Java:1956)
            at Android.view.ViewRootImpl.doTraversal(ViewRootImpl.Java:1054)
            at Android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.Java:5779)
            at Android.view.Choreographer$CallbackRecord.run(Choreographer.Java:767)
            at Android.view.Choreographer.doCallbacks(Choreographer.Java:580)
            at Android.view.Choreographer.doFrame(Choreographer.Java:550)
            at Android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.Java:753)
            at Android.os.Handler.handleCallback(Handler.Java:739)
            at Android.os.Handler.dispatchMessage(Handler.Java:95)
            at Android.os.Looper.loop(Looper.Java:135)
            at Android.app.ActivityThread.main(ActivityThread.Java:5221)
            at Java.lang.reflect.Method.invoke(Native Method)
            at Java.lang.reflect.Method.invoke(Method.Java:372)
            at com.Android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.Java:899)
            at com.Android.internal.os.ZygoteInit.main(ZygoteInit.Java:694)

Qu'est-ce que je fais mal? Cela ressemble à un bogue dans Android 5

34
TjerkW

Je rencontre le même problème et remarque que le blocage se produit si l'élément partagé d'origine n'est plus visible sur l'écran précédent lorsque vous revenez en arrière (il s'agit probablement du dernier élément à l'écran en mode portrait, mais une fois passé en mode paysage, il n'est plus visible). et ainsi la transition n’a nulle part où remettre l’élément partagé.

Ma solution consiste à supprimer la transition de retour (dans la 2e activité) si l'écran a été pivoté avant de revenir en arrière, mais je suis sûr qu'il doit exister un meilleur moyen de gérer cela:

@Override
public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);
    mOrientationChanged = !mOrientationChanged;
}

@Override
public void supportFinishAfterTransition() {
    if (mOrientationChanged) {
        /**
         * if orientation changed, finishing activity with shared element
         * transition may cause NPE if the original element is not visible in the returned
         * activity due to new orientation, we just finish without transition here
         */
        finish();
    } else {
        super.supportFinishAfterTransition();
    }
}
8
hidro

Essayez de supprimer les balises XML de fusion que vous pourriez avoir dans la vue de l'activité finale. J'ai remarqué que la transition vers une vue contenant une balise de fusion, dans laquelle l'élément de transition est un enfant direct de la balise de fusion, provoquera cette erreur, mais devrais-je remplacer la balise de fusion par un autre conteneur tel que CardView, l'animation fonctionne très bien. Assurez-vous également qu'il existe une relation 1: 1 entre les noms de transition dans les vues.

UPDATE: J'ai rencontré ce problème une fois de plus en effectuant une transition d'activité, en cliquant sur le bouton Précédent pour revenir à l'activité initiale, puis en essayant à nouveau la transition. J'accédais au parent direct du 'composant de transition', ( A RelativeLayout ) par identifiant, avec un appel findViewById (), puis en appelant removeAllViews (). J'ai fini par changer le code pour appeler 'removeAllViews ()' sur un ancêtre plus grand que le parent, et également supprimé une balise de l'élément qui devait remplacer le 'composant de transition' après le chargement de la page. Cela a atténué mon problème.

4
DanielGaffey

Si vous utilisez Proguard, essayez d’ajouter cela dans votre fichier de règles. J'ai eu le même problème et il semble fonctionner?

-keep public class Android.app.ActivityTransitionCoordinator

3
Sababado

Assurez-vous que la vue vers laquelle vous effectuez la transition dans la deuxième activité ne correspond pas à la disposition racine. Vous pouvez simplement l’envelopper dans un FrameLayout avec une fenêtre transparente.

3
Tinashe

J'ai eu le même problème, pour moi, cela était dû à l'exécution par Recyclerview de mises à jour après/pendant la première transition de sortie. Je pense que la vue des éléments partagés était parfois parfois recyclée, ce qui signifie qu'elle ne serait plus disponible pour l'animation de transition, d'où le blocage (normalement sur la transition de retour mais parfois sur la transition de sortie). Je l'ai résolu en bloquant les mises à jour si l'activité était en pause (utilisé un indicateur isRunning) - notez qu'elle était en pause mais ne s'arrêtait pas car elle était toujours visible en arrière-plan. De plus, j'ai bloqué le processus de mise à jour si la transition était en cours d'exécution. J'ai trouvé assez pour écouter ce rappel:

Transition sharedElementExitTransition = getWindow().getSharedElementExitTransition();
        if (sharedElementExitTransition != null) {
            sharedElementExitTransition.addListener(.....);
        }

Comme dernière mesure, bien que je ne sois pas sûr que cela ait eu un impact positif, j’ai également fait recyclerView.setLayoutFrozen(true)/recyclerView.setLayoutFrozen(false) dans onTransitionStart/onTransitionEnd.

3
hmac

Assurez-vous que "itemView" que vous transmettez dans la transition est la vue sur laquelle vous avez cliqué (reçu sur votre rappel onClick ()).

2
Fabio Rocha

Comme @Fabio Rocha a dit, assurez-vous que la itemView est extraite de la ViewHolder.

Vous pouvez obtenir la ViewHolder par position via

mRecyclerView.findViewHolderForAdapterPosition(position);
1
Chaos

J'ai eu cette même erreur, la mienne a été causée par le même raisonnement derrière la réponse de hidro mais par le clavier cachant l'élément partagé vers lequel la transition revenait.

Ma solution de contournement consistait à fermer le clavier par programme juste avant de terminer l'activité afin que l'élément partagé de l'activité précédente ne soit pas masqué.

View view = this.getCurrentFocus();
if (view != null) {  
    InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
    imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
supportFinishAfterTransition();
1
Hammy

Ce que j’ai proposé, c’est d’éviter de revenir à l’activité avec RecyclerView ou de changer de transition avec autre chose.

Désactiver toutes les transitions de retour:

@TargetApi(Build.VERSION_CODES.Lollipop)
@Override
public void finishAfterTransition() {
    finish();
}

Ou, si vous souhaitez désactiver uniquement la transition de retour d'éléments partagés et pouvoir définir votre propre transition de retour:

// Track if finishAfterTransition() was called
private boolean mFinishingAfterTransition;

@Override
protected void onCreate(@Nullable final Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    mFinishingAfterTransition = false;
}

public boolean isFinishingAfterTransition() {
    return mFinishingAfterTransition;
}

@Override
@TargetApi(Build.VERSION_CODES.Lollipop)
public void finishAfterTransition() {
    mFinishingAfterTransition = true;
    super.finishAfterTransition();
}

public void clearSharedElementsOnReturn() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Lollipop) {
        TransitionUtilsLollipop.clearSharedElementsOnReturn(this);
    }
}

@TargetApi(Build.VERSION_CODES.Lollipop)
private static final class TransitionUtilsLollipop {

    private TransitionUtilsLollipop() {
        throw new UnsupportedOperationException();
    }

    static void clearSharedElementsOnReturn(@NonNull final BaseActivity activity) {
        activity.setEnterSharedElementCallback(new SharedElementCallback() {

            @Override
            public void onMapSharedElements(final List<String> names,
                    final Map<String, View> sharedElements) {
                super.onMapSharedElements(names, sharedElements);
                if (activity.isFinishingAfterTransition()) {
                    names.clear();
                    sharedElements.clear();
                }
            }
        });
    }

Avec cela implémenté dans l'activité de base, vous pouvez facilement l'utiliser dans onCreate ()

@Override
protected void onCreate(@Nullable final Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    clearSharedElementsOnReturn(this);
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Lollipop) {
        // set your own transition
        getWindow().setReturnTransition(new VerticalGateTransition());
    }
}
1
Yaroslav Mytkalyk

La raison en est en fait assez simple: Lorsque vous revenez à l’activité ou au fragment parent, la vue n’est pas encore présente (pourrait être pour de nombreuses raisons).
Donc, ce que vous voulez faire est de reporter la transition Entrée jusqu'à ce que la vue soit disponible.

Mon travail consiste à appeler la fonction suivante dans onCreate () dans mon fragment (mais fonctionne également dans l'activité):

private void checkBeforeTransition() {
    // Postpone the transition until the window's decor view has
    // finished its layout.
    getActivity().supportPostponeEnterTransition();

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

Même problème, causé par la mise à jour de la vue du recycleur en arrière-plan, la vue recycleur recréera la vue quand notifyItemChanged (int index), de sorte que la vue de partage a été recyclée et qu'elle s'est écrasée à son retour.

Ma solution s'appelle recyclerView.setItemAnimator(null); et empêchera la vue du recycleur de recréer la vue.

0
Glorin

J'ai été confronté au même problème. En fait, j'ai utilisé Firebase et une liste d'informations. Lorsque l'utilisateur a tapé, il appellerait detailActivity avec sharedAnimation dans cette activité. Si ce problème est invoqué car le recycleur a vu que l’écran layout était en train d’être affecté.

et il appelle une exception parce que la transition identifiée par celle que nous avons adoptée n’était plus, alors je résous ce problème en utilisant cette méthode.

onPause () J'ai gelé la mise en page et onResume () l'a définie sur false;

 @Override
public void onPause() {
    super.onPause();
    mRecycler.setLayoutFrozen(true);
}

@Override
public void onResume() {
    super.onResume();
    mRecycler.setLayoutFrozen(false);
}

Et ça marche.

0
Abdul Rizwan