Карусель с MotionLayout

Попробуйте способ «Композиции»
Jetpack Compose — рекомендуемый набор инструментов для разработки пользовательского интерфейса для Android. Узнайте, как добавить карусель в Compose.

Carousel — это вспомогательный объект для создания настраиваемых карусельных представлений, отображающих список элементов, которые пользователь может просматривать бегло. По сравнению с другими способами реализации таких представлений, этот вспомогательный объект позволяет быстро создавать сложные движения и изменять размеры вашей Carousel , используя MotionLayout .

Виджет Carousel поддерживает списки с началом и концом, а также циклические списки.

Как работает карусель с MotionLayout

Предположим, вы хотите создать горизонтальную Carousel с увеличенным центральным элементом:

Этот базовый макет содержит несколько представлений, представляющих элементы Carousel :

Создайте MotionLayout со следующими тремя состояниями и присвойте им идентификаторы:

  • предыдущий
  • начинать
  • следующий

Если начальное состояние соответствует базовому макету, то в предыдущем и следующем состоянии элементы Carousel сдвигаются на один элемент влево и вправо соответственно.

Например, возьмём пять представлений на рисунке 3 и предположим, что в начальном состоянии представления B, C и D видимы, а представления A и E находятся за пределами экрана. Настроим предыдущее состояние так, чтобы положения A, B, C и D совпадали с положениями B, C, D и E, при этом представления двигались слева направо. В следующем состоянии должно произойти обратное: B, C, D и E переместятся туда, где были представления A, B, C и D, а представления двигались справа налево. Это показано на рисунке 4:

Крайне важно, чтобы представления заканчивались точно там, где начинались исходные представления. Carousel создаёт иллюзию бесконечной коллекции элементов, возвращая представления на прежнее место, но при этом заново инициализируя их новым соответствующим содержимым. Этот механизм показан на следующей диаграмме. Обратите внимание на значения «item #»:

Переходы

Используя эти три набора ограничений, заданные в файле сцены движения, создайте два перехода — вперёд и назад — между начальным и следующим состояниями, а также между начальным и предыдущим состояниями. Добавьте обработчик OnSwipe для активации переходов в ответ на жест, как показано в следующем примере:

    <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>

После создания этой базовой сцены движения добавьте к макету вспомогательный элемент Carousel и ссылайтесь на виды в том же порядке, в котором вы реализуете предыдущую и следующую анимацию.

Задайте следующие атрибуты для помощника Carousel :

  • app:carousel_firstView : представление, представляющее первый элемент Carousel — в этом примере C.
  • app:carousel_previousState : идентификатор ConstraintSet предыдущего состояния.
  • app:carousel_nextState : идентификатор ConstraintSet следующего состояния.
  • app:carousel_backwardTransition : идентификатор Transition , применяемый между начальным и предыдущим состояниями.
  • app:carousel_forwardTransition : идентификатор Transition , применяемый между начальным и следующим состояниями.

Например, в вашем XML-файле макета есть что-то подобное:

    <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>

Настройте адаптер Carousel в коде:

Котлин

carousel.setAdapter(object : Carousel.Adapter {
            override fun count(): Int {
              // Return the number of items in the Carousel.
            }

            override fun populate(view: View, index: Int) {
                // Implement this to populate the view at the given index.
            }

            override fun onNewItem(index: Int) {
                // Called when an item is set.
            }
        })

Ява

carousel.setAdapter(new Carousel.Adapter() {
            @Override
            public int count() {
                // Return the number of items in the Carousel.
            }

            @Override
            public void populate(View view, int index) {
                // Populate the view at the given index.
            }

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

Дополнительные примечания

В зависимости от текущего выбранного элемента в Carousel , представления, представляющие элементы до или после неё, могут потребовать скрытия для корректного учёта начала и конца Carousel . Помощник Carousel обрабатывает это автоматически. По умолчанию в таких ситуациях он помечает эти представления как View.INVISIBLE , поэтому общий макет не меняется.

Доступен альтернативный режим, в котором помощник Carousel отмечает эти представления как View.GONE . ​​Вы можете установить этот режим с помощью следующего свойства:

app:carousel_emptyViewsBehavior="gone"

Примеры

Дополнительные примеры использования помощника Carousel см. в примерах проектов на GitHub.