Monday, October 22, 2018

Continuous Shared Chemical Ingredient Transitions: Recyclerview To Viewpager

By Shalom Gibly, Software Engineer, Google's Material Gallery Team

Transitions inward Material Design apps furnish visual continuity. As the user navigates the app, views inward the app modify state. Motion too transformation reinforce the thought that interfaces are tangible, connecting mutual elements from i sentiment to the next.

This post aims to furnish guidelines too implementation for a specific continuous transition betwixt Android Fragments. We volition demonstrate how to implement a transition from an ikon inward a RecyclerView into an ikon inward a ViewPager too back, using 'Shared Elements' to decide which views participate inward the transition too how. We volition too grip the tricky instance of transitioning dorsum to the grid after paging to an exceptional that was previously offscreen.

This is the resultant nosotros are aiming for:

If you lot wishing to skip the explanation too move out straight off to the code, you lot tin flame honor it here.

What are shared elements?

Influenza A virus subtype H5N1 shared chemical cistron transition determines how views that are introduce inward 2 fragments transition betwixt them. For example, an ikon that is displayed on an ImageView on both Fragment A too Fragment B transitions from A to B when B becomes visible.

There are numerous previously published examples which explicate how shared elements move too how to implement a basic Fragment transition. This post volition skip most of the basics too volition walk through the specifics on how to create a working transition into a ViewPager too back. However, if you'd similar to larn to a greater extent than nearly transitions, I recommend starting past times reading nearly transitions at the Android's developers website, too accept the fourth dimension to spotter this 2016 Google I/O presentation.

The challenges

Shared Element mapping

We would similar to back upwards a seamless dorsum too forth transition. This includes a transition from the grid to the pager, too and therefore a transition dorsum to the relevant image, fifty-fifty when the user paged to a dissimilar image.

To create so, nosotros volition take to honor a means to dynamically remap the shared elements inward club to furnish the Android's transition organization what it needs to create its magic!

Delayed loading

Shared chemical cistron transitions are powerful, but tin flame endure tricky when dealing amongst elements that take to endure loaded earlier nosotros tin flame transition to them. The transition may only non move equally expected when views at the target fragment are non set out too ready.

In this project, in that location are 2 areas where a loading fourth dimension affects the shared chemical cistron transition:

  1. It takes a few milliseconds for the ViewPager to charge its internal fragments. Additionally, it takes fourth dimension to charge an ikon into the displayed pager fragment (may fifty-fifty include a download fourth dimension for the asset).
  2. The RecyclerView too faces a similar delay when loading the images into its views.

Demo app design

Basic structure

Before nosotros dive into the juicy transitions, hither is a trivial combat nearly how the demo app is structured.

The MainActivity loads a GridFragment to introduce a RecyclerView of images. The RecyclerView adapter loads the ikon items (a constant array that is defined at the ImageData class), too manages the onClick events past times replacing the displayed GridFragment amongst an ImagePagerFragment.

The ImagePagerFragment adapter loads the nested ImageFragments to display the private images when paging happens.

Note: The demo app implementation uses Glide, which loads images into views asynchronously. The images inward the demo app are bundled amongst it. However, you lot may easily convert the ImageData floor to fit URL strings that indicate to online images.

Coordinating a selected/displayed position

To communicate the selected ikon seat betwixt the fragments, nosotros volition usage the MainActivity equally a house to shop the position.

When an exceptional is clicked, or when a page is changed, the MainActivity is updated amongst the relevant item's position.

The stored seat is later on used inward several places:

  • When determining which page to exhibit inward the ViewPager.
  • When navigating dorsum to the grid too auto-scrolling to the seat to brand certain it's visible.
  • And of course, when hooking upwards the transitions callbacks, equally we'll come across inward the adjacent section.

Setting upwards the transitions

As mentioned above, nosotros volition take to honor a means to dynamically remap the shared elements inward club to orbit the transition organization what it needs to create its magic.

Using a static mapping past times setting upwards transitionName attributes for the ikon views at the XML volition non work, equally nosotros are dealing amongst an arbitrary amount of views that part the same layout (e.g. views inflated past times the RecyclerView adapter, or views inflated past times the ImageFragment).

To arrive at this, we'll usage about of what the transition organization provides us:

  1. We set a transition cry on the ikon views past times calling setTransitionName. This volition seat the sentiment amongst a unique quest the transition. setTransitionName is called when binding a sentiment at the grid's RecyclerView adapter, too onCreateView at the ImageFragment. In both locations, nosotros usage the unique ikon resources equally a cry to seat the view.
  2. We gear upwards SharedElementCallbacks to intercept onMapSharedElements too adjust the mapping of the shared chemical cistron names to views. This volition endure done when exiting the GridFragment too when entering the ImagePagerFragment.

