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
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();
}
}
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.
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
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.
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
.
Be sure the "itemView" you are passing in the transition is the view clicked (received on your onClick() callback)
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.
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());
}
}
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();
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);
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;
}
});
}
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.
来源:https://stackoverflow.com/questions/26566385/shared-element-activity-transition-on-android-5