sharedElment transition using fragments not transitioning

1.4k views Asked by At
AndroidStudio 2.3 Beta 1

I am trying to get transitions work with fragments, how the image I am using doesn't transition. It just pop up as normal.

I have created a simple App to try and get this to work. I have 2 fragments ListMovieFragment and DetailMovieFragment. And 1 MainActivity.

The user will click the image in ListMovieFragment to transition to DetailMovieFragment.

This is my Transition xml change_image_transform:

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

ListMovieFragment:

public class ListMovieFragment extends Fragment {
    public interface MovieSelectedListener {
        void onMovieSelected(int movieId);
    }
    private MovieSelectedListener mMovieSelectedListener;

    public ListMovieFragment() {
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        mMovieSelectedListener = (MovieSelectedListener)context;
    }

    @Override
    public void onDetach() {
        super.onDetach();
        mMovieSelectedListener = null;
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        final View view = inflater.inflate(R.layout.fragment_list, container, false);
        final ImageView ivMoviePoster = (ImageView)view.findViewById(R.id.ivMoviePoster);

        ivMoviePoster.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if(mMovieSelectedListener != null) {
                    mMovieSelectedListener.onMovieSelected(12345);
                }
            }
        });

        Glide.with(getActivity())
                .load("https://image.tmdb.org/t/p/w185/qjiskwlV1qQzRCjpV0cL9pEMF9a.jpg")
                .placeholder(R.drawable.placeholder_poster)
                .centerCrop()
                .crossFade()
                .into(ivMoviePoster);

        return view;
    }
}

DetailMovieFragment

public class DetailMovieFragment extends Fragment {
    public DetailMovieFragment() {
        // Required empty public constructor
    }

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

        final ImageView ivMovieDetailPoster = (ImageView)view.findViewById(R.id.ivMovieDetailPoster);

        Glide.with(getActivity())
                .load("https://image.tmdb.org/t/p/w185/qjiskwlV1qQzRCjpV0cL9pEMF9a.jpg")
                .placeholder(R.drawable.placeholder_poster)
                .centerCrop()
                .crossFade()
                .into(ivMovieDetailPoster);

        return view;
    }
}

MainActivity

public class MainActivity extends AppCompatActivity implements ListMovieFragment.MovieSelectedListener {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        if(savedInstanceState == null) {
            FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
            fragmentTransaction.add(R.id.activity_main, new ListMovieFragment(), "listmoviefragment");
            fragmentTransaction.commit();
        }
    }

    @Override
    public void onMovieSelected(int movieId) {
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            /* Get the fragments that will be using the transition */
            ListMovieFragment listMovieFragment = new ListMovieFragment();
            DetailMovieFragment detailMovieFragment = new DetailMovieFragment();

            /* Inflate the transition */
            Transition changeTransition = TransitionInflater
                    .from(MainActivity.this)
                    .inflateTransition(R.transition.change_image_transform);

            Transition explodeTransition = TransitionInflater
                    .from(MainActivity.this)
                    .inflateTransition(android.R.transition.explode);

            /* Set the exit and return on the source fragment (ListMovieFragment) */
            listMovieFragment.setSharedElementReturnTransition(changeTransition);
            listMovieFragment.setExitTransition(explodeTransition);

            /* Set the enter on the destination fragment (MovieDetailFragment) */
            detailMovieFragment.setSharedElementEnterTransition(changeTransition);
            detailMovieFragment.setEnterTransition(explodeTransition);

            /* Get the shared imageview from the source fragment (MovieListFragment) */
            final ImageView ivSharedImage = (ImageView)findViewById(R.id.ivMoviePoster);

            FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
            fragmentTransaction.replace(R.id.activity_main, new DetailMovieFragment(), "detailmoviefragment");
            fragmentTransaction.addToBackStack("detailmoviefragment");
            fragmentTransaction.addSharedElement(ivSharedImage, getResources().getString(R.string.transition_poster_image));
            fragmentTransaction.commit();
        }
        else {
            FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
            fragmentTransaction.replace(R.id.activity_main, new DetailMovieFragment(), "detailmoviefragment");
            fragmentTransaction.addToBackStack("detailmoviefragment");
            fragmentTransaction.commit();
        }
    }

    @Override
    public void onBackPressed() {
        if(getSupportFragmentManager().getBackStackEntryCount() > 0) {
            getSupportFragmentManager().popBackStackImmediate();
        }
        else {
            super.onBackPressed();
        }
    }
}

The layout for ListMovieFragment

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:paddingBottom="6dp">

    <ImageView
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/ivMoviePoster"
        android:layout_width="match_parent"
        android:layout_height="300dp"
        android:src="@drawable/placeholder_poster"
        android:scaleType="fitXY"
        android:adjustViewBounds="true"
        android:transitionName="@string/transition_poster_image">
    </ImageView>
</LinearLayout>

Layout for DetailMovieFragment:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="me.androidbox.fragmenttransitions.detail.DetailMovieFragment">
    <ImageView
        android:id="@+id/ivMovieDetailPoster"
        android:layout_width="140dp"
        android:layout_height="160dp"
        android:layout_marginEnd="16dp"
        android:layout_marginTop="112dp"
        android:adjustViewBounds="true"
        android:scaleType="fitXY"
        android:layout_gravity="end"
        android:transitionName="@string/transition_poster_image"/>
</FrameLayout>

The string name for the transition name:

<string name="transition_poster_image">imagePoster</string>

The implementation seems simple so I think my mistake is something i'm overlooking.

Many thanks for any suggestions,

2

There are 2 answers

6
romtsn On BEST ANSWER

