How to start shared element transition using Fragments?

AndroidFragmentMaterial Design

Android Problem Overview


I am trying to implement transitions between fragments which have "shared elements" as described in the new material design specs. The only method I can find is the ActivityOptionsCompat.makeSceneTransitionAnimation, which I believe works on Activity only. I've been searching for this same functionality but with/for fragments.

Android Solutions


Solution 1 - Android

I had the same problem but had it working by adding a new fragment from another fragment. The following link is very helpful in getting started on this: https://developer.android.com/training/material/animations.html#Transitions

Following is my code that works. I'm animating an ImageView from one fragment to the other. Make sure the View you want to animate has the same android:transitionName in both fragments. The other content doesn't really matter.

As a test, you could copy this to both your layout xml files. Make sure the image exists.

<ImageView
android:transitionName="MyTransition"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:src="@drawable/test_image" />

Then I have 1 file in my res/transition folder, named change_image_transform.xml.

<?xml version="1.0" encoding="utf-8"?>
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android">
    <changeImageTransform />
</transitionSet>

Now you can get started. Lets say you have Fragment A containing the image and want to add Fragment B.

Run this in Fragment A:

@Override
public void onClick(View v) {
    switch(v.getId()) {
        case R.id.product_detail_image_click_area:
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                setSharedElementReturnTransition(TransitionInflater.from(getActivity()).inflateTransition(R.transition.change_image_transform));
                setExitTransition(TransitionInflater.from(getActivity()).inflateTransition(android.R.transition.explode));

                // Create new fragment to add (Fragment B)
                Fragment fragment = new ImageFragment();
                fragment.setSharedElementEnterTransition(TransitionInflater.from(getActivity()).inflateTransition(R.transition.change_image_transform));
                fragment.setEnterTransition(TransitionInflater.from(getActivity()).inflateTransition(android.R.transition.explode));

                // Our shared element (in Fragment A)
                mProductImage   = (ImageView) mLayout.findViewById(R.id.product_detail_image);

                // Add Fragment B
                FragmentTransaction ft = getFragmentManager().beginTransaction()
                        .replace(R.id.container, fragment)
                        .addToBackStack("transaction")
                        .addSharedElement(mProductImage, "MyTransition");
                ft.commit();
            }
            else {
                // Code to run on older devices
            }
            break;
    }
}

Solution 2 - Android

The shared element fragment transitions do work with ListViews, as long as the source and target views have the same (and unique) transitionName.

If you make your list view adapter to set unique transitionNames to the views you want (e.g. some constant + specific item id) and also change your detail fragment to set the same transitionNames to the target views at runtime (onCreateView), the transitions actually work!

Solution 3 - Android

Shared elements do work with Fragments but there are some things to keep in mind:

  1. Don't try to set the sharedElementsTransition in the onCreateView of your Fragment. You have to define them when creating an instance of your Fragment or in onCreate.

  2. Take note of the official documentation on the possible animations for enter/exit transitions & sharedElementTransition. They are not the same.

  3. Trial and error :)

Solution 4 - Android

This should be a comment to the accepted answer, as I am unable to comment on it.

The accepted answer (by WindsurferOak and ar34z) works, except for a "minor" problem which caused a null pointer exception when navigating up with the backStack. It seems that setSharedElementReturnTransition() should be called on the target fragment instead of the original fragment.

So instead of:

setSharedElementReturnTransition(TransitionInflater.from(getActivity()).inflateTransition(R.transition.change_image_transform));

it should be

fragment.setSharedElementReturnTransition(TransitionInflater.from(getActivity()).inflateTransition(R.transition.change_image_transform));

https://github.com/tevjef/Rutgers-Course-Tracker/issues/8

Solution 5 - Android

Solution 6 - Android

The key is to use a custom transaction with

transaction.addSharedElement(sharedElement, "sharedImage");

Shared Element Transition Between Two Fragments

In this example, one of two different ImageViews should be translated from the ChooserFragment to the DetailFragment.

In the ChooserFragment layout we need the unique transitionName attributes:

<ImageView
    android:id="@+id/image_first"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:src="@drawable/ic_first"
    android:transitionName="fistImage" />

<ImageView
    android:id="@+id/image_second"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:src="@drawable/ic_second"
    android:transitionName="secondImage" />

In the ChooserFragments class, we need to pass the View which was clicked and an ID to the parent Activity wich is handling the replacement of the fragments (we need the ID to know which image resource to show in the DetailFragment). How to pass information to a parent activity in detail is surely covered in another documentation.