Setting the FragmentManager transaction

The showtime affair nosotros gear upwards to initiate a transition for a fragment replacement is at the FragmentManager transaction preparation. We take to inform the organization that nosotros create got a shared chemical cistron transition.

fragment.getFragmentManager()    .beginTransaction()    .setReorderingAllowed(true) // setAllowOptimization earlier 26.1.0    .addSharedElement(imageView, imageView.getTransitionName())    .replace(R.id.fragment_container,          novel ImagePagerFragment(),         ImagePagerFragment.class.getSimpleName())    .addToBackStack(null)    .commit();

The setReorderingAllowed is set to true. It volition reorder the nation changes of fragments to allow for ameliorate shared chemical cistron transitions. Added fragments volition create got onCreate(Bundle) called earlier replaced fragments create got onDestroy() called, allowing the shared sentiment to acquire created too set out earlier the transition starts.

Image transition

To define how the ikon transitions when it animates to its novel location, nosotros gear upwards a TransitionSet inward an XML file too charge it at the ImagePagerFragment.

<ImagePagerFragment.java>
Transition transition =    TransitionInflater.from(getContext())        .inflateTransition(R.transition.image_shared_element_transition); setSharedElementEnterTransition(transition);
<image_shared_element_transition.xml>
<?xml version="1.0" encoding="utf-8"?> <transitionSet    xmlns:android="http://schemas.android.com/apk/res/android"    android:duration="375"    android:interpolator="@android:interpolator/fast_out_slow_in"    android:transitionOrdering="together">  <changeClipBounds/>  <changeTransform/>  <changeBounds/> </transitionSet>

Adjusting the shared chemical cistron mapping

We'll start past times adjusting the shared chemical cistron mapping when leaving the GridFragment. For that, nosotros volition telephone phone the setExitSharedElementCallback() too furnish it amongst a SharedElementCallback which volition map the chemical cistron names to the views we'd similar to include inward the transition.

It's of import to banking concern annotation that this callback volition endure called spell exiting the Fragment when the fragment-transaction occurs, too spell re-entering the Fragment when it's popped out of the backstack (on dorsum navigation). We volition usage this demeanour to remap the shared sentiment too adjust the transition to grip cases where the sentiment is changed after paging the images.

In this specific case, nosotros are exclusively interested inward a unmarried ImageView transition from the grid to the fragment the view-pager holds, therefore the mapping exclusively needs to endure adjusted for the first named chemical cistron received at the onMapSharedElements callback.

