本文簡要介紹如何使用 Composition ExpressionAnimations 製作基於關係的動畫。
動態關係式經驗
在應用程式中建構動態體驗時,有時動態並非基於時間,而是依賴於另一個物件上的屬性。 KeyFrameAnimations 無法輕易表達這類動態體驗。 在這些特定情況下,運動不再需要是離散且預先定義的。 相反地,運動可以根據與其他物體屬性的關係動態調整。 例如,你可以根據物件的水平位置來製作不透明度動畫。 其他例子包括像 Sticky Headers 和 Parallax 這類動態體驗。
這類動態體驗讓你創造出更有連結感的使用者介面,而非單一且獨立的感覺。 對使用者來說,這給人一種動態介面體驗的印象。
使用「ExpressionAnimations」
要建立基於關係的動態體驗,你可以使用 ExpressionAnimation 類型。 ExpressionAnimations(簡稱 Expressions)是一種新型態的動畫,讓你能表達數學關係——系統用來計算每一幀動畫屬性的值。 換句話說,表達式就是一個數學方程式,定義每幀動畫屬性的期望值。 表達式是一個非常多功能的元件,可用於多種情境,包括:
- 相對大小和偏移動畫。
- 黏性標題,搭配 ScrollViewer 進行視差效果。 (參見 提升現有 ScrollViewer 體驗。)
- 用 InertiaModifiers 和 InteractionTracker 來捕捉點數。 (參見 「建立帶有慣性修飾器的吸附點」。)
使用 ExpressionAnimations 時,有幾點值得一開始就說明:
- Never Ending——與 KeyFrameAnimation 的姊妹作品不同,Expressions 沒有有限的持續時間。 因為表達式是數學關係,它們是持續「運行」的動畫。 你可以選擇停止這些動畫。
- 運行中,但不一定能評估——持續運行的動畫效能總是令人擔憂的。 不過不用擔心,系統夠聰明,只有當任何輸入或參數改變時,表達式才會重新評估。
- 解析到正確的物件類型——由於表達式是數學關係,因此必須確保定義表達式的方程式解析為動畫所針對屬性的相同類型。 例如,如果要動畫 Offset,你的表達式應該會解析成 Vector3 類型。
表達式的組成部分
在建立表達式的數學關係時,有幾個核心組成部分:
- 參數——代表常數值或參考其他 Composition 物件的值。
- 數學運算子——典型的數學運算子加(+)、減(-)、乘(*)、除(/),將參數結合形成方程式。 同時也包含條件運算子,如大於(>)、等(==)、三元運算子(條件 ? ifTrue : ifFalse)等。
- 數學函數 – 基於 System.Numerics 的數學函數/捷徑。 欲了解完整的支援函式清單,請參見 ExpressionAnimation。
表達式也支援一組關鍵字——這些特殊片語僅在 ExpressionAnimation 系統中具有獨特意義。 這些功能(連同完整的數學函式清單)都列在 ExpressionAnimation 文件中。
使用 ExpressionBuilder 建立表達式
在 WinUI 應用程式中建立表達式有兩種選項:
- 透過官方公開的 API 將方程式建成字串。
- 透過 Windows 社群工具包附贈的 ExpressionBuilder 工具,將方程式建置成型別安全的物件模型。
為了本文,我們將使用 ExpressionBuilder 來定義我們的表達式。
參數
參數構成表達式的核心。 參數分為兩種類型:
- 常數:這些參數代表特定類型的 System.Numeric 變數。 這些參數在動畫開始時會被分配一次值。
- 參考:這些參數代表對 CompositionObjects 的參考——這些參數在動畫開始後會持續更新其值。
一般而言,引用是表達式輸出如何動態變化的主要因素。 隨著這些參考改變,表達式的輸出也會隨之改變。 如果你用字串建立表達式,或在模板化情境中使用(用表達式針對多個 CompositionObjects),你需要命名並設定參數的值。 更多資訊請參見範例章節。
使用關鍵幀動畫 (KeyFrameAnimations)
表達式也可以搭配 KeyFrameAnimations 一起使用。 在這些情況下,你會想用表達式來定義某個時間點的關鍵影格值——這類關鍵影格稱為表達關鍵幀。
KeyFrameAnimation.InsertExpressionKeyFrame(Single, String)
KeyFrameAnimation.InsertExpressionKeyFrame(Single, ExpressionNode)
然而,與 ExpressionAnimations 不同的是,ExpressionKeyFrames 在啟動 KeyFrameAnimation 時只會被評估一次。 請記住,你不應該直接使用 ExpressionAnimation 作為 KeyFrame 的值,而是應使用字串(如果使用 ExpressionBuilder,則為 ExpressionNode)。
範例
現在讓我們來示範一個使用 Expressions 的範例,特別是來自 Windows UI 範例庫的 PropertySet 範例。 我們將探討管理藍球軌道運動行為的表達式。
整體體驗由三個要素共同構成:
- 一個關鍵幀動畫,負責動畫紅色球的 Y 偏移。
- 一個帶有 旋轉 屬性的 PropertySet,可幫助驅動軌道運行,並由另一個 KeyFrameAnimation 進行動畫化。
- ExpressionAnimation 用於驅動藍球的偏移量,並參考紅球的偏移量和旋轉屬性,以維持完美的軌道。
我們將專注於 #3 中定義的 ExpressionAnimation。 我們也會使用 ExpressionBuilder 類別來建構這個表達式。 最後列出了用來透過字串建立這種體驗的程式碼副本。
在這個方程式中,你需要參考 PropertySet 中的兩個屬性;一個是中心點偏移,另一個是旋轉。
var propSetCenterPoint =
_propertySet.GetReference().GetVector3Property("CenterPointOffset");
// This rotation value will animate via KFA from 0 -> 360 degrees
var propSetRotation = _propertySet.GetReference().GetScalarProperty("Rotation");
接著,你需要定義 Vector3 分量,以考慮實際的軌道旋轉。
var orbitRotation = EF.Vector3(
EF.Cos(EF.ToRadians(propSetRotation)) * 150,
EF.Sin(EF.ToRadians(propSetRotation)) * 75, 0);
備註
EF 是一種簡寫「using」語法,用來從您的 WinUI 表達式建構函式庫中定義 ExpressionFunctions。
最後,將這些成分結合起來,並參考紅球的位置來定義數學關係。
var orbitExpression = redSprite.GetReference().Offset + propSetCenterPoint + orbitRotation;
blueSprite.StartAnimation("Offset", orbitExpression);
假設你想用同樣的表達式,但同時搭配另外兩個視覺元素,也就是兩組繞圈的圓圈。 使用 CompositionAnimations,你可以重複使用動畫並針對多個 CompositionObjects。 當你使用這個表達式作為額外軌道情況時,唯一需要更改的是對視覺的參考。 我們稱之為模板化。
在這種情況下,你修改了之前建立的表達式。 你不是「取得」CompositionObject 的參考,而是建立一個帶有名稱的參考,然後賦予不同的值:
var orbitExpression = ExpressionValues.Reference.CreateVisualReference("orbitRoundVisual");
orbitExpression.SetReferenceParameter("orbitRoundVisual", redSprite);
blueSprite.StartAnimation("Offset", orbitExpression);
// Later on … use same Expression to assign to another orbiting Visual
orbitExpression.SetReferenceParameter("orbitRoundVisual", yellowSprite);
greenSprite.StartAnimation("Offset", orbitExpression);
如果你透過公開 API 定義了帶有字串的表達式,以下是程式碼。
ExpressionAnimation expressionAnimation = compositor.CreateExpressionAnimation("visual.Offset + " +
"propertySet.CenterPointOffset + " +
"Vector3(cos(ToRadians(propertySet.Rotation)) * 150," + "sin(ToRadians(propertySet.Rotation)) * 75, 0)");
var propSetCenterPoint = _propertySet.GetReference().GetVector3Property("CenterPointOffset");
var propSetRotation = _propertySet.GetReference().GetScalarProperty("Rotation");
expressionAnimation.SetReferenceParameter("propertySet", _propertySet);
expressionAnimation.SetReferenceParameter("visual", redSprite);