MotionLayout を使用したカルーセル

Carousel は、ユーザーがスキミングできる要素のリストを表示するカスタム カルーセル ビューを作成するためのモーション ヘルパー オブジェクトです。このようなビューを実装する他の方法とは異なり、このヘルパーでは MotionLayout を利用することで、Carousel の複雑なモーションや寸法の変更をすばやく作成できます。

Carousel ウィジェットは、開始と終了のあるリストと円形のラップアラウンド リストをサポートしています。

Carousel と MotionLayout の仕組み

中央のアイテムが拡大された横向きの Carousel ビューを作成するとします。

この基本的なレイアウトには、Carousel アイテムを表すいくつかのビューが含まれています。

次の 3 つの状態で MotionLayout を作成し、ID を付与します。

  • 前へ
  • 開始
  • 次へ

start 状態がベース レイアウトに対応している場合、Previous 状態とnext状態では、Carousel アイテムがそれぞれ左に 1 つ、右にシフトされます。

たとえば、図 3 の 5 つのビューについて、start 状態ではビュー B、C、D が表示され、A と E は画面の外部にあるとします。以前の状態をセットアップして、A、B、C、D の位置を B、C、D、E の位置にして、ビューを左から右に移動します。次の状態では、B、C、D、E が A、B、C、D の位置に移動し、ビューが右から左に移動するという逆の操作が必要になります。これを図 4 に示します。

必ず、元のビューの開始位置にビューを正確に配置する必要があります。Carousel は、実際のビューを元の場所に戻し、一致する新しいコンテンツで再初期化することで、要素が無限に集合されているように見せます。次の図は、このメカニズムを示しています。「アイテム #」の値に注意してください)。

切り替え効果

モーション シーン ファイルにこれら 3 つの制約セットを定義して、開始状態と次へ状態、開始と前状態の間に 2 つの遷移(前進と後退)を作成します。ジェスチャーへの応答として遷移をトリガーする 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 ID。
  • app:carousel_nextState: 次の状態の ConstraintSet ID。
  • app:carousel_backwardTransition: 開始状態と前状態の間に適用される Transition ID。
  • app:carousel_forwardTransition: start 状態と next 状態の間に適用される Transition ID。

たとえば、レイアウト 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 アダプターを設定します。

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

その他の注意事項

Carousel で「選択されている」現在のアイテムによっては、Carouselstartend を正しく反映するため、前後のアイテムを表すビューを非表示にする必要が生じる場合があります。これは Carousel ヘルパーによって自動的に処理されます。デフォルトでは、このような状況ではビューが View.INVISIBLE としてマークされるため、全体的なレイアウトは変わりません。

代わりに Carousel ヘルパーがこれらのビューを View.GONE としてマークする別のモードも使用できます。このモードを設定するには、次のプロパティを使用します。

app:carousel_emptyViewsBehavior="gone"

カルーセル ヘルパーを使用するその他の例については、GitHub のサンプル プロジェクトをご覧ください。