閱讀說明:
- 本文假設讀者已掌握如何使用
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
檔案中就可以不需要佈局檔案了。
佈局檔案預覽:
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
:當前約束關聯到的那個View
的id
。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>
複製程式碼
效果預覽:
<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
:設定屬性的值(布林型別)。