在 MotionScene 檔案中定義場景約束

溜了溜了發表於2019-04-11

閱讀說明:

  • 本文假設讀者已掌握如何使用 ConstraintLayout
  • 本文假設讀者已對 MotionLayout 有了基本瞭解,知道如何建立 MotionScene 檔案,以及如何使用 MotionLayout 在兩個 layout 佈局檔案間建立過渡動畫。如您完全不瞭解這些,建議您閱讀 《MotionLayout 基礎教程》
  • 建議讀者在閱讀過程中動手實踐,有助於更好的理解,如您現在不方便,可以稍後閱讀。
  • 由於官方文件不全,部分內容來自筆者的個人理解,如有錯誤,歡迎指正。

本文主要介紹如何在 MotionScene 檔案中定義場景約束與如何使用自定義屬性。

在 MotionScene 檔案中定義約束

可以在 <MotionScene> 元素中使用 <ConstraintSet> 子元素定義一個場景約束集,並在 <ConstraintSet> 元素中使用 <Constraint> 元素定義單個 View 的屬性約束。

:在 MotionScene 檔案中定義 End 場景的約束集

<?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">

    <!-- 定義 End 場景的約束集 -->
    <ConstraintSet android:id="@+id/activity_main_end">

        <!-- 定義佈局中 id 為 image 的 View 的屬性約束 -->
        <Constraint
            android:id="@+id/button"
            android:layout_width="56dp"
            android:layout_height="56dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintVertical_bias="0.0" />

    </ConstraintSet>

    <!-- Start 場景是佈局檔案,End 場景是定義在 MotionScene 檔案中的約束集 -->
    <Transition
        app:constraintSetStart="@layout/activity_main"
        app:constraintSetEnd="@id/activity_main_end"
        app:duration="1000">

        <OnClick
            app:clickAction="toggle"
            app:targetId="@id/button" />

    </Transition>

</MotionScene>
複製程式碼

實際上,你即也可以把 Start 場景的約束集定義在 MotionScene 檔案中,也可以把 End 場景的約束集定義在 MotionScene 檔案中。或者僅在 MotionScene 檔案中定義這兩者之一,另一個場景使用 layout 佈局檔案定義。

建議:建議把 Start 場景和 End 場景的約束集都定義在 MotionScene 檔案中。因為 MotionLayout 框架某些特性(例如自定義屬性(下節會介紹))依賴於 MotionScene 檔案中 Start 場景,如果沒有在 MotionScene 檔案中定義 Start 場景,這些特性可能會無法使用(該建議僅針對 ConstraintLayout 2.0.0-alpha4 版本,因為 ConstraintLayout 2.0 正式版還未釋出,正式釋出會後可能不會存在這個問題)。

:在 MotionScene 檔案中定義 Start 場景約束與 End 場景約束(本示例分為以下兩步)

1. 建立佈局檔案:

檔名: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:id="@+id/motionLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layoutDescription="@xml/activity_main_motion_scene">

    <Button
        android:id="@+id/button"
        android:layout_width="56dp"
        android:layout_height="56dp"
        android:background="@color/colorPrimary"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="1.0" />

</androidx.constraintlayout.motion.widget.MotionLayout>
複製程式碼

提示:佈局檔案還是要有的,並不是說將 Start 場景約束和 End 場景約束都定義在了 MotionScene 檔案中就可以不需要佈局檔案了。

佈局檔案預覽:

在 MotionScene 檔案中定義場景約束

2. 建立 MotionScene 檔案:

檔名:activity_main_motion_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">

    <!-- 定義 Start 場景的屬性約束集 -->
    <ConstraintSet android:id="@+id/activity_main_start">

        <!-- 定義佈局中 id 為 image 的 View 的屬性約束 -->
        <Constraint
            android:id="@id/button"
            android:layout_width="56dp"
            android:layout_height="56dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintVertical_bias="1.0" />

    </ConstraintSet>


    <!-- 定義 End 場景的屬性約束集 -->
    <ConstraintSet android:id="@+id/activity_main_end">

        <!-- 定義佈局中 id 為 image 的 View 的屬性約束 -->
        <Constraint
            android:id="@+id/button"
            android:layout_width="56dp"
            android:layout_height="56dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintVertical_bias="0.0" />

    </ConstraintSet>

    <!-- Start 場景與 End 場景都是定義在 MotionScene 檔案中的約束集 -->
    <Transition
        app:constraintSetStart="@id/activity_main_start"
        app:constraintSetEnd="@id/activity_main_end"
        app:duration="1000">

        <OnClick
            app:clickAction="toggle"
            app:targetId="@id/button" />

    </Transition>

</MotionScene>
複製程式碼

<ConstraintSet> 元素屬性說明:

  • android:id:設定當前約束集的 id。這個 id 值可被 <Transition> 元素的 app:constraintSetStart 或者 app:constraintSetEnd 引用。

<Constraint> 元素屬性說明:

  • android:id:當前約束關聯到的那個 Viewid
  • app:transitionEasing:定義動畫到此點時使用的緩動曲線,該值可以是一個字串(例如 "curve(1.0,0,0,1.0)")還可以是以下幾個列舉值之一:
    • standard:標準
    • accelerate:加速
    • decelerate:減速
    • linear:線性

