sharedElment transition using fragments not transitioning
Asked Answered
S

2

6
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,

Sack answered 22/12, 2016 at 17:59 Comment(0)
L
12

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

Lovel answered 26/12, 2016 at 10:24 Comment(6)
Thanks this as helped.Sack
After making all the corrections. The transition look great entering the detailFragment. But when the back button is press the transition doesn't transition. I thought this happened automatically when fragment are popped from the backstack. Is this correct?Sack
Yes, you probably need to add return transition to detailMovieFragment, like this: detailMovieFragment.setSharedElementReturnTransition(changeTransition);. Guess it should resolve your problem.Lovel
That didn't actually work. However, thanks for your help.Sack
Hm, one thing - what if you will remove your onBackPressed method implementation from MainActivity? But leave the return transition.Lovel
I have tested with the onBackPressed and without it and the result is the same. I have a git animation on this new post here where you can see what is going on: #41356256Sack
O
3

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();
                }
            }
        }
Officialdom answered 27/12, 2016 at 6:15 Comment(1)
Thanks this has helpedSack

© 2022 - 2024 — McMap. All rights reserved.