Carousel is a motion helper object to easily build custom "carousel"
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.
The Carousel widget supports lists with a start and end as well as circular
wrap-around lists.
Concept: How Carousel with MotionLayout works
Let's imagine that we want to build a simple horizontal carousel view, with
a centered view enlarged:
Our basic layout contains several views, representing our carousel items:
Create a MotionLayout with three 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, with the views 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, and the views 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 defined in your motion scene file,
create two transitions – forward and backward – between the start
and next, and start and previous. Let's add an OnSwipe
handler to trigger the transitions via a gesture. For example:
motion:constraintSetStart="@id/start"
motion:constraintSetEnd="@+id/next"
motion:duration="1000"
android:id="@+id/forward">
motion:dragDirection="dragLeft"
motion:touchAnchorSide="left" />
motion:constraintSetStart="@+id/start"
motion:constraintSetEnd="@+id/previous"
android:id="@+id/backward">
motion:dragDirection="dragRight"
motion:touchAnchorSide="right" />
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:
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" />
Finally, we also need to set up a Carousel adapter in code:
Kotlin
carousel.setAdapter(object : Carousel.Adapter {
override fun count(): Int {
// need to return the number of items we have in the carousel
}
override fun populate(view: View, index: Int) {
// need to implement this to populate the view at the given index
}
override fun onNewItem(index: Int) {
// called when an item is set
}
})Java
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 in which the Carousel helper instead marks
those views as View.GONE. This mode can be set using the following property:
app:carousel_emptyViewsBehavior="gone"
Examples
For more examples using the Carousel helper, see the example projects
on GitHub.