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