Shared Element Transition Is Not Exiting Properly
Solution 1:
Basically, Android start the transition with your pre-defined View
and transitionName
and automatically use the same properties for the return transition. When you change your focused View in ViewPager, Android doesn't know about that and keep the transition on the previous one on its way back. So you need to inform Android about the changes:
- Remap the transition properties: Use
setEnterSharedElementCallback
to change thetransitionName
andView
to the new one before returning fromActivity2
. - Wait for the
Activity1
to finish renderingaddOnPreDrawListener
.
It's a bit complex in the final implementation. But you can look at my sample code https://github.com/tamhuynhit/PhotoGallery. I try to implement the shared-element-transition from many simple to complex sections.
Your problem appeared from Level 3
and solved in Level 4
.
I am writing a tutorial about this but it's not in English so hope the code can help
UPDATE 1: Work flow
Here is how I implement it in my code:
Override
finishAfterTransition
in Activity2 and callsetEnterSharedElementCallback
method to re-map the current selected item in ViewPager. Also, callsetResult
to pass the new selected index back to previous activity here.@Override@TargetApi(Build.VERSION_CODES.LOLLIPOP) publicvoidfinishAfterTransition() { setEnterSharedElementCallback(newSharedElementCallback() { @OverridepublicvoidonMapSharedElements(List<String> names, Map<String, View> sharedElements) { View selectedView = getSelectedView(); if (selectedView == null) return; // Clear all current shared views and names names.clear(); sharedElements.clear(); // Store new selected view and nameString transitionName = ViewCompat.getTransitionName(selectedView); names.add(transitionName); sharedElements.put(transitionName, selectedView); setExitSharedElementCallback((SharedElementCallback) null); } }); Intent intent = newIntent(); intent.putExtra(PHOTO_FOCUSED_INDEX, mCurrentIndex); setResult(RESULT_PHOTO_CLOSED, intent); super.finishAfterTransition(); }
Write a custom
ShareElementCallback
so I can set the callback before knowing whichView
is going to be used.@TargetApi(Build.VERSION_CODES.LOLLIPOP) privatestaticclassCustomSharedElementCallbackextendsSharedElementCallback { privateView mView; /** * Set the transtion View to the callback, this should be called before starting the transition so the View is not null */publicvoidsetView(View view) { mView = view; } @OverridepublicvoidonMapSharedElements(List<String> names, Map<String, View> sharedElements) { // Clear all current shared views and names names.clear(); sharedElements.clear(); // Store new selected view and nameString transitionName = ViewCompat.getTransitionName(mView); names.add(transitionName); sharedElements.put(transitionName, mView); } }
Override
onActivityReenter
in Activity1, get the selected index from the resultIntent
. SetsetExitSharedElementCallback
to re-map new selectedView
when the transition begins.CallsupportPostponeEnterTransition
to delay a bit because your newView
may not be rendered at this point. UsegetViewTreeObserver().addOnPreDrawListener
to listen for the layout changes, find the rightView
by the selected index and continue the transitionsupportStartPostponedEnterTransition
.@Override@TargetApi(Build.VERSION_CODES.LOLLIPOP) publicvoidonActivityReenter(int resultCode, Intent data) { if (resultCode != LevelFourFullPhotoActivity.RESULT_PHOTO_CLOSED || data == null) return; final int selectedIndex = data.getIntExtra(LevelFourFullPhotoActivity.PHOTO_FOCUSED_INDEX, -1); if (selectedIndex == -1) return; // Scroll to the new selected view in case it's not currently visible on the screen mPhotoList.scrollToPosition(selectedIndex); final CustomSharedElementCallback callback = newCustomSharedElementCallback(); getActivity().setExitSharedElementCallback(callback); // Listen for the transition end and clear all registered callbackgetActivity().getWindow().getSharedElementExitTransition().addListener(newTransition.TransitionListener() { @OverridepublicvoidonTransitionStart(Transition transition) {} @OverridepublicvoidonTransitionPause(Transition transition) {} @OverridepublicvoidonTransitionResume(Transition transition) {} @OverridepublicvoidonTransitionEnd(Transition transition) { removeCallback(); } @OverridepublicvoidonTransitionCancel(Transition transition) { removeCallback(); } privatevoidremoveCallback() { if (getActivity() != null) { getActivity().getWindow().getSharedElementExitTransition().removeListener(this); getActivity().setExitSharedElementCallback((SharedElementCallback) null); } } }); // Pause transition until the selected view is fully drawngetActivity().supportPostponeEnterTransition(); // Listen for the RecyclerView pre draw to make sure the selected view is visible,// and findViewHolderForAdapterPosition will return a non null ViewHolder mPhotoList.getViewTreeObserver().addOnPreDrawListener(newViewTreeObserver.OnPreDrawListener() { @OverridepublicbooleanonPreDraw() { mPhotoList.getViewTreeObserver().removeOnPreDrawListener(this); RecyclerView.ViewHolder holder = mPhotoList.findViewHolderForAdapterPosition(selectedIndex); if (holder instanceofViewHolder) { callback.setView(((ViewHolder) holder).mPhotoImg); } // Continue the transitiongetActivity().supportStartPostponedEnterTransition(); returntrue; } }); }
UPDATE 2: getSelectedItem
To get selected View from the ViewPager, don't use getChildAt
or you get the wrong View, use findViewWithTag
instead
In the PagerAdapter.instantiateItem
, use position as tag for each View:
@Overridepublic View instantiateItem(ViewGroup container, int position) {
// Create the View
view.setTag(position)
// ...
}
Listen to onPageSelected
event to get the selected index:
mViewPager.addOnPageChangeListener(newViewPager.OnPageChangeListener() {
@OverridepublicvoidonPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@OverridepublicvoidonPageSelected(int position) {
mSelectedIndex = position;
}
@OverridepublicvoidonPageScrollStateChanged(int state) {
}
});
Call getSelectedView
to get the current view by the selected index
private View getSelectedView() {
try {
return mPhotoViewPager.findViewWithTag(mSelectedIndex);
} catch (IndexOutOfBoundsException | NullPointerException ex) {
returnnull;
}
}
Solution 2:
This is actually a default behavior, I was struggling SharedElementTransitions a lot, but I have nested fragments. I got my solution from an article (very recent article), it shows an implementation with a RecyclerView
, which I assume you have. In short, the solution is to override onLayoutChange
:
recyclerView.addOnLayoutChangeListener(
newOnLayoutChangeListener() {
@OverridepublicvoidonLayoutChange(View view,
int left,
int top,
int right,
int bottom,
int oldLeft,
int oldTop,
int oldRight,
int oldBottom) {
recyclerView.removeOnLayoutChangeListener(this);
final RecyclerView.LayoutManagerlayoutManager=
recyclerView.getLayoutManager();
ViewviewAtPosition=
layoutManager.findViewByPosition(MainActivity.currentPosition);
// Scroll to position if the view for the current position is null (not // currently part of layout manager children), or it's not completely// visible.if (viewAtPosition == null
|| layoutManager.isViewPartiallyVisible(viewAtPosition, false, true)){
recyclerView.post(()
-> layoutManager.scrollToPosition(MainActivity.currentPosition));
}
}
});
Here is the article, and you will also find the project on GitHub.
Post a Comment for "Shared Element Transition Is Not Exiting Properly"