androidandroid-fragmentsandroid-recyclerviewandroid-cardviewandroid-transitions

Shared element back transition not working with recyclerview and cardviews in fragments


I'm trying to create a transition between a recycler view with cardviews fragment to a fragment that only contains 1 card. The problem is that the back transition is not working, while the enter transition is. If I remove setReorderingAllowed(true); then the back transition is working, but the enter transition stops working. This is what I have.

Fragment with recyclerview with cardviews

public class OrdersFragment extends Fragment implements OrderAdapter.OnOrderListener {

    private OrderAdapter mAdapter;

    private TextView bonnenTextView;

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        postponeEnterTransition();
    }

    public View onCreateView(@NonNull LayoutInflater inflater,
                             ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_bonnen, container, false);
        bonnenTextView = view.findViewById(R.id.bonnen_text_view);
        RecyclerView orderRecyclerView = view.findViewById(R.id.order_recycler_view);
        RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(getContext());
        orderRecyclerView.setLayoutManager(layoutManager);
        if (mAdapter == null) {
            mAdapter = new OrderAdapter(this);
            fetchOrders();
        }
        orderRecyclerView.setAdapter(mAdapter);
        return view;
    }

    private void fetchOrders() {
        new OrderFetcher().fetch(new Callback() {
            @Override
            public void onComplete(Result result) {
                if (result instanceof Result.Success) {
                    mAdapter.setOrders((Order[]) ((Result.Success<?>)result).data);
                    mAdapter.notifyDataSetChanged();
                } else {
                    Toast.makeText(getContext(), "Could not load orders", Toast.LENGTH_SHORT).show();
                }
                startPostponedEnterTransition();
            }
        });
    }

    @Override
    public void onOrderClick(int position, View view, Order order) {
        Log.i("OrderClick", "Transition name " + view.getTransitionName());

        View carrierTextView = view.findViewById(R.id.carrier_text_view);
        View numberTextView = view.findViewById(R.id.id_text_view);
        View pickerTextView = view.findViewById(R.id.picker_text_view);
        View locationTextView = view.findViewById(R.id.location_text_view);

        FragmentTransaction transaction = getParentFragmentManager().beginTransaction();
        transaction.addSharedElement(view, view.getTransitionName());
        transaction.addSharedElement(carrierTextView, carrierTextView.getTransitionName());
        transaction.addSharedElement(numberTextView, numberTextView.getTransitionName());
        transaction.addSharedElement(pickerTextView, pickerTextView.getTransitionName());
        transaction.addSharedElement(locationTextView, locationTextView.getTransitionName());
        transaction.addSharedElement(bonnenTextView, bonnenTextView.getTransitionName());
        transaction.replace(R.id.nav_host_fragment, BonFragment.newInstance(view.getTransitionName(), order));
        transaction.addToBackStack(null);
        transaction.setReorderingAllowed(true);
        transaction.commit();
    }
}

xml that goes with fragment above

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".ui.orders.OrdersFragment">

    <TextView
        android:id="@+id/bonnen_text_view"
        android:transitionName="bonnen_text_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="10dp"
        android:layout_marginTop="10dp"
        android:text="@string/orders"
        android:textColor="@color/secondary"
        android:textSize="40sp"
        android:textStyle="bold"
        android:typeface="normal" />

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/order_recycler_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_margin="20dp"/>
</LinearLayout>

Fragment with single cardview