<GridFragment.java>
setExitSharedElementCallback(    novel SharedElementCallback() {      @Override      world void onMapSharedElements(          List<String> names, Map<String, View> sharedElements) {        // Locate the ViewHolder for the clicked position.        RecyclerView.ViewHolder selectedViewHolder = recyclerView            .findViewHolderForAdapterPosition(MainActivity.currentPosition);        if (selectedViewHolder == aught || selectedViewHolder.itemView == null) {          return;        }         // Map the showtime shared chemical cistron cry to the kid ImageView.        sharedElements            .put(names.get(0),                 selectedViewHolder.itemView.findViewById(R.id.card_image));      }    });

We too take to adjust the shared chemical cistron mapping when entering the ImagePagerFragment. For that, nosotros volition telephone phone the setEnterSharedElementCallback().

<ImagePagerFragment.java>
setEnterSharedElementCallback(    novel SharedElementCallback() {      @Override      world void onMapSharedElements(          List<String> names, Map<String, View> sharedElements) {           // Locate the ikon sentiment at the principal fragment (the ImageFragment           // that is currently visible). To locate the fragment, telephone phone           // instantiateItem amongst the pick position.           // At this stage, the method volition only render the fragment at the           // seat too volition non create a novel one.        Fragment currentFragment = (Fragment) viewPager.getAdapter()            .instantiateItem(viewPager, MainActivity.currentPosition);        View sentiment = currentFragment.getView();        if (view == null) {          return;        }         // Map the showtime shared chemical cistron cry to the kid ImageView.        sharedElements.put(names.get(0), view.findViewById(R.id.image));      }    });

Postponing the transition

The images nosotros would similar to transition are loaded into the grid too the pager too accept fourth dimension to load. To move out far move properly, nosotros volition take to postpone the transition until the participating views are ready (e.g. set out too loaded amongst the ikon data).

To create so, nosotros telephone phone a postponeEnterTransition() inward our fragments' onCreateView(), too i time the ikon is loaded, nosotros start the transition past times calling startPostponedEnterTransition().

Note: postpone is called for both the grid too the pager fragments to back upwards both forrad too backward transitions when navigating the app.

Since nosotros are using Glide to charge the images, nosotros gear upwards listeners that trigger the acquire into transition when images are loaded.

This is done inward 2 places:

  1. When an ImageFragment ikon is loaded, a telephone phone is made to its bring upwards ImagePagerFragment to start the transition.
  2. When transitioning dorsum to the grid, a start transition is called after the "selected" ikon is loaded.

Here is how the ImageFragment loads an ikon too notifies its bring upwards when it's ready.

Note that the postponeEnterTransition is made at the the ImagePagerFragment, spell the startPostponeEnterTransition is called from the kid ImageFragment that is created past times the pager.

<ImageFragment.java>
Glide.with(this)    .load(arguments.getInt(KEY_IMAGE_RES)) // Load the ikon resources    .listener(new RequestListener<Drawable>() {      @Override      world boolean onLoadFailed(@Nullable GlideException e, Object model,          Target<Drawable> target, boolean isFirstResource) {        getParentFragment().startPostponedEnterTransition();        render false;      }       @Override      world boolean onResourceReady(Drawable resource, Object model,          Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {        getParentFragment().startPostponedEnterTransition();        render false;      }    })    .into((ImageView) view.findViewById(R.id.image));

As you lot may create got noticed, nosotros too telephone phone to start the postponed transition when the loading fails. This is of import to preclude the UI from hanging during failure.

Final touches

To brand our transitions fifty-fifty smoother, nosotros would similar to fade out the grid items when the ikon transitions to the pager view.

To create that, nosotros create a TransitionSet that is applied equally an move out transition for the GridFragment.

<GridFragment.java>
setExitTransition(TransitionInflater.from(getContext())    .inflateTransition(R.transition.grid_exit_transition));
<grid_exit_transition.xml>
<?xml version="1.0" encoding="utf-8"?> <transitionSet xmlns:android="http://schemas.android.com/apk/res/android"    android:duration="375"    android:interpolator="@android:interpolator/fast_out_slow_in"    android:startDelay="25">  <fade>    <targets android:targetId="@id/card_view"/>  </fade> </transitionSet>

This is what the transition looks similar after this move out transition is set up:

As you lot may create got noticed, the transition is all the same non completely polished amongst this setup. The fade animation is running for all the grid's carte du jour views, including the carte du jour that holds the ikon that transitions to the pager.

To ready it, nosotros exclude the clicked carte du jour from the move out transition earlier commiting the fragment transaction at the GridAdapter.

// The 'view' is the carte du jour sentiment that was clicked to initiate the transition. ((TransitionSet) fragment.getExitTransition()).excludeTarget(view, true);

After this change, the animation looks much ameliorate (the clicked carte du jour doesn't fade out equally usage of the move out transition, spell the residuum of the cards fade out):

As a terminal touch, nosotros gear upwards the GridFragment to scroll too expose the carte du jour nosotros transition to when navigating dorsum from the pager (done at the onViewCreated):

<GridFragment.java>
recyclerView.addOnLayoutChangeListener(    novel OnLayoutChangeListener() {       @Override       world void onLayoutChange(View view,                 int left,                  int top,                  int right,                  int bottom,                  int oldLeft,                  int oldTop,                  int oldRight,                  int oldBottom) {          recyclerView.removeOnLayoutChangeListener(this);          terminal RecyclerView.LayoutManager layoutManager =             recyclerView.getLayoutManager();          View viewAtPosition =              layoutManager.findViewByPosition(MainActivity.currentPosition);          // Scroll to seat if the sentiment for the electrical flow seat is aught (not             // currently usage of layout managing director children), or it's non completely          // visible.          if (viewAtPosition == aught               || layoutManager.isViewPartiallyVisible(viewAtPosition, false, true)){             recyclerView.post(()                 -> layoutManager.scrollToPosition(MainActivity.currentPosition));          }      } });

Wrapping up

In this article, nosotros implemented a smooth transition from a RecyclerView to a ViewPager too back.

We showed how to postpone a transition too start it after the views are ready. We too implemented shared chemical cistron remapping to acquire the transition going when shared views are changing dynamically spell navigating the app.

These changes transformed our app's fragment transitions to furnish ameliorate visual continuity equally users interact amongst it.

The code for the demo app tin flame endure constitute here.

Related Post

Continuous Shared Chemical Ingredient Transitions: Recyclerview To Viewpager
4/ 5
Oleh