MotionLayoutとは
Viewのアニメーションを管理できるViewGroupです。
ConstraintLayoutのサブクラスで、ConstraintLayoutを利用しているレイアウトなら、まるっとMotionLayoutに置き換えることができます。
※MotionLayoutはAPIレベル14との下位互換性があります。
導入
gradleに依存関係を追加します。
MotionLayoutは ConstraintLayoutのライブラリの2.0.0から導入
されたものですので、ConstraintLayoutのバージョンを上げる必要があります。
※2020/3/5の時点での最新バージョンは2.0.0-beta4でした。
// AndroidXを導入している場合はこちら
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta4'
// それ以外はこちら
implementation 'com.android.support.constraint:constraint-layout:2.0.0-beta4'
MotionLayoutで利用するアニメーションファイル※1を入れるためのXMLフォルダ※2を作成します。
※1 厳密にはアニメーション開始時と終了時のViewの状態を記述したファイルです。
※2 AndroidStudioのメニューから、File > New > Folder > XML Resource Folderで作成。
触ってみる
凝ったレイアウトを作る時間がないので、デフォルトの activity_main.xml
にMotionLayoutを適応してみます。
<!-- ConstraintLayoutからMotionLayoutに置き換えました。 -->
<androidx.constraintlayout.motion.widget.MotionLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
>
<!-- アニメーションさせたいViewにidをつけます。 -->
<TextView
android:id="@+id/main_text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
/>
</androidx.constraintlayout.motion.widget.MotionLayout>
次に res/xml/
にアニメーションファイルを作成します。
<!-- MotionScene※3タグで大枠を作ります。xmlns:motion="~"を忘れずに。 -->
<MotionScene
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:motion="http://schemas.android.com/apk/res-auto"
>
<!-- 開始、終了アニメーションの指定と実行時間を記述します。 -->
<Transition
motion:constraintSetEnd="@+id/end"
motion:constraintSetStart="@+id/start"
motion:duration="1000"
>
<!-- トリガーとなるアクションとViewのidを紐付けます。 -->
<OnClick motion:targetId="@id/main_text_view"/>
</Transition>
<!-- アニメーション開始時のViewの状態を記述します。 -->
<ConstraintSet android:id="@+id/start">
<Constraint
android:id="@+id/main_text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toTopOf="parent"
/>
</ConstraintSet>
<!-- アニメーション終了時のViewの状態を記述します。 -->
<ConstraintSet android:id="@+id/end">
<Constraint
android:id="@+id/main_text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toStartOf="parent"
/>
</ConstraintSet>
</MotionScene>
※3 xmlns:motion="~"を含めれば、MotionSceneという命名でなく、どのような名前を付けても認識してくれます。
最後に、MotionLayoutとアニメーションファイルを紐づけてやります。
<androidx.constraintlayout.motion.widget.MotionLayout
〜 省略 〜
app:layoutDescription="@xml/motion_scene_main"
>
端末で確認するとしてみます。
テキストをタップするとしっかりアニメーションしていますね。
MotionLayoutはView自体をアニメーションさせるだけでなく、Viewのパラメータを動的に変更することもできます。
アニメーションファイルに少し手を加えてみます。
<?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"
>
<Transition
motion:constraintSetEnd="@+id/end"
motion:constraintSetStart="@+id/start"
motion:duration="1000"
>
<OnClick motion:targetId="@id/main_text_view" />
</Transition>
<ConstraintSet android:id="@+id/start">
<Constraint
android:id="@+id/main_text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toTopOf="parent"
>
<!-- アニメーション開始時のテキストの色を指定します。 -->
<CustomAttribute
motion:attributeName="textColor"
motion:customColorValue="#ff0000"
/>
</Constraint>
</ConstraintSet>
<ConstraintSet android:id="@+id/end">
<Constraint
android:id="@+id/main_text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
>
<!-- アニメーション終了時のテキストの色を指定します。 -->
<CustomAttribute
motion:attributeName="textColor"
motion:customColorValue="#0000ff"
/>
</Constraint>
</ConstraintSet>
</MotionScene>
CustomAttribute
というタグは、attributeNameに指定したViewのパラメータをアニメーションに合わせて変更してくれます。
色を変える他に、アニメーションに合わせてViewを変形させたり、画像をクロスフェードさせたりできます。
CustomAttributeを複数指定することももちろん可能です。
ここでは特に触れないので気になった方は調べてみてください。
追記1
上記ではアニメーションファイルにトリガーとなるViewを紐づけていましたが、実際のプロジェクトでは、コードで指定する方が一般的かと思います。
<!-- ここは削除して構いません。 -->
<OnClick motion:targetId="@id/main_text_view" />
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val motionLayout = findViewById<MotionLayout>(R.id.main_motion_layout)
// トリガーとなるViewのクリック時に、指定したid※4のアニメーションを開始します。
findViewById<TextView>(R.id.main_text_view).setOnClickListener {
// 現在のアニメーションの状態を判定して、開始するアニメーションを決めます。
if (motionLayout.currentState == R.id.start) {
motionLayout.transitionToEnd()
} else {
motionLayout.transitionToStart()
}
}
}
}
※4 アニメーションファイルで定義したConstraintSetのidです。
追記2
要件によってはアニメーションの進行状況を検知したい場合があると思います。
そんな時は、 MotionLayout.TransitionListener
をセットすればコールバックが得られます。
motionLayout.setTransitionListener(object : MotionLayout.TransitionListener {
override fun onTransitionTrigger(motionLayout: MotionLayout?, triggerId: Int, positive: Boolean, progress: Float) {
// トリガーした時に呼ばれます。
}
override fun onTransitionStarted(motionLayout: MotionLayout?, startId: Int, endId: Int) {
// アニメーション開始時に呼ばれます。
}
override fun onTransitionChange(motionLayout: MotionLayout?, startId: Int, endId: Int, progress: Float) {
// アニメーション中に呼ばれます。
}
override fun onTransitionCompleted(motionLayout: MotionLayout?, currentId: Int) {
// アニメーション終了時に呼ばれます。
}
})
感想
MotionLayoutの魅力は、ほぼ既存の実装に影響せず、線形補完の滑らかなアニメーションで手軽にリッチな表現ができるところにあると思いました。また、学習コストが比較的低いのも◎。
参考