Skip to content

Carousel

Nicolas Roard edited this page Mar 11, 2021 · 17 revisions

Carousel is a motion helper object to easily build custom "carousels" views -- showing a list of elements that a user can skim through. Compared to other solutions to implement such views, this helper lets you quickly create complex motion and dimension changes for your carousel, by taking advantage of MotionLayout.

Carousel supports either lists with a start and end, as well as infinite wrap-around lists.

Concept : how it works

Let's imagine that we want to build a simple horizontal carousel view, with a centered view enlarged:

Our basic layout will contains several views, representing our carousel items:

Create a motion layout with 3 states (make sure to give them Ids)

  • previous
  • start
  • next

If the start state corresponds to that base layout, the previous state should be done in such a way that the carousel items will be shifted by one.

For example let's imagine we have 5 views A, B, C, D, E in the start state, with B, C, D visible and A and E outside of the screen. We want to set up the previous state such that the positions of A, B, C, D now are where B, C, D, E were, such that the views are moving from left to right. In the next state, the opposite needs to happen, with B, C, D, E moving to where A, B, C, D were, such that the views are moving from right to left.

What is critical is that the views end up exactly where the original views started; the way the carousel give the illusion of an infinite collection of elements is by moving the actual views back to where they were, but reinitializing them with the new matching content. The following diagram shows this mechanism (pay attention to the "item #" values):

Transitions

With those three constraintsets in your motion scene file defined, we need to create two transitions -- forward and backward -- between the start and next, and start and previous. Typically here, for Carousel we would add an OnSwipe handler to trigger the transitions via a gesture. For example:

<Transition
    motion:constraintSetStart="@id/start"
    motion:constraintSetEnd="@+id/next"
    motion:duration="1000"
    android:id="@+id/forward">
    <OnSwipe
        motion:dragDirection="dragLeft"
        motion:touchAnchorSide="left" />
</Transition>

<Transition
    motion:constraintSetStart="@+id/start"
    motion:constraintSetEnd="@+id/previous"
    android:id="@+id/backward">
    <OnSwipe
        motion:dragDirection="dragRight"
        motion:touchAnchorSide="right" />
</Transition>

Adding the Carousel

Once this basic motion scene is created, we only need to add a Carousel helper to the layout and references those views (in the same order we implemented our previous/next animation).

The Carousel helper also need a couple of attributes to be set up:

  • app:carousel_firstView : the view that will represent the first element of the carousel, in our example, C
  • app:carousel_previousState : the constraintset id of the previous state
  • app:carousel_nextState : the constraintset id of the next state
  • app:carousel_backwardTransition : the transition id applied between start -> previous
  • app:carousel_forwardTransition : the transition id applied between start -> next

For example, you'd have something like this in your layout XML file:

<androidx.constraintlayout.motion.widget.MotionLayout ... >

    <ImageView  android:id="@+id/imageView0" .. />
    <ImageView  android:id="@+id/imageView1" .. />
    <ImageView  android:id="@+id/imageView2" .. />
    <ImageView  android:id="@+id/imageView3" .. />
    <ImageView  android:id="@+id/imageView4" .. />

    <androidx.constraintlayout.helper.widget.Carousel
        android:id="@+id/carousel"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:carousel_forwardTransition="@+id/forward"
        app:carousel_backwardTransition="@+id/backward"
        app:carousel_previousState="@+id/previous"
        app:carousel_nextState="@+id/next"
        app:carousel_infinite="true"
        app:carousel_firstView="@+id/imageView2"
        app:constraint_referenced_ids="imageView0,imageView1,imageView2,imageView3,imageView4" />

</androidx.constraintlayout.motion.widget.MotionLayout>

Finally, we also need to set up a Carousel adapter in code:

    carousel.setAdapter(new Carousel.Adapter() {
        @Override
        public int count() {
            // need to return the number of items we have in the carousel
        }

        @Override
        public void populate(View view, int index) {
            // need to implement this to populate the view at the given index
        }

        @Override
        public void onNewItem(int index) {
             // called when an item is set
        }
    });

Additional notes

Depending on the current item "selected" in the Carousel, the views representing the items before or after may need to be hidden in order to correctly account for the Carousel start and end. The Carousel helper will handle this automatically for you, by default marking those views as View.INVISIBLE in those situations (so the overall layout doesn't change). An alternative mode is available to that the Carousel helper instead marks those views as View.GONE.

    app:carousel_emptyViewsBehavior="gone"

Examples

Take a look at the example projects to see different ways you can use the Carousel helper.