You have two problems in your code, inside your onMovieSelected method:

  1. You're creating a new instance of ListMovieFragment and then apply transition logic to it. But you forgot that you already have the instance of this fragment (you were created it in onCreate method). So you need to retrieve the existing ListMovieFragment object from FragmentManager, and apply your transitions to it.
  2. You're applying transition logic to one instance of DetailMovieFragment, but then suddenly replacing the ListMoveFragment with the new one.

So your fixed onMovieSelected method will be:

@Override
public void onMovieSelected(int movieId) {
    if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        /* Get the fragments that will be using the transition */
        ListMovieFragment listMovieFragment = (ListMovieFragment) getSupportFragmentManager().findFragmentByTag("listmoviefragment");
        DetailMovieFragment detailMovieFragment = new DetailMovieFragment();

        /* Inflate the transition */
        Transition changeTransition = TransitionInflater
                .from(MainActivity.this)
                .inflateTransition(R.transition.change_image_transform);

        Transition explodeTransition = TransitionInflater
                .from(MainActivity.this)
                .inflateTransition(android.R.transition.explode);

        /* Set the exit and return on the source fragment (ListMovieFragment) */
        listMovieFragment.setSharedElementReturnTransition(changeTransition);
        listMovieFragment.setExitTransition(explodeTransition);

        /* Set the enter on the destination fragment (MovieDetailFragment) */
        detailMovieFragment.setSharedElementEnterTransition(changeTransition);
        detailMovieFragment.setEnterTransition(explodeTransition);

        /* Get the shared imageview from the source fragment (MovieListFragment) */
        final ImageView ivSharedImage = (ImageView)findViewById(R.id.ivMoviePoster);

        FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
        fragmentTransaction.replace(R.id.activity_main, detailMovieFragment, "detailmoviefragment");
        fragmentTransaction.addToBackStack("detailmoviefragment");
        fragmentTransaction.addSharedElement(ivSharedImage, getResources().getString(R.string.transition_poster_image));
        fragmentTransaction.commit();
    } else {
        FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
        fragmentTransaction.replace(R.id.activity_main, new DetailMovieFragment(), "detailmoviefragment");
        fragmentTransaction.addToBackStack("detailmoviefragment");
        fragmentTransaction.commit();
    }
}

Now this should work.

P.S. I've used your code and run it, and noticed you have some strange transitions, so if you will have some issues, check this great article, how to make smooth and user-friendly transitions with fragments:)

1
Jinesh Francis On

Don't create new object Fragment while adding to fragment transaction,reuse the existing fragment in which you applied the enter and exit transitions.

      public class MainActivity extends AppCompatActivity implements ListMovieFragment.MovieSelectedListener {

            @Override
            protected void onCreate(Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
                setContentView(R.layout.activity_main);
                if(savedInstanceState == null) {
                    if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                        ListMovieFragment listMovieFragment = new ListMovieFragment();
                        Transition changeTransition = TransitionInflater
                                .from(MainActivity.this)
                                .inflateTransition(R.transition.change_image_transform);

                        Transition explodeTransition = TransitionInflater
                                .from(MainActivity.this)
                                .inflateTransition(android.R.transition.explode);
                        listMovieFragment.setSharedElementReturnTransition(changeTransition);
                        listMovieFragment.setExitTransition(explodeTransition);
                        FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
                        //don't create new  ListMovieFragment use existing listMovieFragment instance
                        fragmentTransaction.add(R.id.activity_main, listMovieFragment, "listmoviefragment");
                        fragmentTransaction.commit();
                    }else {
                        FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
                        fragmentTransaction.add(R.id.activity_main, new ListMovieFragment(), "listmoviefragment");
                        fragmentTransaction.commit();
                    }
                }
            }

            @Override
            public void onMovieSelected(int movieId) {
                if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                    /* Get the fragments that will be using the transition */
                    ListMovieFragment listMovieFragment = new ListMovieFragment();
                    DetailMovieFragment detailMovieFragment = new DetailMovieFragment();

                    /* Inflate the transition */
                    Transition changeTransition = TransitionInflater
                            .from(MainActivity.this)
                            .inflateTransition(R.transition.change_image_transform);

                    Transition explodeTransition = TransitionInflater
                            .from(MainActivity.this)
                            .inflateTransition(android.R.transition.explode);

                    /* Set the exit and return on the source fragment (ListMovieFragment) */
                    listMovieFragment.setSharedElementReturnTransition(changeTransition);
                    listMovieFragment.setExitTransition(explodeTransition);

                    /* Set the enter on the destination fragment (MovieDetailFragment) */
                    detailMovieFragment.setSharedElementEnterTransition(changeTransition);
                    detailMovieFragment.setEnterTransition(explodeTransition);

                    /* Get the shared imageview from the source fragment (MovieListFragment) */
                    final ImageView ivSharedImage = (ImageView)findViewById(R.id.ivMoviePoster);

                    FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
                    //don't create new  DetailMovieFragment use existing detailMovieFragment instance
                    fragmentTransaction.replace(R.id.activity_main, detailMovieFragment, "detailmoviefragment");
                    fragmentTransaction.addToBackStack("detailmoviefragment");
                    fragmentTransaction.addSharedElement(ivSharedImage, getResources().getString(R.string.transition_poster_image));
                    fragmentTransaction.commit();
                }
                else {
                    FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
                    fragmentTransaction.replace(R.id.activity_main, new DetailMovieFragment(), "detailmoviefragment");
                    fragmentTransaction.addToBackStack("detailmoviefragment");
                    fragmentTransaction.commit();
                }
            }
            @Override
            public void onBackPressed() {
                if(getSupportFragmentManager().getBackStackEntryCount() > 0) {
                    getSupportFragmentManager().popBackStackImmediate();
                }
                else {
                    super.onBackPressed();
                }
            }
        }