MotionLayout を使用したカルーセル

Compose をお試しください
Jetpack Compose は、Android で推奨される UI ツールキットです。Compose でカルーセルを追加する方法をご覧ください。

Carousel は、ユーザーがスクロールできる要素のリストを表示するカスタム カルーセル ビューを構築するためのモーション ヘルパー オブジェクトです。このようなビューを実装する他の方法と比較して、このヘルパーを使用すると、MotionLayout を利用して Carousel の複雑なモーションとディメンションの変更をすばやく作成できます。

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

MotionLayout を使用したカルーセルの仕組み

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

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

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

  • 前へ
  • start
  • 次へ

開始状態がベース レイアウトに対応している場合、前の状態と次の状態では、Carousel 項目がそれぞれ 1 つ左と右にシフトします。

たとえば、図 3 の 5 つのビューを考えます。開始状態では、ビュー B、C、D が表示され、A と E は画面外にあるとします。A、B、C、D の位置が B、C、D、E の位置になるように、前の状態を設定します。ビューは左から右に移動します。次の状態では、B、C、D、E が A、B、C、D のあった場所に移動し、ビューが右から左に移動するという、逆の処理が行われる必要があります。これらを図 4 に示します。

ビューが元のビューの開始位置と正確に一致することが重要です。Carousel は、実際のビューを元の位置に戻し、新しい一致するコンテンツで再初期化することで、要素の無限コレクションの錯覚を生み出します。次の図は、このメカニズムを示しています。(「item #」の値に注意してください)。

切り替え効果

モーション シーン ファイルで定義された 3 つの制約セットを使用して、start 状態と next 状態の間、および start 状態と previous 状態の間に、順方向と逆方向の 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: 開始状態との状態の間に適用される 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 で現在「選択」されている項目によっては、Carousel開始終了を正しく考慮するために、前後の項目を表すビューを非表示にする必要がある場合があります。Carousel ヘルパーはこれを自動的に処理します。デフォルトでは、このような状況ではビューが View.INVISIBLE としてマークされるため、レイアウト全体は変わりません。

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

app:carousel_emptyViewsBehavior="gone"

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