Fragment shared element transitions don't work with ViewPager Fragment shared element transitions don't work with ViewPager android android

Fragment shared element transitions don't work with ViewPager


Probably you've already found an answer to this but in case you haven't, here's what I did to fix it after a few hours of scratching my head.

The problem I think is a combination of two factors:

  • The Fragments in ViewPager load with a delay, meaning that the activity returns a lot faster than its fragments which are contained inside the ViewPager

  • If you are like me, your ViewPager's child fragments are most likely the same type. This means, they all share the same transition name (if you've set them in your xml layout), unless you set them in code and only set it once, on the visible fragment.

To fix both of these problems, this is what I did:

1. Fixing the delayed loading problem:

Inside your activity (the one that contains the ViewPager), add this line after super.onCreate() and before setContentView():

@Overrideprotected void onCreate(Bundle savedInstanceState) {    super.onCreate(savedInstanceState);    ActivityCompat.postponeEnterTransition(this); // This is the line you need to add    setContentView(R.layout.feeds_content_list_activity);    ...}

2. Fixing the problem with multiple fragments with the same transition name:

Now there are quite a few ways of going about doing this but this is what I ended up with inside my "detail" activity, i.e. the activity that contains the ViewPager (in the onCreate() but you can do it anywhere really):

_viewPager.setAdapter(_sectionsPagerAdapter);_viewPager.setCurrentItem(position);......_pagerAdapter.getItem(position).setTransitionName(getResources().getString(R.string.transition_contenet_topic));

You need to be careful since the Activity may not yet be attached to your ViewPager fragment so it's easier to just pass in the transition name from the activity to the fragment if you're loading it from a resource

The actual implementation is as simple as you expect:

public void setTransitionName(String transitionName) {    _transitionName = transitionName;}

Then inside your fragment's onViewCreated(), add this line:

public void onViewCreated(View view, Bundle savedInstanceState) {    super.onViewCreated(view, savedInstanceState);    ...    if (_transitionName != null && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {        setTransitionNameLollipop();    }    ...}@TargetApi(Build.VERSION_CODES.LOLLIPOP)private void setTransitionNamesLollipop() {    _imgTopic.setTransitionName(_transitionName);}

The last piece of the puzzle is to find out when your fragment is fully loaded and then call ActivityCompat.startPostponedEnterTransition(getActivity());.

In my case, my fragments were not fully loaded until later since I'm loading most things off the UI thread which means I had to figure out a way to call this when everything was loaded but if that's not your case, you can call this right after you call setTransitionNameLollipop().

The problem with this approach is that the exit transition may not work unless you're very careful and reset the transition name on the "visible" fragment right before you exit the activity to navigate back. That can easily be done like so:

  1. Listen to page change on your ViewPager
  2. Remove the transition name(s) as soon as your fragment is out of view
  3. Set the transition name(s) on the visible fragment.
  4. Instead of calling finish(), call ActivityCompat.finishAfterTransition(activity);

This can get very complex very soon if you back transition needs to transition to a RecyclerView which was my case. For that, there's a much better answer provided by @Alex Lockwood here: ViewPager Fragments – Shared Element Transitions which has a very well written example code (albeit a lot more complicated than what I just wrote) here: https://github.com/alexjlockwood/activity-transitions/tree/master/app/src/main/java/com/alexjlockwood/activity/transitions

In my case, I didn't have to go so far as to implement his solution and the above solution that I posted worked for my case.

In case you have multiple shared elements, I'm sure you can figure out how to extend the methods in order to cater to them.


On the activitysupportPostponeEnterTransition();

And when your fragments are loaded (try to sync them, maybe with EventBus or whatever)

startPostponedEnterTransition();

Refer to this sample

http://www.androiddesignpatterns.com/2015/03/activity-postponed-shared-element-transitions-part3b.html


I have been banging my head against the wall with this one recently. All I wanted was a Fragment in a ViewPager to launch another Fragment with a nice expanding card type shared element transition. None of the suggestions above worked for me so I decided to try launching an Activity styled like a Dialog:

<style name="AppTheme.CustomDialog" parent="MyTheme">    <item name="android:windowIsTranslucent">true</item>    <item name="android:windowBackground">@android:color/transparent</item>    <item name="android:windowContentOverlay">@null</item>    <item name="android:windowNoTitle">true</item>    <item name="android:backgroundDimEnabled">true</item></style>

Then to launch the Activity from the Fragment:

Intent intent = new Intent(getContext(), MyDialogActivity.class);ActivityOptionsCompat options = ActivityOptionsCompat                        .makeSceneTransitionAnimation(getActivity(),                                Pair.create(sharedView, "target_transition"));ActivityCompat.startActivity(getActivity(), intent, options.toBundle());

In the Fragment layout put an android:transition_name on the sharedView and in the Activity layout have android:transition_name="target_transition" (same as Pair.create() second argument).