view.findViewById(R.id.image_first).setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        if (mCallback != null) {
            mCallback.showDetailFragment(view, 1);
        }
    }
});

view.findViewById(R.id.image_second).setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        if (mCallback != null) {
            mCallback.showDetailFragment(view, 2);
        }
     }
});

In the DetailFragment, the ImageView of the shared element also needs the unique transitionName attribute.

<ImageView
    android:id="@+id/image_shared"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center"
    android:transitionName="sharedImage" />

In the onCreateView() method of the DetailFragment, we have to decide which image resource should be shown (if we don't do that, the shared element will disappear after the transition).

public static DetailFragment newInstance(Bundle args) {
    DetailFragment fragment = new DetailFragment();
    fragment.setArguments(args);
    return fragment;
}

@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    super.onCreateView(inflater, container, savedInstanceState);
    View view = inflater.inflate(R.layout.fragment_detail, container, false);

    ImageView sharedImage = (ImageView) view.findViewById(R.id.image_shared);

    // Check which resource should be shown.
    int type = getArguments().getInt("type");

    // Show image based on the type.
    switch (type) {
        case 1:
            sharedImage.setBackgroundResource(R.drawable.ic_first);
            break;

        case 2:
            sharedImage.setBackgroundResource(R.drawable.ic_second);
            break;
    }

    return view;
}

The parent Activity is receiving the callbacks and handles the replacement of the fragments.

@Override
public void showDetailFragment(View sharedElement, int type) {
    // Get the chooser fragment, which is shown in the moment.
    Fragment chooserFragment = getFragmentManager().findFragmentById(R.id.fragment_container);

    // Set up the DetailFragment and put the type as argument.
    Bundle args = new Bundle();
    args.putInt("type", type);
    Fragment fragment = DetailFragment.newInstance(args);

    // Set up the transaction.
    FragmentTransaction transaction = getFragmentManager().beginTransaction();

    // Define the shared element transition.
    fragment.setSharedElementEnterTransition(new DetailsTransition());
    fragment.setSharedElementReturnTransition(new DetailsTransition());

    // The rest of the views are just fading in/out.
    fragment.setEnterTransition(new Fade());
    chooserFragment.setExitTransition(new Fade());

    // Now use the image's view and the target transitionName to define the shared element.
    transaction.addSharedElement(sharedElement, "sharedImage");

    // Replace the fragment.
    transaction.replace(R.id.fragment_container, fragment, fragment.getClass().getSimpleName());

    // Enable back navigation with shared element transitions.
    transaction.addToBackStack(fragment.getClass().getSimpleName());

    // Finally press play.
    transaction.commit();
}

Not to forget - the Transition itself. This example moves and scales the shared element.

@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public class DetailsTransition extends TransitionSet {

    public DetailsTransition() {
        setOrdering(ORDERING_TOGETHER);
        addTransition(new ChangeBounds()).
            addTransition(new ChangeTransform()).
            addTransition(new ChangeImageTransform());
    }

}

Solution 7 - Android

I searched for SharedElement in fragments and I find very useful source code on GitHub.

1.first you should define transitionName for your Objects(Like ImageView) in both Fragments layout(We add a button in fragment A for handling click event):

fragment A:

  <ImageView
    android:id="@+id/fragment_a_imageView"
    android:layout_width="128dp"
    android:layout_height="96dp"
    android:layout_alignParentBottom="true"
    android:layout_centerHorizontal="true"
    android:layout_marginBottom="80dp"
    android:scaleType="centerCrop"
    android:src="@drawable/gorilla"
    android:transitionName="@string/simple_fragment_transition />

<Button
    android:id="@+id/fragment_a_btn"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_alignParentBottom="true"
    android:layout_centerHorizontal="true"
    android:layout_marginBottom="24dp"
    android:text="@string/gorilla" />

fragment B:

    <ImageView
    android:id="@+id/fragment_b_image"
    android:layout_width="match_parent"
    android:layout_height="250dp"
    android:scaleType="centerCrop"
    android:src="@drawable/gorilla"
    android:transitionName="@string/simple_fragment_transition" />

