56

My app contains a view which consists of a ViewPager consisting of a handful of fragments. When you click on an item in one of these fragments, the expected behavior is for the shared element (in this case an image) to transition to the fragment which displays more information about the clicked content.

Here is a very simple video of what it should look like:

https://dl.dropboxusercontent.com/u/97787025/device-2015-06-03-114842.mp4

This is just using a Fragment->Fragment transition.

The problem arises when you place the starting fragment inside a ViewPager. I suspect this is because the ViewPager uses its parent fragment's child fragment manager, which is different than the fragment manager of the activity, which is handling the fragment transaction. Here is a video of what happens:

https://dl.dropboxusercontent.com/u/97787025/device-2015-06-03-120029.mp4

I'm pretty certain the issue here as I explained above is the child fragment manager vs the activity's fragment manager. Here is how I am making the transition:

SimpleFragment fragment = new SimpleFragment();

FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.am_list_pane, fragment, fragment.getClass().getSimpleName());

if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
    TransitionSet enterTransition = new TransitionSet();
    enterTransition.addTransition(new ChangeBounds());
    enterTransition.addTransition(new ChangeClipBounds());
    enterTransition.addTransition(new ChangeImageTransform());
    enterTransition.addTransition(new ChangeTransform());

    TransitionSet returnTransition = new TransitionSet();
    returnTransition.addTransition(new ChangeBounds());
    returnTransition.addTransition(new ChangeClipBounds());
    returnTransition.addTransition(new ChangeImageTransform());
    returnTransition.addTransition(new ChangeTransform());

    fragment.setSharedElementEnterTransition(enterTransition);
    fragment.setSharedElementReturnTransition(returnTransition);

    transaction.addSharedElement(iv, iv.getTransitionName());
}

transaction.addToBackStack(fragment.getClass().getName());

transaction.commit();

This works fine when both fragments are managed by the activity's fragment manager, but when I load up a ViewPager like this:

ViewPager pager = (ViewPager) view.findViewById(R.id.pager);
pager.setAdapter(new Adapter(getChildFragmentManager()));

The children of the ViewPager are not being managed by the activity, and it doesn't work anymore.

Is this an oversight by the Android team? Is there any way to pull this off? Thanks.

JMRboosties
  • 15,500
  • 20
  • 76
  • 116
  • 1
    can you please provide the entire code of your Activity, Parent Fragment & Child Fragment ? Thanks :) – Yash Sampat Jun 17 '15 at 06:31
  • I had problems with nested `Fragment`s and shared element transitions too, [here](http://stackoverflow.com/questions/29029970/fragment-shared-element-transition-not-working-in-nested-views) is my question. – Veaceslav Gaidarji Jun 17 '15 at 09:20
  • Why do u pass to the adapter the ChildFragmentManager and not the FragmentManger? – EE66 Jun 18 '15 at 06:46
  • @EE66 I believe the ChildFragmentManager is passed because the ViewPager children are not being managed by the activity but by the ViewPager, so it is not the same fragment manager. – AdamMc331 Jun 18 '15 at 13:39
  • Hello this is not working for me.. what i am trying is that i have a list in a viewpager's fragment and on click of any item i want to transact the image to its detail fragment.. i tried every possible solution i got here..but nothing is working..any help will be appreciated.thanks in advance. – Harry Sharma Mar 03 '16 at 10:29
  • I see that you accepted the answer below, but I was wondering if you'd be able to post the full sample of code to show how the viewpager adapter handles the getItem method when creating the new fragments. Trying to figure out if this will work for my situation - going from ViewPager to ViewPager... Activity A (list) contains a TabLayout and ViewPager with 3 fragments - tapping an item then goes to Activity B (detail) which contains a ViewPager 1 or more fragments. – Kyle Mar 10 '16 at 14:29

3 Answers3

40

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():

@Override
protected 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.

Stephan
  • 15,704
  • 7
  • 48
  • 63
kha
  • 19,123
  • 9
  • 34
  • 67
  • Hello this is not working for me.. what i am trying is that i have a list in a viewpager's fragment and on click of any item i want to transact the image to its detail fragment.. i tried every possible solution i got here..but nothing is working..any help will be appreciated.thanks in advance. – Harry Sharma Mar 03 '16 at 10:29
  • How to achieve this with fragment transactions can be found here: http://stackoverflow.com/a/26984314/2380518 – Miloš Černilovský Jun 01 '16 at 12:23
  • @ligi glad to hear it helped you. – kha Oct 25 '16 at 09:41
  • I'm facing a issue. When in ViewPager i i scroll to next items then on BackPressed it doesn't animate to new item on previous Activity which is also having a ViewPager. It returns to the old selected item. – Usman Rana Aug 29 '18 at 08:07
  • @kha cay you please help me with this SO question https://stackoverflow.com/q/59431465/4291272 – Faisal Shaikh Dec 21 '19 at 14:05
1

On the activity supportPostponeEnterTransition();

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

0

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).

Mark
  • 3,334
  • 1
  • 22
  • 27