How to implement Material Design parent-child transition with recyclerview
Asked Answered
P

1

11

I'm trying to implement this

enter image description here

like others before me.


What I find so far:

  1. How to implement the "parent-to-child" navigational transition as prescribed by Material Design
  2. Material Design parent-child navigational transition recyclerview entry to detail fragment
  3. How to create Parent-child transition in which list item lifts out and become details screen
  4. Custom fragment transition parent-to-child navigation setExitTransition not showing
  5. https://medium.com/@bherbst/fragment-transitions-with-shared-elements-7c7d71d31cbb
  6. https://medium.com/@jim.zack.hu/android-inbox-material-transitions-for-recyclerview-71fc7326bcb5
  7. https://github.com/saket/InboxRecyclerView
  8. https://www.youtube.com/watch?v=EjTJIDKT72M

What I've so far:

enter image description here


Parent Fragment

@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    initViewModel();
    setupRecyclerView();
    initViewModelSubscriptions();
}

@Override
public void onClick(View v, CustomNavigator.Extras extras) {
    Bundle bundle = new Bundle();
    bundle.putString("transitionName", v.getTransitionName());

    NavController navController = NavHostFragment.findNavController(this);
    navController.navigate(R.id.action_devicemgmt_to_deviceMgmtEditFragment,
            bundle, // Bundle of args
            null, // NavOptions
            extras);
}

Detail Fragment

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Transition t = TransitionInflater.from(getContext()).inflateTransition(R.transition.test).setDuration(1000);
    setSharedElementEnterTransition(t);
}

@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    setupToolbar();
    setTransitionName();
}

private void setTransitionName() {
    binding.coord.setTransitionName(getArguments().getString("transitionName"));
}

R.transition.test

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

ViewHolder of RecyclerView Adapter

static class BindingHolder extends RecyclerView.ViewHolder {

    private final ItemDevicemgmtBinding binding;

    BindingHolder(ItemDevicemgmtBinding binding) {
        super(binding.getRoot());
        this.binding = binding;

        binding.getRoot().setOnClickListener(v -> {
            CustomNavigator.Extras extras = new CustomNavigator.Extras.Builder()
                    .addSharedElement(v, v.getTransitionName())
                    .build();

            callback.onClick(v, extras);
        });
    }

    void bind(String deviceSn) {
        binding.getRoot().setTransitionName(String.valueOf(getItemId()));
        binding.setVariable(BR.deviceSn, deviceSn);
        binding.executePendingBindings();
    }
}

Parent Fragment Layout

<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recycler_mgmt_devices"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

    </androidx.recyclerview.widget.RecyclerView>

    <include
        android:id="@+id/layout_mgmt_no_devices"
        layout="@layout/layout_mgmt_no_devices"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginBottom="@dimen/bottomappbar_height"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

Detail Fragment Layout

<androidx.coordinatorlayout.widget.CoordinatorLayout
    android:id="@+id/coord"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.google.android.material.appbar.AppBarLayout
        android:id="@+id/appbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <androidx.appcompat.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            app:title="New Item" />

    </com.google.android.material.appbar.AppBarLayout>

    <androidx.core.widget.NestedScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:fillViewport="true"
        app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior">

        <androidx.constraintlayout.widget.ConstraintLayout
            android:id="@+id/const_container"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@color/design_default_color_primary">

            <EditText
                android:id="@+id/editText2"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_marginStart="8dp"
                android:layout_marginTop="16dp"
                android:layout_marginEnd="8dp"
                android:ems="10"
                android:inputType="textPersonName"
                android:text="Serial Number"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent" />

        </androidx.constraintlayout.widget.ConstraintLayout>

    </androidx.core.widget.NestedScrollView>

</androidx.coordinatorlayout.widget.CoordinatorLayout>

RecyclerView Item layout

<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="48dp"
    android:clickable="true"
    android:focusable="true"
    android:paddingStart="@dimen/single_line_item_padding_start"
    android:paddingTop="@dimen/single_line_item_padding_top"
    android:paddingEnd="@dimen/single_line_item_padding_end"
    android:paddingBottom="@dimen/single_line_item_padding_bottom">

    <TextView
        android:id="@+id/text_devicemgmtitem_sn"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginEnd="8dp"
        android:text="@{deviceSn}"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@+id/imagebutton_devicemgmtitem_more"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        tools:text="89745897696978456790456456" />

</androidx.constraintlayout.widget.ConstraintLayout>

As you can see, the sharedEnterTransition is working but the reverse transition when leaving the Fragment is not what I have expected. Instead of fragments container the item container is transformed. Nick Butcher is talking about this transition in the video I linked in point 8 but unfortunately he only explains the collapse on scroll function. When I understood him right...

the idea..the previous content is still there..the new screen is lifted up and sitting on top of them

so at least its clear to have two seperate screens whereas the creator of InboxRecyclerView is doing it in same layout.

But in Nick's example hes using Activities whereas I'm using Fragments so not sure if the replacement of Parent Fragment with Detail Fragment is a problem here?!

So can someone help me?

Playa answered 9/11, 2018 at 1:44 Comment(0)
S
0

You have to postpone the end transition until RecyclerView is ready. Luckily, we have two handy methods at our disposal: postponeEnterTransition and startPostponedEnterTransition.

These allow us to delay the transition until we know our shared elements are laid out and can be found by the transition system. In Reply, we can postpone until we are sure our RecyclerView adapter has been populated and our list items have been bound with transition names using the following snippet:

postponeEnterTransition()
view.doOnPreDraw { startPostponedEnterTransition() }
Scammon answered 23/8, 2021 at 13:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.