提示 1<Constraint> 元素的 app:transitionEasing 屬性與 <Transition> 元素的 app:motionInterpolator 屬性類似,都是用來定義過渡動畫的插值器的。不同的是,<Constraint> 元素的 app:transitionEasing 屬性定義的是單獨某個 View 的過渡動畫的插值器,而 <Transition> 元素的 app:motionInterpolator 定義的是整個過渡動畫的插值器。

提示 2:如果為 <Constraint> 元素指定了 app:transitionEasing 插值器,這個插值器將與 app:motionInterpolator 屬性指定的全域性插值器同時作用於 View 的過渡動畫,而不是替換掉 app:motionInterpolator 屬性指定的全域性插值器。

提示 3<Constraint> 元素的 app:transitionEasing 屬性值應該在 Start 場景中指定,(經測試)僅在 End 場景中指定 app:transitionEasing 無法生效。

提示 4:(經測試,似乎無效)可以使用形如 "curve(1.0,0,0,1.0)" 的字串來為 <Constraint> 元素的 app:transitionEasing 屬性設定一個緩動曲線,MotionLayout 框架將根據這個緩動曲線來生成一個插值器。

  • app:transitionPathRotate:【浮點值】(功能未知,文件不全)相對於所採用的路徑旋轉物件。
  • app:drawPath:(功能未知,文件不全)繪製佈局將為動畫設定動畫的路徑。可以是以下幾個列舉值之一:
    • none
    • path
    • pathRelative
    • deltaRelative
    • asConfigured
    • rectangles
  • app:progress:【浮點值】(功能未知,文件不全)在關聯的 View 上呼叫 setProgress(float) 方法(用於與巢狀的 ConstraintLayout 互動)

前面說過,<Constraint> 元素用來定義單個 View 的屬性約束,它支援對 View 的所有 ConstraintLayout 屬性定義約束,以及對 View 的下面這些標準屬性定義約束:

  • android:visibility
  • android:alpha
  • android:elevation
  • android:rotation
  • android:rotationX
  • android:rotationY
  • android:scaleX
  • android:scaleY
  • android:translationX
  • android:translationY
  • android:translationZ

View 的標準屬性或者 ConstraintLayout 屬性發生改變時,MotionLayout 會自動應用過渡動畫。

自定義屬性

MotionLayout 控制元件只會檢測標準屬性和 ConstraintLayout 屬性這類佈局相關的屬性變動,對於其他的屬性變動,如 View 的背景顏色變動是無法檢測出來的,因此就需要使用自定義屬性。

<Constraint> 元素中使用 <CustomAttribute> 子元素來指定自定義屬性。

例:

<?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">

    <!-- 定義 Start 場景的屬性約束集 -->
    <ConstraintSet android:id="@+id/activity_main_start">

        <!-- 定義佈局中 id 為 image 的 View 的屬性約束 -->
        <Constraint
            android:id="@id/button"
            android:layout_width="56dp"
            android:layout_height="56dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintVertical_bias="1.0">

            <!-- 使用自定義屬性 -->
            <CustomAttribute
                app:attributeName="backgroundColor"
                app:customColorValue="@color/colorPrimary" />

        </Constraint>

    </ConstraintSet>


    <!-- 定義 End 場景的屬性約束集 -->
    <ConstraintSet android:id="@+id/activity_main_end">

        <!-- 定義佈局中 id 為 image 的 View 的屬性約束 -->
        <Constraint
            android:id="@+id/button"
            android:layout_width="56dp"
            android:layout_height="56dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintVertical_bias="0.0">

            <!-- 使用自定義屬性 -->
            <CustomAttribute
                app:attributeName="backgroundColor"
                app:customColorValue="@color/colorAccent" />

        </Constraint>

    </ConstraintSet>

    <Transition
        app:constraintSetEnd="@id/activity_main_end"
        app:constraintSetStart="@id/activity_main_start"
        app:duration="1000">

        <OnClick
            app:clickAction="toggle"
            app:targetId="@id/button" />

    </Transition>

</MotionScene>
複製程式碼

效果預覽:

在 MotionScene 檔案中定義場景約束

<CustomAttribute> 元素屬性說明:

  • app:attributeName 屬性用來指定自定義屬性的名字(例如 "backgroundColor")。關聯的 View 必須要有一對與這個名字相關的 getter/setter 方法(例如 getBackgroundColor()/setBackgroundColor(int color))。
  • 剩下的其他屬性都是用來設定自定義屬性的值的。需要根據自定義屬性的值型別使用以下 XML 屬性之一來設定自定義屬性的值:
    • app:customColorValue:設定屬性的值(顏色型別)。
    • app:customColorDrawableValue:設定屬性的值(顏色型別)。
    • app:customIntegerValue:設定屬性的值(整數型別)。
    • app:customFloatValue:設定屬性的值(浮點型別)。
    • app:customStringValue:設定屬性的值(字串型別)。
    • app:customDimension:設定屬性的值(尺寸型別)。
    • app:customPixelDimension:設定屬性的值(尺寸型別)。
    • app:customBoolean:設定屬性的值(布林型別)。

相關文章