MotionLayoutを使って無限Pagerを実装する


この記事は、はてなエンジニア Advent Calendar 2019 の 23 日目の記事です。

MotionLayout でこのような無限 Pager を実装してみたというちょっとしたお遊びです。

MotionLayout で Pager を作る考え方

  • 表示させているもの + 前後に表示されず隠れている View を用意する

  • スワイプさせて次の View が表示されたタイミングで、表示をそのままに Motion を最初の状態に戻す

といった感じで Pager を実現させています。

実装

MotionScene の定義

ConstraintSet を最初の状態、右にスワイプした時、左にスワイプした時の 3 パターン用意します。
最初の状態を起点に Transition を右にスワイプした時、左にスワイプした時で 2 つ用意します。  

<?xml version="1.0" encoding="utf-8"?>
<MotionScene xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:motion="http://schemas.android.com/apk/res-auto">

    <ConstraintSet android:id="@+id/base_state">
        <Constraint android:id="@id/centerView">
            <Layout
                android:layout_width="320dp"
                android:layout_height="320dp"
                motion:layout_constraintBottom_toBottomOf="parent"
                motion:layout_constraintEnd_toEndOf="parent"
                motion:layout_constraintStart_toStartOf="parent"
                motion:layout_constraintTop_toTopOf="parent" />

        </Constraint>

        <Constraint android:id="@id/leftView">
            <Layout
                android:layout_width="320dp"
                android:layout_height="320dp"
                motion:layout_constraintBottom_toBottomOf="parent"
                motion:layout_constraintEnd_toStartOf="parent"
                motion:layout_constraintTop_toTopOf="parent" />
        </Constraint>

        <Constraint android:id="@id/rightView">
            <Layout
                android:layout_width="320dp"
                android:layout_height="320dp"
                motion:layout_constraintBottom_toBottomOf="parent"
                motion:layout_constraintStart_toEndOf="parent"
                motion:layout_constraintTop_toTopOf="parent" />
        </Constraint>
    </ConstraintSet>

    <ConstraintSet android:id="@+id/move_left_to_right">
        <!-- 省略 -->
        <!-- 左へスワイプした時のViewの状態を定義する -->
    </ConstraintSet>

    <ConstraintSet android:id="@+id/move_right_to_left">
        <!-- 省略 -->
        <!-- 右へスワイプした時のViewの状態を定義する -->
    </ConstraintSet>

    <Transition
        motion:constraintSetEnd="@id/move_left_to_right"
        motion:constraintSetStart="@id/base_state">
        <OnSwipe
            motion:dragDirection="dragRight"
            motion:onTouchUp="autoCompleteToStart"
            motion:touchAnchorId="@id/centerView"
            motion:touchAnchorSide="right" />
    </Transition>

    <Transition
        motion:constraintSetEnd="@id/move_right_to_left"
        motion:constraintSetStart="@id/base_state">
        <OnSwipe
            motion:dragDirection="dragLeft"
            motion:onTouchUp="autoCompleteToStart"
            motion:touchAnchorId="@id/centerView"
            motion:touchAnchorSide="left" />
    </Transition>

</MotionScene>

アイテムの表示処理の実装

アニメーションが完了したら(スワイプで完全に移動した時)、リストの位置をずらしてアニメーションを最初に戻す (motionLayout?.progress = 0F) だけです。

motionLayout.setTransitionListener(object : MotionLayout.TransitionListener {
    override fun onTransitionCompleted(motionLayout: MotionLayout?, currentId: Int) {
        when (currentId) {
            R.id.move_left_to_right -> {
                if (currentPosition > 0) {
                    currentPosition--
                } else {
                    currentPosition = itemList.lastIndex
                }
                motionLayout?.progress = 0F
                updateView()
            }
            R.id.move_right_to_left -> {
                if (currentPosition < itemList.lastIndex) {
                    currentPosition++
                } else {
                    currentPosition = 0
                }
                motionLayout?.progress = 0F
                updateView()
            }
        }
    }
})

MotionLayout を使っているため KeyFrameSet を調整することで様々なアニメーションを実現できます。

全コードはこちらに公開しています
https://github.com/NUmeroAndDev/MotionLayoutPager-android

MotionLayout で無限 Pager を実装してみました。
プロダクトに当てはめるには考慮すべきことや表現できないパターンがありそうですが、ぬるぬるアニメーションさせるなら MotionLayout は便利だなと思いました。
来年には MotionEditor が使えるようになりそうなので、MotionLayout をもっと使っていきたいですね

明日のはてなエンジニア Advent Calendar 2019 は @hitode909 さんです!