LoginSignup
17
5

More than 3 years have passed since last update.

【Android】MotionLayoutを触ってみる

Last updated at Posted at 2020-03-05

MotionLayoutとは

Viewのアニメーションを管理できるViewGroupです。

ConstraintLayoutのサブクラスで、ConstraintLayoutを利用しているレイアウトなら、まるっとMotionLayoutに置き換えることができます。

※MotionLayoutはAPIレベル14との下位互換性があります。

導入

gradleに依存関係を追加します。
MotionLayoutは ConstraintLayoutのライブラリの2.0.0から導入 されたものですので、ConstraintLayoutのバージョンを上げる必要があります。
※2020/3/5の時点での最新バージョンは2.0.0-beta4でした。

build.gradle(app)
// 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を適応してみます。

activity_main.xml
<!-- 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/ にアニメーションファイルを作成します。

res/xml/motion_scene_main.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とアニメーションファイルを紐づけてやります。

activity_main.xml
<androidx.constraintlayout.motion.widget.MotionLayout

     省略 

    app:layoutDescription="@xml/motion_scene_main"
    >

端末で確認するとしてみます。
テキストをタップするとしっかりアニメーションしていますね。
20200305.gif

MotionLayoutはView自体をアニメーションさせるだけでなく、Viewのパラメータを動的に変更することもできます。
アニメーションファイルに少し手を加えてみます。

res/xml/motion_scene_main.xml
<?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>

20200305_2.gif

CustomAttribute というタグは、attributeNameに指定したViewのパラメータをアニメーションに合わせて変更してくれます。
色を変える他に、アニメーションに合わせてViewを変形させたり、画像をクロスフェードさせたりできます。
CustomAttributeを複数指定することももちろん可能です。

ここでは特に触れないので気になった方は調べてみてください。

追記1

上記ではアニメーションファイルにトリガーとなるViewを紐づけていましたが、実際のプロジェクトでは、コードで指定する方が一般的かと思います。

res/xml/motion_scene_main.xml
<!-- ここは削除して構いません。 -->
<OnClick motion:targetId="@id/main_text_view" />
MainActivity.kt
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 をセットすればコールバックが得られます。

MainActivity.kt
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の魅力は、ほぼ既存の実装に影響せず、線形補完の滑らかなアニメーションで手軽にリッチな表現ができるところにあると思いました。また、学習コストが比較的低いのも◎。

参考

17
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
5