Saturday, 16 January 2021

MaterialContainerTransform transition is not Working on Return

My MaterialContainerTransform transition is working from source -> destination, but not the other way around. My situation is pretty standard -- I am attempting to provide a simple transition from a RecyclerView item (source Fragment) to a "details" Fragment (destination Fragment). The items within the RecyclerView are MaterialCardViews, each with an ImageView which is shared in the destination Fragment.

Following these docs https://material.io/develop/android/theming/motion it seems to be fairly straightforward, though the docs are written in Kotlin and I am using Java so maybe I am missing something? I dynamically set the ImageView transitionName in each RecyclerView item, and pass that to the destination Fragment which then copies the transitionName to its own ImageView. Through logging, I can confirm the shared transitionName matches in each fragment.

The transition from source -> destination works great, but when I hit the back button there is no transition back. This is strange because even the docs state:

Fragments are able to define enter and return shared element transitions. When only an enter shared element transition is set, it will be re-used when the Fragment is popped (returns).

Any ideas why the return transition (destination -> source) is not functioning? Here is the relevant code:

RecyclerView Item (Source) **the ImageView is the shared element

<com.google.android.material.card.MaterialCardView
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/result_layout_card"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:cardCornerRadius="5dp"
    app:cardElevation="4dp"
    android:layout_margin="8dp"
    android:clickable="true"
    android:focusable="true"
    android:checkable="true"
    app:checkedIconTint="@color/checkedYellow"
    >

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:layout_gravity="center">


        <ImageView
            android:id="@+id/result_image_thumbnail"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:adjustViewBounds="true"
            android:scaleType="fitCenter"
            android:contentDescription="@string/thumbnail_content_description"
            android:src="@drawable/corolla_preview"
            />

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="5dp"
            android:orientation="vertical">

            <TextView
                android:id="@+id/result_text_title"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="2018 Toyota Corolla"
                style="@style/TextAppearance.MaterialComponents.Subtitle1"
                />

            <TextView
                android:id="@+id/result_text_stock"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="#626546"
                style="@style/TextAppearance.MaterialComponents.Body2"
                />

            <TextView
                android:id="@+id/result_text_price"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="$28,998"
                style="@style/TextAppearance.MaterialComponents.Overline"
                />

        </LinearLayout>

    </LinearLayout>

</com.google.android.material.card.MaterialCardView>

Detail Fragment (destination) ** again the ImageView is the shared element

<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_height="match_parent"
    android:layout_width="match_parent"
    >

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:padding="8dp">

        <ImageView
            android:id="@+id/detail_image_thumbnail"
            android:layout_width="match_parent"
            android:layout_height="225dp"
            android:contentDescription="@string/thumbnail_content_description"/>
        
    </LinearLayout>
</ScrollView>

RecyclerView Holder (source) ** the viewResults method is where I start the fragment transaction

public class ResultsHolder extends RecyclerView.ViewHolder {

    private static final String TAG = ResultsHolder.class.getSimpleName();
    private final FragmentManager fragmentManager;
    private ResultModel resultModel;

    private final MaterialCardView cardView;
    private final ImageView thumbnail;
    private String searchId;

    private ResultsFragment resultsFragment;
    private int position;
    private View thumbnailView;

    public ResultsHolder(View itemView, FragmentManager fragmentManager,
                         String searchId, ResultsFragment resultsFragment) {
        super(itemView);

        cardView = itemView.findViewById(R.id.result_layout_card);
        thumbnail = itemView.findViewById(R.id.result_image_thumbnail);

        this.fragmentManager = fragmentManager;
        this.searchId = searchId;

        this.resultsFragment = resultsFragment;

    }

    public void bindResult(ResultModel result, int position) {
        resultModel = result;

        Picasso.get().load(result.getImageUrl()).into(thumbnail);

        cardView.setChecked(result.isChecked());
        cardView.setOnClickListener(cardViewClickListener);

        this.position = position;
        thumbnail.setTransitionName(result.getVin()); // transition name is unique for each recyclerview 
                                                      // item

    }

    private final View.OnClickListener cardViewClickListener = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            viewDetails(resultModel, searchId);
        }
    };


    // This is the method where I start the destination fragment
    public void viewDetails(ResultModel result, String searchId) {
        Bundle args = new Bundle();
        args.putParcelable("RESULT", Parcels.wrap(result));
        args.putString("SEARCH_ID", searchId);

        DetailFragment fragment = new DetailFragment();

        // Destination fragment enter transition!
        fragment.setSharedElementEnterTransition(new MaterialContainerTransform());
        fragment.setArguments(args);

        fragmentManager
                .beginTransaction()
                .setReorderingAllowed(true)
                .addSharedElement(thumbnail, thumbnail.getTransitionName()) // Shared element!
                .replace(R.id.main_fragment_container,
                        fragment,
                        DetailFragment.class.getSimpleName())
                .addToBackStack(null)
                .commit();
    }

Details Fragment (destination)

public class DetailFragment extends Fragment {

    @SuppressWarnings("unused")
    private static final String TAG = "DetailFragment";
    private ResultModel result;
    private String searchId;

    ImageView image;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (getArguments() != null) {

            // Get result and search ID
            result = Parcels.unwrap(getArguments().getParcelable("RESULT"));
            searchId = getArguments().getString("SEARCH_ID");

        }

    }


    private void instantiateUI(View v) {
        
        TextView vin = v.findViewById(R.id.tv_details_vin);
        vin.setText(result.getVin());
        
        image = v.findViewById(R.id.detail_image_thumbnail);
        Picasso.get().load(result.getImageUrl()).fit().into(image);

    }

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

        // Set transitionName exactly same as the recyclerview item which was clicked
        v.findViewById(R.id.detail_image_thumbnail).setTransitionName(result.getVin());
        return v;
    }

    @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        if (((AppCompatActivity) getActivity()) != null
                && ((AppCompatActivity) getActivity()).getSupportActionBar() != null) {

            Objects.requireNonNull(((AppCompatActivity) getActivity())
                    .getSupportActionBar()).setTitle("Details");
        }

        instantiateUI(view);
    }
}


from MaterialContainerTransform transition is not Working on Return

No comments:

Post a Comment