原文連結: Introduction to MotionLayout (part II)
前言
這是系列文章「MotionLayout 介紹」的 part II。閱讀之前請先檢視 part I! (中文點這)
在文字中,我們將繼續通過各種示例揭露基本的 MotionLayout 特性,介紹自定義屬性 (custom attribute)、影像操作 (image operations) 和關鍵幀 (keyframes)。
示例 03: 自定義屬性 (Custom attribute)
在part I的最後,我們建立了一個引用MotionScene
的MotionLayout
(self-contained MotionScene)。我們可以進一步利用它,來實現其他屬性的過渡。
事實上,最初的ConstraintSet
只封裝了佈局規則;但是為了豐富動畫效果,我們經常需要做些別的事(比如背景顏色)。在 ConstraintLayout 2.0,ConstraintSet
也可以儲存自定義屬性狀態。檢視下方動畫,背景顏色跟著移動過程發生了改變。
以前,你不得不在程式碼中處理此問題。現在,你可以直接通過 XML 指定屬性:
<Constraint
android:id="@+id/button" ...>
<CustomAttribute
motion:attributeName="backgroundColor"
motion:customColorValue="#D81B60"/>
</Constraint>
複製程式碼
這是這個動畫修改後的MotionScene
檔案
<?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"
motion:interpolator="linear">
<OnSwipe
motion:dragDirection="dragRight"
motion:touchAnchorId="@+id/button"
motion:touchAnchorSide="right" />
</Transition>
<ConstraintSet android:id="@+id/start">
<Constraint
android:id="@+id/button"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_marginStart="8dp"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toTopOf="parent">
<CustomAttribute
motion:attributeName="backgroundColor"
motion:customColorValue="#D81B60" />
</Constraint>
</ConstraintSet>
<ConstraintSet android:id="@+id/end">
<Constraint
android:id="@+id/button"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_marginEnd="8dp"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintTop_toTopOf="parent">
<CustomAttribute
motion:attributeName="backgroundColor"
motion:customColorValue="#9999FF" />
</Constraint>
</ConstraintSet>
</MotionScene>
複製程式碼
自定義屬性是用屬性名字 (attributeName) 來指定的,屬性名字需要和物件中的getter/setter
方法對應:
- getter:
getName
(e.g. getBackgroundColor) - setter:
setName
(e.g. setBackgroundColor)
另外還需要指定屬性值的型別:
customColorValue
customIntegerValue
customFloatValue
customStringValue
customDimension
customBoolean
最後當我們定義一個自定義屬性,你需要同時定義開始 (start) 和結束 (end) 的ConstraintSet
示例 04 : 影像濾鏡(ImageFilterView)(1/2)
當我們處理複雜的過渡時,常常需要對影像進行一些操作,並且對他們進行動畫處理。ConstraintLayout2.0 引入了名為 ImageFilterView (繼承與AppCompatImageView)的一個有用的工具類來輕鬆實現這一點。
下面是我們在兩張圖片之間做的淡入淡出 (cross-fade) 效果:
首先我們需要建立一個包含 ImageFilterView 的 MotionLayout 檔案。
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.motion.MotionLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/motionLayout"
app:layoutDescription="@xml/scene_04"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.constraint.utils.ImageFilterView
android:id="@+id/image"
android:background="@color/colorAccent"
android:src="@drawable/roard"
app:altSrc="@drawable/hoford"
android:layout_width="64dp"
android:layout_height="64dp"/>
</android.support.constraint.motion.MotionLayout>
複製程式碼
它與 ImageView 的主要區別在於altSrc
屬性
<android.support.constraint.image.ImageFilterView
android:id="@+id/image"
...
android:src="@drawable/roard"
app:altSrc="@drawable/hoford"/>
複製程式碼
在MotionScene 檔案中使用對應的淡入淡出屬性(crossfade
)
<?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"
motion:interpolator="linear">
<OnSwipe
motion:dragDirection="dragRight"
motion:touchAnchorId="@+id/image"
motion:touchAnchorSide="right" />
</Transition>
<ConstraintSet android:id="@+id/start">
<Constraint
android:id="@+id/image"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_marginStart="8dp"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toTopOf="parent">
<CustomAttribute
motion:attributeName="crossfade"
motion:customFloatValue="0" />
</Constraint>
</ConstraintSet>
<ConstraintSet android:id="@+id/end">
<Constraint
android:id="@+id/image"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_marginEnd="8dp"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintTop_toTopOf="parent">
<CustomAttribute
motion:attributeName="crossfade"
motion:customFloatValue="1" />
</Constraint>
</ConstraintSet>
</MotionScene>
複製程式碼
示例 05 : 影像濾鏡(ImageFilterView)(1/2)
ImageFilterView 也提供了更多的功能:
飽和度 saturation : 0 = grayscale, 1 = original, 2 = hyper saturated
對比度 contrast : 1 = unchanged, 0 = gray, 2 = high contrast
色溫 warmth : 1 = neutral, 2 = warm (red tint), 0.5 = cold (blue tint)
淡入淡出 crossfade (withapp:altSrc
)
這裡有另外一個例子顯示怎麼使用濾鏡飽和度:
簡單的指定自定義屬性就可以操作飽和度:
<CustomAttribute
motion:attributeName="saturation"
motion:customFloatValue="1" />
複製程式碼
這裡是這個例子使用的 MotionLayout 檔案:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.motion.MotionLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/motionLayout"
app:layoutDescription="@xml/scene_05"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.constraint.utils.ImageFilterView
android:id="@+id/image"
android:src="@drawable/sunset2"
android:scaleType="centerCrop"
android:layout_width="match_parent"
android:layout_height="300dp" />
</android.support.constraint.motion.MotionLayout>
複製程式碼
這裡是對應的場景(Scene)檔案:
<?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:constraintSetStart="@+id/start"
motion:constraintSetEnd="@+id/end"
motion:duration="1000">
<OnSwipe
motion:touchAnchorId="@+id/image"
motion:touchAnchorSide="top"
motion:dragDirection="dragUp" />
</Transition>
<ConstraintSet android:id="@+id/start">
<Constraint
android:id="@+id/image"
android:layout_width="match_parent"
android:layout_height="300dp"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toTopOf="parent">
<CustomAttribute
motion:attributeName="saturation"
motion:customFloatValue="1" />
</Constraint>
</ConstraintSet>
<ConstraintSet android:id="@+id/end">
<Constraint
android:id="@+id/image"
android:layout_width="match_parent"
android:layout_height="300dp"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintEnd_toEndOf="parent">
<CustomAttribute
motion:attributeName="saturation"
motion:customFloatValue="0" />
</Constraint>
</ConstraintSet>
</MotionScene>
複製程式碼
關鍵幀(Keyframes)
大多數情況下,MotionLayout 是使用“靜止狀態” (resting states) 的 ConstraintSets 實現的。這種方式下,我們知道最終的佈局結果將適應不同的螢幕尺寸:從本質上來說,MotionLayout的表現像一個典型的 ConstraintLayout。
原文:The general idea for MotionLayout is that “resting states” are implemented as ConstraintSets. This way, we know that the resulting layouts will correctly adapt to different screen sizes: essentially, MotionLayout will behave like a typical ConstraintLayout.
在某些情況,你可能希望有一箇中間狀態——一個要經過的狀態(a state to go through),而不是要停留的狀態(not a state to stay in)。當然你可以指定兩個以上的 ConstraintSets 來解決,但是更好的做法是使用 Keyframes。
關鍵幀可以應用於位置或屬性值。它們主要讓你在轉換期間指定一個時間節點上的改變。
舉個例子,你可能想讓一個控制元件在過渡進行到 25% 的時候轉換變成紅色。或者在過渡中的 50% 時,改為向上移動。
示例 06 : 關鍵幀 (1/2), 座標(postion)
有多種設定位置關鍵幀 (KeyPosition) 的方法 (pathRelative、deltaRelative、parentRelative),我們將在本系列的第4部分詳細介紹。
簡單介紹位置關鍵幀 (position keyframes),這裡指定了在過渡進行到 50% 的時候,位置在螢幕高度的 25%處。
<Transition ...>
<KeyFrameSet>
<KeyPosition
motion:keyPositionType="parentRelative"
motion:percentY="0.25"
motion:framePosition="50"
motion:target="@+id/button"/>
</KeyFrameSet>
</Transition>
複製程式碼
最終的效果如下:
和以往一樣,MotionLayout 檔案仍然非常簡單:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.motion.MotionLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/motionLayout"
app:layoutDescription="@xml/scene_06"
android:layout_width="match_parent"
android:layout_height="match_parent">
<View
android:id="@+id/button"
android:background="@color/colorAccent"
android:layout_width="64dp"
android:layout_height="64dp" />
</android.support.constraint.motion.MotionLayout>
複製程式碼
MotionScene 檔案與我們之前看到的非常相似,只是新增了一個KeyPosition
元素:
<?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:constraintSetStart="@+id/start"
motion:constraintSetEnd="@+id/end"
motion:duration="1000"
motion:interpolator="linear">
<OnSwipe
motion:touchAnchorId="@+id/button"
motion:touchAnchorSide="right"
motion:dragDirection="dragRight" />
<KeyFrameSet>
<KeyPosition
motion:keyPositionType="parentRelative"
motion:percentY="0.25"
motion:framePosition="50"
motion:target="@+id/button"/>
</KeyFrameSet>
</Transition>
<ConstraintSet android:id="@+id/start">
<Constraint
android:id="@+id/button"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_marginStart="8dp"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toTopOf="parent">
<CustomAttribute
motion:attributeName="backgroundColor"
motion:customColorValue="#D81B60"/>
</Constraint>
</ConstraintSet>
<ConstraintSet android:id="@+id/end">
<Constraint
android:id="@+id/button"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_marginEnd="8dp"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintTop_toTopOf="parent">
<CustomAttribute
motion:attributeName="backgroundColor"
motion:customColorValue="#9999FF"/>
</Constraint>
</ConstraintSet>
</MotionScene>
複製程式碼
Example 07 : 關鍵幀 (2/2), 屬性(attribute)
類似位置關鍵幀,你可以在過渡中的特定的指定屬性值(使用KeyAttribute
)。
例如,我們可能需要操縱物件在 50% 位置的時候,指定執行縮放和旋轉,效果如下:
這可以通過在KeyFrameSet
中新增一個KeyAttribute
元素來實現:
<KeyFrameSet>
<KeyAttribute
android:scaleX="2"
android:scaleY="2"
android:rotation="-45"
motion:framePosition="50"
motion:target="@id/button" />
</KeyFrameSet>
複製程式碼
MotionLayout 檔案與前一個例子相同,唯一不同的是 MotionScene 檔案中新增了KeyAttribute
:
<?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:constraintSetStart="@+id/start"
motion:constraintSetEnd="@+id/end"
motion:duration="1000"
motion:interpolator="linear">
<OnSwipe
motion:touchAnchorId="@+id/button"
motion:touchAnchorSide="right"
motion:dragDirection="dragRight" />
<KeyFrameSet>
<KeyAttribute
android:scaleX="2"
android:scaleY="2"
android:rotation="-45"
motion:framePosition="50"
motion:target="@id/button" />
<KeyPosition
motion:keyPositionType="screenRelative"
motion:percentY="0.2"
motion:framePosition="50"
motion:target="@id/button"/>
</KeyFrameSet>
</Transition>
<ConstraintSet android:id="@+id/start">
<Constraint
android:id="@+id/button"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_marginStart="8dp"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toTopOf="parent">
<CustomAttribute
motion:attributeName="backgroundColor"
motion:customColorValue="#D81B60"/>
</Constraint>
</ConstraintSet>
<ConstraintSet android:id="@+id/end">
<Constraint
android:id="@+id/button"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_marginEnd="8dp"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintTop_toTopOf="parent">
<CustomAttribute
motion:attributeName="backgroundColor"
motion:customColorValue="#9999FF"/>
</Constraint>
</ConstraintSet>
</MotionScene>
複製程式碼
結論
第二章介紹了 MotionLayout 更高階的功能,給出瞭如何利用自定義屬性和關鍵幀建立更引入注目的動畫示例。
你可以在ConstraintLayout examples github repository檢視這些示例的原始碼。
在本系列文章中還有更多內容:
- Introduction to MotionLayout (part I)
- Custom attributes, image transitions, keyframes (part II)
- Taking advantage of MotionLayout in your existing layouts (CoordinatorLayout, DrawerLayout, ViewPager) (part III)
- All about Keyframes! (part IV)
- MotionLayout as a choreographer of root layout
- Nesting MotionLayout & other Views
- MotionLayout with fragments