Android MotionLayout概論

この記事は eureka Advent Calendar 2018 6日目の記事です。

PairsのGlobal版(台湾・韓国展開中)のAndroidエンジニアをしているt-kurimuraです。今年は、Goを書いたり、BigQueryやMySQLと格闘している時間もそこそこ長いです。

さて、今回はGoogleI/O 2018で発表され、2018年12月現在ConstraintLayout2.0のalpha版で提供されているMotionLayoutを利用してみたので、概要を感想を交えてご紹介します。

GoogleIO 2018でのMotionLayoutの発表

MotionLayoutとは

MotionLayoutとは、ConstraintLayoutを継承したViewGroupのひとつです。

複数のConstraintLayoutの制約に従って定義された複数のレイアウトの状態を、アニメーションを明示的に定義することなくTransitionさせることができます。また、状態が時系列に変化するアニメーションのみではなく、スワイプの操作に追従した状態の遷移などの定義も可能です。


MotionLayoutを使うメリット

お気づきの方もいるかも知れませんが、上述の状態遷移は、特にアニメーションのみに関して言えば、ConstraintLayoutの ConstraintSetを利用した実装でも以下のように実現可能です。

ConstraintLayoutの ConstraintSetを利用したレイアウトの状態遷移アニメーションの実装

この場合、KotlinなどでActivityやFragment側に遷移の状態をProgramaticallyに定義するコードを書く必要があります。このサンプル程度の変化対象Viewと変化するプロパティの数のならMotionLayoutを利用するよりもこのConstraintSetを利用するほうがスマートに実装できます。

しかし、3つも4つもViewのプロパティーが変わる変化をする場合や、それぞれのレイアウトの状態を明確にしたい場合は、Kotlin側で書くコードが膨大になり可読性も低下しますし、運用も面倒になることが多いと思います。

MotionLayoutを利用する場合、[レイアウトの状態1]と[レイアウトの状態2]を別々のXMLに定義することができます。ここもConstraintLayoutでもできなくもないが)、2つの状態の遷移のトリガーをもXMLの中に定義し隠蔽することができます。

したがって、 ActivityやFragment側にアニメーションなどの遷移に関する定義を全く書くことなく、リッチな遷移アニメーションを表現することができます。


MotionLayoutの構成と可読性

MotionLayoutでは2つの状態と遷移の仕方、トリガーをそれぞれ別のXMLに定義することができます。同じXML上で表現することも可能ですが、特に大きなアプリだと役割毎にXMLファイルを別に分けたほうが[レイアウトの状態1]と[レイアウトの状態2]をそれぞれ独立して確認することができるため、可読性が高くなると思います。
activityのレイアウトは当然res/layout/以下に配置しますが、それ以外はres/xml/配下に置く必要があります。

Activity

ActivityのLayoutXMLでは、一般的なレイアウトと同様にレイアウトの定義をConstraintLayoutで行います。Transitionによって変化しないViewプロパティの定義をっておくとConstraintSetで二重に書く必要がなくなります。

Scene

MotionSceneの中では遷移に関しての定義を行います。Transitionタグの中では、遷移がはじまるときと終わる時の状態を指定します。また、Transitionタグの小要素ではOnclickとOnSwipeで遷移のトリガーを指定します。

ConstraintSet

ここでは遷移開始前と遷移後のレイアウトの状態をそれぞれ定義します。ConstraintSetはMotionSceneの子要素として定義することも可能ですが、大きなレイアウトの場合1ファイルが長くなるので別ファイルで定義することをおすすめします。

ここでのレイアウトの定義方法はやや特殊です。

ConstraintSetをConstraintLayoutと、Constraintを各Viewへと読みかえ、その子要素のようにactivityのレイアウトで定義した要素の`layout_constraintBottom_toBottomOf ` といったプロパティを定義していきます。ただ、このときの
xmlnsのAttributeは appではなくmotionであるところに注意してください。


MotionLayoutのなめらかなAnimation

MotionLayoutでは、[レイアウトの状態1]と[レイアウトの状態2]に遷移するときにconstratinで定義
されたプロパティを読み取り位置の変更を自動的になめらかに行なってくれます。

それぞれのViewの要素に定義された以下のプロパティについても遷移時に自動的にTransitionAnimationが適用されます。

  • alpha
  • visibility
  • elevation
  • rotation, rotation[X/Y]
  • translation[X/Y/Z]
  • scaleX/Y

上記のプロパティのいくつかをMotionLayoutでTransitionで遷移させたサンプルとそのSceneの実装です。

MotionLayout Simple Sample

MotionLayoutの動的制御

MotionLayoutは、ActivityやFragment側での制御の実装なしに、なめらかな遷移が実現できることが魅力です。
ただ、APIでの処理に合わせて特定の状態に遷移させたり、遷移終了時になにか次の動作を行いたいこともあると思います。
これらを実現できるような以下のようなメソッドが提供されています。

OnClick/Swipe以外で、レイアウトの状態を変更する(アニメーションあり)

OnClick/Swipe以外で、レイアウトの状態を変更する

Transitionの進行状況や開始・終了を検知する

AndroidDevelpersのDocumentには引数が何を示しているのが未記載だったので意味がわかるように命名しています。


デモ - MotionLayoutで実現するSpeedDial

Material Design Speed Dial

ライブラリなどで頻出するFloatingActionからボタンが選択できるMaterialDesignのUIをMotionLayoutで実現したサンプルです。
このサンプルの最大の魅力はActivity側に全くこの動作にかかわる実装を行わず、関連するコードがXML上で収まっている点にあります。

残念ながらColorTransitionは自動的には行われないので、重ねたViewのAlphaで疑似的にColorTransitionを再現しています。
サンプルコードをGithub上で公開しているので参考にしてください 。


まとめ

MotionLayoutは、
 
・ 遷移の際の複雑なAnimationを細かく定義する必要がない
・関連する実装がすべてXMLの中で完結できる
・レイアウトの状態をファイルを分割して定義できる

といった観点で複雑なUXを可読性を担保しつつ容易にしていっているようで非常に魅力を感じました。古くからあるSharedElementなどのAnimationと組み合わせるとAndroidでもリッチなUXがより簡単に提供できるようになるように思います。

この情報は2018/12時点でのAlpha版での利用に基づいて構成しているので変更になる場合が大いにありますのでご了承ください。また誤りがあればご指摘いただけると幸いです。この記事が少しでも皆様の参考になればと思います。