Shared element activity transition on android 5
S

12

38

I wanted to setup a shared element transition when going from one Activity to another.

The first Activity has a RecyclerView with items. When an item is clicked that item should animate to the new activity.

So i've set a android:transitionName="item" on both the final activity views, as wel as the recycler-view item views.

I'm also using this code when going to the next activity:

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

When clicking an item, it transitions properly and the new view is shown. It is really nice. However when i click the back button. Sometimes it works fine, but most of the time my activity crashes with the following stacktrace:

   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)

What am i doing wrong? It looks like a bug in android 5

Sniggle answered 25/10, 2014 at 19:29 Comment(7)
could you add all the relevant code so that its easier to figure out where you are going wrong?Sulfide
Try setting transitionName on only the shared element views.Rosenkranz
I have the same issue when I open a new activity, rotate screen, and press back button. Any solutions?Priggery
Did you ever figure out a fix? I just got the same exception.Reach
@mntgoat check out my answer, not the best way but probably will helpLebbie
I have the same issue, but I noticed some strange behavior. Error appears sometimes only on first three elements in my RecyclerView (when I go back from the details activity). I haven't found a way to solve this yet.Beckiebeckley
I just filled an issue on android reporting this error: issuetracker.google.com/issues/37943857Highkey
L
8

I encounter the same issue, and notice the crash happens if the original shared element is no longer visible on the previous screen when you go back (probably it is the last element on screen in portrait, but once switched to landscape it's no longer visible), and thus the transition has nowhere to put back the shared element.

My workaround is to remove the return transition (in the 2nd activity) if the screen has been rotated before going back, but I'm sure there must be a better way to handle this:

@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();
    }
}
Lebbie answered 6/4, 2015 at 6:15 Comment(0)
H
4

If you're using Proguard then try adding this into your rules file. I had the same issue and it appears to work?

-keep public class android.app.ActivityTransitionCoordinator

Hyperborean answered 13/11, 2014 at 17:31 Comment(0)
T
4

Try removing any merge xml tags that you might have on the final activity's view. I have noticed that transitioning to a view, that contains a merge tag, in which the transitioning element is a direct child of the merge tag, will cause this error, but should I replace the merge tag with a different container like CardView, the animation works just fine. Also make sure that there is a 1:1 relationship between the transitionNames in the views.

UPDATE: I experienced this issue once more when doing an activity transition, clicking the back button to return to the initial activity, and then trying the transition again. I was accessing the direct parent of the 'transition component', (A RelativeLayout) by id, with a findViewById() call, and then calling removeAllViews(). I ended up changing the code to call 'removeAllViews()' on a greater ancestor than the parent, also removed a tag from the element that was to take the place of the 'transition component' after page load. This alleviated my issue.

Telegony answered 13/3, 2015 at 18:2 Comment(0)
B
3

Make sure the View you are Transitioning to in the Second Activity is not the root layout. You can just wrap it in a FrameLayout with a transparent windowBackground.

Bout answered 24/11, 2016 at 15:33 Comment(1)
It was a solution!Renege
D
3

I had this same issue, for me it was being caused by the recyclerview executing updates after/during the first exit transition. I think the shared element view was then sometimes getting recycled, meaning it would no longer be available for the transition animation, hence the crash (normally on the return transition but sometimes on the exit transition). I solved it by blocking updates if the activity is paused (used an isRunning flag) - note it was pausing but not stopping as it was still visible in the background. Additionally I blocked the update process if the transition was running. I found it enough to listen to this callback:

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

As a final measure, although i'm not sure if this made a difference, I also did recyclerView.setLayoutFrozen(true) / recyclerView.setLayoutFrozen(false) in the onTransitionStart / onTransitionEnd.

Dysplasia answered 28/2, 2017 at 10:46 Comment(0)
B
2

Be sure the "itemView" you are passing in the transition is the view clicked (received on your onClick() callback)

Balneal answered 26/2, 2015 at 13:48 Comment(0)
B
2

I have faced the same issue, actually I used firebase and I have list of information and when user tap it will call detailActivity with sharedAnimation in this activity I was updating it as seen using firebase so firebase event updating the list item as seen, in this case this problem is invoking because recycler view that screen layout was getting effected.

and it invoke an exception because that transition id which one we have passed it was no more, so I solve this issue using this method.

onPause() I have frozen the layout and onResume() set it as false;

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

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

And it's working.

Bilberry answered 10/7, 2017 at 12:28 Comment(2)
Had this issue using a RecyclerView (updates were triggered in a stopped state). Making layout frozen helped.Cervantez
Thank you, seems to be my case, but the setLayoutFrozen() is deprecated now, I used suppressLayout() instead.Rayleigh
T
1

What I came up with is to avoid transitioning back to Activity with RecyclerView, or changing back transition with something else.

Disable all return transitions:

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

Or, if you want to disable only shared elements return transition, and be able to set your own return transition:

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

With that implemented in base activity, you can easily use it in 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());
    }
}
Towle answered 2/12, 2016 at 13:48 Comment(0)
A
1

I had this same error, mine was caused by the same reasoning behind hidro's answer but was caused by the keyboard hiding the shared element that the transition was going back to.

My workaround was to programmatically close the keyboard right before finishing the activity so the shared element on the previous activity isn't obscured.

View view = this.getCurrentFocus();
if (view != null) {  
    InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
    imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
supportFinishAfterTransition();
Arcature answered 21/12, 2016 at 2:33 Comment(0)
S
1

As @Fabio Rocha said, make sure that the itemView is retrieved from the ViewHolder.

You can get the ViewHolder by position via

mRecyclerView.findViewHolderForAdapterPosition(position);
Shorthanded answered 2/3, 2017 at 15:30 Comment(0)
D
0

The reason for this is actually quite simple: When you Navigate back to the parent Activity or Fragment, the View is not there yet (could be for many reasons).
So, what you want to do is to postpone the Enter Transition until the View is available.

My work around is to call the following function in onCreate() in my Fragment (but works in Activity too):

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;
        }
    });
}
Docila answered 22/5, 2017 at 22:15 Comment(0)
R
0

Got same issue, and it caused by recycler view updating in background, the recycler view will recreate view when notifyItemChanged(int index), so the share view was recycled and it got crash when come back.

My solution is call recyclerView.setItemAnimator(null);, and it will prevent recycler view from recreating view.

Robb answered 31/10, 2018 at 6:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.