この記事は、はてなエンジニア 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 さんです!