LoginSignup
8
0

More than 3 years have passed since last update.

【Android】独自のConstraintHelperを作成し、MotionSceneをちょっとスッキリさせてみる

Posted at

この記事は、and factory Advent Calendar 202020日目の記事です。
昨日は@kawamoto_kazuyaさんの「GoでgRPCの構築」でした!

はじめに

MotionLayoutいいですよね!
アニメーションをxmlで宣言的に定義できたり、Android StudioのMotion Editorもなかなかに便利です。

そんなMotionLayoutですが、いつの間にかMotionSceneのxmlファイルの記述が多くなってしまうこともあるかと思います。

そこで本記事では、独自のConstraintHelperを作成することで、MotionSceneファイルをちょっとスッキリさせてみる方法をご紹介します。

※ 本記事の内容は、ConstraintLayout ver.2.0.4で確認しています。

サンプル

矢印をドラッグすることで、ドロイド君の透明度を徐々に1→0にするアニメーションを作ってみます。

MotionSceneファイルは下記のような感じです。

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

    <Transition
        app:constraintSetEnd="@id/end"
        app:constraintSetStart="@id/start">
        <OnSwipe
            app:dragDirection="dragRight"
            app:touchAnchorId="@id/arrow"
            app:touchAnchorSide="right" />
    </Transition>

    <ConstraintSet android:id="@+id/start">
        <Constraint android:id="@id/arrow">
            <Layout
                android:layout_width="60dp"
                android:layout_height="60dp"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent" />
        </Constraint>
    </ConstraintSet>

    <ConstraintSet android:id="@+id/end">
        <Constraint android:id="@id/arrow">
            <Layout
                android:layout_width="60dp"
                android:layout_height="60dp"
                app:layout_constraintEnd_toEndOf="parent" />
        </Constraint>

        <Constraint android:id="@id/droid">
            <PropertySet android:alpha="0" />
        </Constraint>
    </ConstraintSet>
</MotionScene>
  1. ドラッグできるように、矢印( @id/arrow )に対して OnSwipe の指定をする。

  2. 矢印は、開始時点( @id/start )では画面左になるように制約を付け、終了時点( @id/end )で画面右になるように制約を付ける。

  3. ドロイド君( @id/droid )は、終了時点で透明度( alpha )が0になるように制約を付ける。

