Carrossel com MotionLayout

Carousel é um objeto auxiliar de movimento para criar visualizações de carrossel personalizadas que mostram uma lista de elementos que o usuário pode conferir. Em comparação com outras maneiras de implementar essas visualizações, esse auxiliar permite criar rapidamente mudanças complexas de movimento e dimensão para a Carousel usando MotionLayout.

O widget Carousel oferece suporte a listas com início e fim, além de listas circulares.

Como o Carousel com MotionLayout funciona

Suponha que você queira criar uma visualização Carousel horizontal com o item central aumentado:

Esse layout básico contém várias visualizações que representam os itens Carousel:

Crie uma MotionLayout com os três estados abaixo e atribua IDs a eles:

  • anterior
  • iniciar
  • próximo

Se o estado start corresponder ao layout básico, nos estados previous e o next, os itens Carousel serão deslocados um para a esquerda e para a direita, respectivamente.

Por exemplo, considere as cinco visualizações da Figura 3 e suponha que, no estado start, as visualizações B, C e D estão visíveis e A e E estão fora da tela. Configure o estado anterior para que as posições de A, B, C e D fiquem onde B, C, D e E estejam, com as visualizações se movendo da esquerda para a direita. No próximo estado, o oposto precisa acontecer, com B, C, D e E se movendo para onde A, B, C e D estavam e as visualizações se movendo da direita para a esquerda. Isso é mostrado na Figura 4:

É fundamental que as visualizações cheguem exatamente onde as originais começam. Carousel dá a ilusão de uma coleção infinita de elementos, movendo as visualizações reais de volta para onde estavam, mas reiniciando-as com o novo conteúdo correspondente. O diagrama a seguir mostra esse mecanismo. Preste atenção aos valores do "item #":

Transições

Com esses três conjuntos de restrições definidos no arquivo de cena de movimento, crie duas transições, para frente e para trás, entre os estados start e next e os estados start e previous. Adicione um gerenciador OnSwipe para acionar as transições em resposta a um gesto, conforme mostrado no exemplo abaixo.

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

Depois que essa cena de movimento básica for criada, adicione um auxiliar Carousel ao layout e faça referência às visualizações na mesma ordem em que você implementa a animação anterior e a próxima.

Defina os atributos abaixo para o auxiliar Carousel:

  • app:carousel_firstView: a visualização que representa o primeiro elemento da Carousel. Neste exemplo, C.
  • app:carousel_previousState: o ID do ConstraintSet do estado anterior.
  • app:carousel_nextState: o ID ConstraintSet do próximo estado.
  • app:carousel_backwardTransition: o ID Transition aplicado entre os estados start e previous.
  • app:carousel_forwardTransition: o ID Transition aplicado entre os estados start e next.

Por exemplo, você tem algo assim no seu arquivo XML de layout:

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

Configure um adaptador Carousel no código:

Kotlin

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.
            }
        })

Java

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.
            }
        });

Outras observações

Dependendo do item atual "selecionado" na Carousel, as visualizações que representam os itens anteriores ou posteriores talvez precisem ser ocultadas para contabilizar corretamente o Carousel start e end. O auxiliar Carousel processa isso automaticamente. Por padrão, ele marca essas visualizações como View.INVISIBLE nessas situações, para que o layout geral não mude.

Um modo alternativo está disponível em que o auxiliar Carousel marca essas visualizações como View.GONE. É possível definir esse modo usando a seguinte propriedade:

app:carousel_emptyViewsBehavior="gone"

Exemplos

Para mais exemplos que usam o auxiliar Carousel, consulte os exemplos de projetos no GitHub.