public static BonFragment newInstance(String cardTransitionName, Order order) {
        BonFragment bonFragment = new BonFragment();

        Bundle args = new Bundle();
        args.putParcelable("orderParcel", order);
        args.putString("cardTransitionName", cardTransitionName);
        bonFragment.setArguments(args);

        return bonFragment;
    }

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

        CardView orderCard = root.findViewById(R.id.order_card);
        TextView carrierTextView = root.findViewById(R.id.carrier_text_view);
        TextView numberTextView = root.findViewById(R.id.id_text_view);
        TextView pickerTextView = root.findViewById(R.id.picker_text_view);
        TextView locationTextView = root.findViewById(R.id.location_text_view);

        if (getArguments() != null && getArguments().getParcelable("orderParcel") != null) {
            Order order = getArguments().getParcelable("orderParcel");
            orderCard.setTransitionName(getArguments().getString("cardTransitionName"));
            if (order != null) {
                carrierTextView.setTransitionName("carrier" + order.getIndex());
                carrierTextView.setText(order.getCarrier());
                numberTextView.setTransitionName("number" + order.getIndex());
                numberTextView.setText(String.valueOf(order.getNumber()));
                pickerTextView.setTransitionName("picker" + order.getIndex());
                pickerTextView.setText(order.getPicker());
                locationTextView.setTransitionName("location" + order.getIndex());
                locationTextView.setText(order.getPosition());

                carrierTextView.setText("Lorem Ipsum");
                numberTextView.setText("Dolor sit amet");
                pickerTextView.setText("consectetur adipiscing elit");
                locationTextView.setText("Mauris semper");

            }
        }

        orderCard.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                getParentFragmentManager().popBackStack();
            }
        });

        Transition transition = TransitionInflater.from(getContext()).inflateTransition(R.transition.card_transition);
        setSharedElementEnterTransition(transition);
        setSharedElementReturnTransition(transition);
        return root;
    }

xml that goes with fragment above

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".ui.orders.OrdersFragment">

    <TextView
        android:transitionName="bonnen_text_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="10dp"
        android:layout_marginTop="10dp"
        android:text="@string/orders"
        android:textColor="@color/secondary"
        android:textSize="40sp"
        android:textStyle="bold"
        android:typeface="normal" />

    <androidx.cardview.widget.CardView
        android:id="@+id/order_card"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:cardBackgroundColor="@color/primaryLight"
        android:foreground="?android:attr/selectableItemBackground"
        android:clickable="true"
        android:layout_margin="25dp">

        <LinearLayout
            android:transitionName="order_card_layout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            android:layout_margin="10dp">
            <TextView
                android:id="@+id/carrier_text_view"
                android:transitionName="carrier_text_view"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:textColor="@color/secondary"
                android:textSize="20sp"
                android:textStyle="bold"/>
            <TextView
                android:id="@+id/id_text_view"
                android:transitionName="id_text_view"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:textColor="@color/secondary" />
            <TextView
                android:id="@+id/picker_text_view"
                android:transitionName="picker_text_view"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:textColor="@color/secondary" />
            <TextView
                android:id="@+id/location_text_view"
                android:transitionName="location_text_view"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:textColor="@color/secondary" />
        </LinearLayout>
    </androidx.cardview.widget.CardView>
</LinearLayout>

This is what it looks like with setReorderingAllowed(true);

This is what it looks like with setReorderingAllowed(true);

This is what it looks like without setReorderingAllowed(true);

This is what it looks like without setReorderingAllowed(true);

EDIT After applying the answer given by ianhanniballake I got it working. Here is what I did for future reference. The only changes I made is in the OrdersFragment class. I removed the override of the onCreate() method. I removed the startPostponedEnterTransition(); from my fetchOrders() method and I added

@Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        postponeEnterTransition();
        final ViewGroup parentView = (ViewGroup) view.getParent();
        parentView.getViewTreeObserver()
                .addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
                    @Override
                    public boolean onPreDraw() {
                        parentView.getViewTreeObserver().removeOnPreDrawListener(this);
                        startPostponedEnterTransition();
                        return true;
                    }
                });
        super.onViewCreated(view, savedInstanceState);
    }

That's it


Solution

  • As per the Use shared element transitions with a RecyclerView guide, you're calling startPostponedEnterTransition() too early - after setting your data (and calling notifyDataSetChanged() to inform the adapter), you need to wait until the RecyclerView is actually measured and laid out. This requires that you add an OnPreDrawListener and only call startPostponedEnterTransition() once that fires.