これだけで、矢印をドラッグすることでドロイド君を徐々に透明にするアニメーションを実現できます。(MotionLayout便利:grinning:

ドロイド君が増殖した場合

ただ、上記の例ではドロイド君が1体だったのでスッキリと書けていますが、ドロイド君が10体に増殖すると…

GroupLayerでは、透明度( alpha )に関してはサポートしていないため使うことができず、ドロイド君10体に対し、それぞれ愚直に Constraint の制約を付ける必要が出てきてしまいます。

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

    <!-- 省略 -->

    <ConstraintSet android:id="@+id/end">
        <Constraint android:id="@id/arrow">
            <Layout
                android:layout_width="60dp"
                android:layout_height="60dp"
                app:layout_constraintEnd_toEndOf="parent" />
        </Constraint>

        <Constraint android:id="@id/droid_1">
            <PropertySet android:alpha="0" />
        </Constraint>

        <Constraint android:id="@id/droid_2">
            <PropertySet android:alpha="0" />
        </Constraint>

        <Constraint android:id="@id/droid_3">
            <PropertySet android:alpha="0" />
        </Constraint>

        <Constraint android:id="@id/droid_4">
            <PropertySet android:alpha="0" />
        </Constraint>

        <Constraint android:id="@id/droid_5">
            <PropertySet android:alpha="0" />
        </Constraint>

        <Constraint android:id="@id/droid_6">
            <PropertySet android:alpha="0" />
        </Constraint>

        <Constraint android:id="@id/droid_7">
            <PropertySet android:alpha="0" />
        </Constraint>

        <Constraint android:id="@id/droid_8">
            <PropertySet android:alpha="0" />
        </Constraint>

        <Constraint android:id="@id/droid_9">
            <PropertySet android:alpha="0" />
        </Constraint>

        <Constraint android:id="@id/droid_10">
            <PropertySet android:alpha="0" />
        </Constraint>
    </ConstraintSet>
</MotionScene>

独自のConstraintHelperを作成してみる

そこで、透明度を一括で適用できる独自のConstraintHelperを作成し、MotionSceneファイルをちょっとスッキリさせてみたいと思います。

ConstraintHelperを継承して作られているGroup.java を参考にしつつ、自作したクラスが下記となります。(Opacity.kt

Opacity.kt
package app.example.sample

import android.content.Context
import android.util.AttributeSet
import androidx.constraintlayout.widget.ConstraintHelper
import androidx.constraintlayout.widget.ConstraintLayout

class Opacity @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0,
) : ConstraintHelper(context, attrs, defStyleAttr) {

    override fun init(attrs: AttributeSet?) {
        super.init(attrs)
        this.mUseViewMeasure = false // ①
    }

    // ②
    override fun onAttachedToWindow() {
        super.onAttachedToWindow()
        updateReferencedViewsAlpha()
    }

    // ③
    override fun setAlpha(alpha: Float) {
        super.setAlpha(alpha)
        updateReferencedViewsAlpha()
    }

    // ④
    private fun updateReferencedViewsAlpha() {
        val parent = parent
        if (parent is ConstraintLayout) {
            getViews(parent).forEach { view ->
                view?.alpha = alpha
            }
        }
    }
}
  1. 参考にしたGroup.javaでも呼んでいたため同様に呼んでいますが、内部的には onMeasure() 時に無駄な計算をしないためのフラグのようでした。

  2. ViewがWindowにアタッチされたタイミングで処理を行いたいので定義。

  3. 透明度が更新されたタイミングでも処理を行いたいので定義。

  4. このクラスの肝となるメソッドです。 getViews(parent)ConstraintHelper に定義されているメソッドで、 constraint_referenced_ids で指定されたidのViewたちを取得するメソッドです。Opacity.ktではViewたちを取得し、それぞれで alpha を指定してあげている感じです。

使ってみる

レイアウトファイルでは、 Opacity 追加し、 constraint_referenced_ids でドロイド君のImageViewのidをセットします。

activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.motion.widget.MotionLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layoutDescription="@xml/activity_main_scene">

    <!-- 省略(ドロイド君のImageViewがdroid_1, droid_2,...と10個並びます) -->

    <!-- 矢印 -->
    <ImageView
        android:id="@+id/arrow"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/ic_baseline_forward_24" />

    <app.example.sample.Opacity
        android:id="@+id/opacity"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:constraint_referenced_ids="droid_1,droid_2,droid_3,droid_4,droid_5,droid_6,droid_7,droid_8,droid_9,droid_10" />
</androidx.constraintlayout.motion.widget.MotionLayout>

MotionSceneファイルでは、愚直に書いていた制約を削除し、 @id/opacity への制約だけにすることができました…!(スッキリ)

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

    <!-- 省略 -->

    <ConstraintSet android:id="@+id/end">
        <Constraint android:id="@id/arrow">
            <Layout
                android:layout_width="60dp"
                android:layout_height="60dp"
                app:layout_constraintEnd_toEndOf="parent" />
        </Constraint>

        <!-- @id/opacityへの指定だけになった! -->
        <Constraint android:id="@id/opacity">
            <PropertySet android:alpha="0" />
        </Constraint>
    </ConstraintSet>
</MotionScene>

差分は下記のような感じに。

ちょっとスッキリさせることができたのではないでしょうか。

終わりに

独自のConstraintHelperを作成することで、MotionSceneファイルをちょっとスッキリさせてみる方法をご紹介しました。

今回は透明度を変更するという簡単な機能でしたが、うまく使うとさらに効率よくMotionLayout・ConstraintLayoutを実装することができそうなので、便利だなと感じました。

ConstraintLayout 2.1.0では、CarouselというHelperクラスも出てくるようなので、今後もぞくぞくと便利なHelperクラスが誕生してくるのかもしれません。

参考 :pray:

8
0
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
8
0