问题
I have fragment from which I'm launching activity with shared element transition that has viewpager in it, the enter transition works fine but when i scroll in view pager and finish transition the shared image comes from left side which is not desired it should reposition itself to where it was launched, here is my code:
Intent myIntent = new Intent(getActivity(), EnlargeActivity.class);
ActivityOptionsCompat options = ActivityOptionsCompat.
makeSceneTransitionAnimation(getActivity(),
imageView,
ViewCompat.getTransitionName(imageView));
startActivity(myIntent, options.toBundle());
I'm updating view and its name in activity that contains viewpager when finishing activity, but its going with blink:
public void finishAfterTransition() {
setEnterSharedElementCallback(new SharedElementCallback() {
@Override
public void onMapSharedElements(List<String> names, Map<String, View> sharedElements) {
// Clear all current shared views and names
names.clear();
sharedElements.clear();
ViewGroup viewGroup = (ViewGroup) viewPagerDetail.getAdapter()
.instantiateItem(viewPagerDetail, viewPagerDetail.getCurrentItem());
if (viewGroup == null) {
return;
}
// Map the first shared element name to the child ImageView.
sharedElements.put(viewGroup.findViewById(R.id.img).getTransitionName(), viewGroup.findViewById(R.id.img));
// setExitSharedElementCallback((SharedElementCallback) this);
}
});
super.finishAfterTransition();
回答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) public void finishAfterTransition() { setEnterSharedElementCallback(new SharedElementCallback() { @Override public void onMapSharedElements(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 name String transitionName = ViewCompat.getTransitionName(selectedView); names.add(transitionName); sharedElements.put(transitionName, selectedView); setExitSharedElementCallback((SharedElementCallback) null); } }); Intent intent = new Intent(); 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) private static class CustomSharedElementCallback extends SharedElementCallback { private View mView; /** * Set the transtion View to the callback, this should be called before starting the transition so the View is not null */ public void setView(View view) { mView = view; } @Override public void onMapSharedElements(List<String> names, Map<String, View> sharedElements) { // Clear all current shared views and names names.clear(); sharedElements.clear(); // Store new selected view and name String 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) public void onActivityReenter(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 = new CustomSharedElementCallback(); getActivity().setExitSharedElementCallback(callback); // Listen for the transition end and clear all registered callback getActivity().getWindow().getSharedElementExitTransition().addListener(new Transition.TransitionListener() { @Override public void onTransitionStart(Transition transition) {} @Override public void onTransitionPause(Transition transition) {} @Override public void onTransitionResume(Transition transition) {} @Override public void onTransitionEnd(Transition transition) { removeCallback(); } @Override public void onTransitionCancel(Transition transition) { removeCallback(); } private void removeCallback() { if (getActivity() != null) { getActivity().getWindow().getSharedElementExitTransition().removeListener(this); getActivity().setExitSharedElementCallback((SharedElementCallback) null); } } }); // Pause transition until the selected view is fully drawn getActivity().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(new ViewTreeObserver.OnPreDrawListener() { @Override public boolean onPreDraw() { mPhotoList.getViewTreeObserver().removeOnPreDrawListener(this); RecyclerView.ViewHolder holder = mPhotoList.findViewHolderForAdapterPosition(selectedIndex); if (holder instanceof ViewHolder) { callback.setView(((ViewHolder) holder).mPhotoImg); } // Continue the transition getActivity().supportStartPostponedEnterTransition(); return true; } }); }
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:
@Override
public View instantiateItem(ViewGroup container, int position) {
// Create the View
view.setTag(position)
// ...
}
Listen to onPageSelected
event to get the selected index:
mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
mSelectedIndex = position;
}
@Override
public void onPageScrollStateChanged(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) {
return null;
}
}
回答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(
new OnLayoutChangeListener() {
@Override
public void onLayoutChange(View view,
int left,
int top,
int right,
int bottom,
int oldLeft,
int oldTop,
int oldRight,
int oldBottom) {
recyclerView.removeOnLayoutChangeListener(this);
final RecyclerView.LayoutManager layoutManager =
recyclerView.getLayoutManager();
View viewAtPosition =
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.
来源:https://stackoverflow.com/questions/50189286/shared-element-transition-is-not-exiting-properly