2. Then you should write this code in your transition file in transition Directory(if you haven't this Directory so create One: res > new > Android Resource Directory > Resource Type = transition > name = change_image_transform ):

change_image_transform.xml:

 <?xml version="1.0" encoding="utf-8"?>
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android">
  <changeBounds/>
  <changeTransform/>
  <changeClipBounds/>
  <changeImageTransform/>
</transitionSet>

3. In the last step you should complete codes in java:

fragment A:

public class FragmentA extends Fragment {

    public static final String TAG = FragmentA.class.getSimpleName();


    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_a, container, false);
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        final ImageView imageView = (ImageView) view.findViewById(R.id.fragment_a_imageView);
        Button button = (Button) view.findViewById(R.id.fragment_a_btn);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                getFragmentManager()
                        .beginTransaction()
                        .addSharedElement(imageView, ViewCompat.getTransitionName(imageView))
                        .addToBackStack(TAG)
                        .replace(R.id.content, new FragmentB())
                        .commit();
            }
        });
    }
}

fragment B:

public class FragmentB extends Fragment {

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
            setSharedElementEnterTransition(TransitionInflater.from(getContext()).inflateTransition(android.R.transition.move));
        
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_b, container, false);
    }
}

> don't forget to show your "A" fragment in your activity:

 @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        getSupportFragmentManager()
                .beginTransaction()
                .add(R.id.content, new SimpleFragmentA())
                .commit();
    }

source : https://github.com/mikescamell/shared-element-transitions

Solution 8 - Android

How to start shared element transition using Fragments?

I assume you want to transition of your Image using Fragment (instead of Activity)

  • it wont work perfectly if you have already set AppTheme

  • keep the transition name of source and destination same

You have to do three things for transition:

1.Set transitionName to the source View(xml or programatically) -> before calling makeFragmentTransition

private void setImageZoom(boolean isImageZoom) {
    ImageView imageView = this.findViewById(R.id.image);

    if (isImageZoom) {
        imageView.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                ViewCompat.setTransitionName(imageView, "imageTransition");
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                    makeFragmentTransition(imageView);
            }
            }
        });
    }
}

2.Fragment Transition

  • Set TransitionSet for the specicific Transition animation

  • apply them on Fragment

  • call addSharedElement(View, transitionName) while fragmentTransition

    @RequiresApi(Build.VERSION_CODES.LOLLIPOP) public void makeFragmentTransition(ImageView sourceTransitionView) { //transtionName for sourceView
    //MUST set transitionName before calling this method(programattically or give ->transitionName to the view in xml) String sourceTransitionName = ViewCompat.getTransitionName(sourceTransitionView); TransitionSet transitionSet = new TransitionSet(); transitionSet.setDuration(500); transitionSet.addTransition(new ChangeBounds()); //to expand boundaries transitionSet.addTransition(new ChangeTransform()); //for transtion vertically transitionSet.addTransition(new ChangeImageTransform()); // image transform work transitionSet.setOrdering(TransitionSet.ORDERING_TOGETHER);

      ImageTransitionFragment fragment = new ImageTransitionFragment();
      fragment.setSharedElementEnterTransition(transitionSet);
      fragment.setSharedElementReturnTransition(transitionSet);
      fragment.setAllowReturnTransitionOverlap(false);
    
      try {
    
          getHostActivity().getSupportFragmentManager()
                  .beginTransaction()
                  //sharedElement is set here for fragment 
                  //it will throw exception if transitionName is not same for source and destionationView
                  .addSharedElement(sourceTransitionView, sourceTransitionName)
                  //R.id.fragmentView is the View in activity on which fragment will load...
                  .replace(R.id.fragmentView, fragment)
                  .addToBackStack(null)
                  .commit();
      } catch (Exception e) {
          //
          String string = e.toString();
      }
    

    }

3.set desitionNation transitionName in ImageView

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/destionationTransitionPage"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:transitionName="@string/pageTransition"
android:background="@color/black_color">


<com.android.foundation.ui.component.FNImageView
    android:id="@+id/destinationImageView"
    android:layout_width="@dimen/_400dp"
    android:layout_gravity="center"

    android:transitionName="imageTransition"
    android:layout_height="@dimen/_400dp" />
</FrameLayout>

Please respond if anything is not clear or it need more improvement

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionunchosenView Question on Stackoverflow
Solution 1 - Androidar34zView Answer on Stackoverflow
Solution 2 - AndroidDimitris KazakosView Answer on Stackoverflow
Solution 3 - AndroidJordyView Answer on Stackoverflow
Solution 4 - AndroidPaul LView Answer on Stackoverflow
Solution 5 - AndroidLym ZoyView Answer on Stackoverflow
Solution 6 - AndroidmboView Answer on Stackoverflow
Solution 7 - AndroidYasin HajilouView Answer on Stackoverflow
Solution 8 - AndroidBeatle RefractorView Answer on Stackoverflow