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
setEnterSharedElementCallbackto change thetransitionNameandViewto the new one before returning fromActivity2. - Wait for the
Activity1to 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
finishAfterTransitionin Activity2 and callsetEnterSharedElementCallbackmethod to re-map the current selected item in ViewPager. Also, callsetResultto 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
ShareElementCallbackso I can set the callback before knowing whichViewis 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
onActivityReenterin Activity1, get the selected index from the resultIntent. SetsetExitSharedElementCallbackto re-map new selectedViewwhen the transition begins.CallsupportPostponeEnterTransitionto delay a bit because your newViewmay not be rendered at this point. UsegetViewTreeObserver().addOnPreDrawListenerto listen for the layout changes, find the rightViewby 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"