本文譯自 Nick Waggoner 的 "Understand what’s possible with the Windows UI Animation Engine",已獲原作者授權進行翻譯。更多有關 Windows UI、UWP 開發的文章,歡迎訪問我的博客源站:http://validvo ...
本文譯自 Nick Waggoner 的 "Understand what’s possible with the Windows UI Animation Engine",已獲原作者授權進行翻譯。更多有關 Windows UI、UWP 開發的文章,歡迎訪問我的博客源站:http://validvoid.net/
2015 年 11 月,視覺層 (Visual Layer)作為 Windows.UI.Composition
命名空間中的一系列新 API 被引入。這些新 API 標志著開發者首次有機會接觸那些支撐著自 Windows 8 以來各種 UI 框架(例如 IE/Edge、XAML 和 Windows Shell)的功能特性。全新視覺層的一個重要方面就是其動畫引擎。但今年在 //build/ 大會上為開發者進行大量講談之後,我發現開發者們任然不太清楚動畫系統的各部分是如何協同工作的。為了幫助你理解動畫系統的潛力,我們通過兩個問題進行理解:
- 誰負責開始動畫?
- 什麼驅動動畫,改變取值?
隱式 vs. 顯式 – 誰負責開始動畫?
顯式動畫和隱式動畫之間的關鍵區別就在於誰負責觸發動畫。
長話短說:顯式動畫你觸發;隱式動畫你配置。
顯式動畫
提到動畫,大部分人想到的都是顯式動畫,對此你應該很熟悉了。對於顯式動畫,你進行設置之後,也由作為開發者你的自己進行觸發。
例如,在 XAML 中通常用標記語言創建動畫,在後臺代碼中觸發動畫。
標記語言代碼:
<Storyboard x:Name="myStoryboard"> <DoubleAnimation From="1" To="6" Duration="00:00:6" Storyboard.TargetName="rectScaleTransform" Storyboard.TargetProperty="ScaleY"> <DoubleAnimation.EasingFunction> <BounceEase Bounces="2" EasingMode="EaseOut" Bounciness="2" /> </DoubleAnimation.EasingFunction> </DoubleAnimation> </Storyboard>
後臺代碼:
private void OnButtonClick(object sender, RoutedEventArgs e) { myStoryboard.Begin(); }
視覺層中的動畫系統同樣也支持顯式動畫——儘管只在你的後臺代碼中。這使你能取用已知動畫配置並直接使用。
後臺代碼:
// 獲取表示此 UIElement 的 Visual (視覺元素對象)並從中獲取 compositor (合成器對象) Visual tempVisual = ElementCompositionPreview.GetElementVisual(this); Compositor compositor = tempVisual.Compositor; // 創建一個簡單的 ScalarKeyFrameAnimation (標量關鍵幀動畫) ScalarKeyFrameAnimation scalarAnimation = compositor.CreateScalarKeyFrameAnimation(); scalarAnimation.Duration = TimeSpan.FromMilliseconds(300); scalarAnimation.InsertKeyFrame(1f, 200f); // 顯式開始動畫 tempVisual.StartAnimation("Offset.X", scalarAnimation);
以上例子的模式都是相同的。你先定義動畫(也就是動畫的時長、運動軌跡、目標屬性以及取值),然後通過 start/begin 方法顯式觸發動畫。
隱式動畫
相對於顯式動畫,隱式動畫則是由平臺觸發的。例如,下列代碼演示瞭如何在 XAML 中為一個按鈕附加 EntranceThemeTransition
:
<Button Content="EntranceThemeTransition Button"> <Button.Transitions> <TransitionCollection> <EntranceThemeTransition /> </TransitionCollection> </Button.Transitions> </Button>
這就是實現效果所需的全部代碼。當按鈕初次呈現時,它會觸發 EntranceThemeTransition
,使其以動畫形式運動到目標位置。在視覺層出現之前,你只有屈指可數的幾個隱式動畫可供選擇,也就是 XAML 過渡效果動畫 (the XAML Transitions),並且幾乎無法對其進行配置。而視覺層不僅支持隱式動畫,還給了你更大的定製空間:
// 創建一個映射表用於儲存觸發器/動畫配對。 ImplicitAnimationMap implicitAnimationCollection = _compositor.CreateImplicitAnimationMap(); // 創建實際要運行的動畫。 var _offsetKeyFrameAnimation = _compositor.CreateVector3KeyFrameAnimation(); _offsetKeyFrameAnimation.InsertExpressionKeyFrame(1.0f, "this.FinalValue"); _offsetKeyFrameAnimation.Duration = TimeSpan.FromSeconds(3); // 設置當 Offest(觸發器) 改變時要運行的動畫。 implicitAnimationCollection["Offset"] = _offsetKeyFrameAnimation; // 應用隱式動畫。 myVisual.ImplicitAnimations = implicitAnimationCollection;
根據這段代碼,無論何時只要 myVisual
的 Offset 發生改變,_offsetKeyFrameAnimation
都會被平臺觸發。註意在這一隱式動畫的定義中用到了一個 ExpressionKeyFrame
,也就是表達式關鍵幀。表達式關鍵幀允許你設置數學表達式,動畫系統在播放表達式動畫 (ExpressionAnimations)的每一幀時都會計算此表達式的值。在我們的例子里,我們使用了一個簡單的表達式 this.FinalValue
,只是對觸發動畫的條件進行取值。這一動畫只是一個非常基本的示例,但通過表達式你能定義任何你想要的動畫。
視覺層隱式動畫的靈活性使得你能夠將應用的邏輯與動效分離,並提供了一種強大的方式定製你的體驗。例如,隱式動畫一種不錯的用法就是對 "Offset" 設置觸發器,這樣你就能創建從一種佈局向另一種佈局動畫過渡的效果,並且該效果是由 XAML 的佈局引擎自動觸發的。
想要深入瞭解隱式動畫,//build/ 大會上的這一講談節目是個不錯的起點。
專業利器 – 什麼驅動動畫?
時間驅動動畫
時間驅動動畫是開發者們熟知並且喜愛的經典動畫類型。上文中的代碼片段展示了 XAML 的 storyboard 動畫以及 composition 的關鍵幀動畫,它們都是時間驅動型動畫。關鍵幀動畫背後的思想(實際上是標準)就是你為特定時間的動畫都指定目標取值,並描述這些取值之間如何過渡(通常成為插值或緩動函數)。XAML 提供了一大批內建的緩動函數幫助你輕鬆實現美觀的動效。而在視覺層中,負責提供緩動動效的則是CubicBezierEasingFunction 類(意為三次貝塞爾緩動函數)。CubicBezierEasingFunction
通過兩個控制點控制運動軌跡。控制點允許你以細粒度方式控制插值。而且鑒於各類動畫引擎中廣泛使用貝塞爾曲線描述緩動,你能輕鬆獲得很多效果不錯的預定義控制點方案。我通常使用 Easings.net 獲取標準平納緩動函數1的控制點。
引用驅動動畫(數學驅動)
在 Windows 10 的 10586 十一月更新中,ExpressionAnimation (表達式動畫)被引入視覺層的動畫引擎。表達式動畫允許你在動畫系統中創建屬性之間在幀間更新的數學關係。視差動畫就是一個經典的表達式動畫:
// 創建驅動視差動畫的表達式。 ExpressionAnimation parallaxAnimation = compositor.CreateExpressionAnimation("MyForeground.Offset.Y / MyParallaxRatio"); // 設置我們希望背景進行視差滾動的速度。 parallaxAnimation.SetScalarParameter("MyParallaxRatio", 0.5f); // 設置前景對象的引用。 parallaxAnimation.SetReferenceParameter("MyForeground", foregroundVisual); // 在背景對象上開始動畫。 backgroundVisual.StartAnimation("Offset.Y", parallaxAnimation);
這段代碼所做的第一件事是創建一個用於描述一些輸入與動畫結果輸出之間關係的數學表達式。表達式中定義了幾個參數和稍後賦值的引用。參數幫助你配置數學關係,但引用才是使表達式靈動的重點。一個參數(例如 MyParallaxRatio
)是通過調用指定類型的函數(例如 SetScalarParameter
)賦值的。此行為通知動畫引擎對該參數的所有實例以你傳入的取值進行求值。求值動作只在將動畫交由引擎處理前發生一次,因此這是一個指定常量取值的好辦法。相反,一個引用(例如 MyForeground
) 則是動畫引擎在每幀求值的。這正是實際使表達式動畫靈動的魔法所在。
此外還有兩點需要指出。首先,你會註意到我們能夠訪問 MyForeground
的成員以及 Y
子通道。表達式的語法允許訪問成員以及“混合”或交換一個矢量/矩陣的成分。例如:
// 重用 offset 的 X 通道創建一個 Vector2 動畫。 ExpressionAnimation vector2Animation = compositor.CreateExpressionAnimation("MyForeground.Offset.X"); // 設置對前景對象的引用。 vector2Animation.SetReferenceParameter("MyForeground", foregroundVisual);
另一點需要指出的是,視覺層中的所有動畫實際上都是模板。這意味著你可以對多個對象使用同一個動畫或重用動畫的結構,只在下一個對象的動畫開始前更新參數和引用。例如,如果我們想要擴展基本視差動畫,添加多層景深效果,我們可以只需要一個動畫定義即可:
// 創建驅動視差滾動的表達式動畫。 ExpressionAnimation parallaxAnimation = compositor.CreateExpressionAnimation("MyForeground.Offset.Y / MyParallaxRatio"); // 設置前景對象引用。 parallaxAnimation.SetReferenceParameter("MyForeground", foregroundVisual); // 設置背景對象視差滾動速度。 parallaxAnimation.SetScalarParameter("MyParallaxRatio", 0.5f); // 對背景對象開始動畫。 backgroundVisual.StartAnimation("Offset.Y", parallaxAnimation); // 設置遠距背景對象的視差滾動速度。 parallaxAnimation.SetScalarParameter("MyParallaxRatio", 0.2f); // 對遠距背景對象開始動畫。 deepBackgroundVisual.StartAnimation("Offset.Y", parallaxAnimation);
表達式動畫是一種全新而強大的動畫方式,使我們能藉以表示物體如何相對運動。表達式動畫為我們免去了設置一系列複雜動畫的痛苦,使多個不同對象和屬性協同運動因而變得更加容易。要深入瞭解表達式動畫,可參見 //build/ 講談:
P486: Using Expression Animations to Create Engaging & Custom UI
輸入驅動動畫
自大約五年前觸摸漸成主流起,創造低延遲體驗成為了一種普遍需求。使用手指或筆在屏幕上操作,使得人眼獲得了更直觀的參照點來辨識操作的延遲和流暢性。為使操作流暢,主流操作系統公司均將更多的操作移交至系統和 GPU (如 Chrome、IE)執行。在 Windows 上,這由 DirectManipulation 這一或多或少是針對於觸摸構建的動畫引擎實現的。它解決了關鍵的延遲挑戰,也就是如何自然地以展示從輸入驅動到事件驅動過渡的動效。但另一方面,它也幾乎沒有提供對定製慣性觀感的支持,就像福特 T 型車那樣——“只要車是黑色的,你可以把它塗成任意你喜歡的顏色”。2
ElementCompositionPreview.GetScrollViewerManipulationPropertySet
是讓你能夠把玩輸入驅動動效的第一步。雖然它仍然沒給你任何對內容滾動時觀感進行控制的額外能力,但它確實允許你對次級內容應用表達式動畫。例如,我們終於能完成我們的基礎視差滾動代碼:
// 創建驅動視差滾動的表達式動畫。 ExpressionAnimation parallaxAnimation = compositor.CreateExpressionAnimation("MyForeground.Offset.Y / MyParallaxRatio"); // 設置對前景對象的引用。 CompositionPropertySet MyPropertySet = ElementCompositionPreview.GetScrollViewerManipulationPropertySet(MyScrollViewer); parallaxAnimation.SetReferenceParameter("MyForeground", MyPropertySet); // 設置背景對象視差滾動的速度。 parallaxAnimation.SetScalarParameter("MyParallaxRatio", 0.5f); // 對背景對象開始視差動畫。 backgroundVisual.StartAnimation("Offset.Y", parallaxAnimation);
使用這一技巧,你能夠實現多種優秀的效果:視差滾動、粘性表頭、自定義滾動條等等。唯一缺失的就是定製操作本身的觀感……
再來看看 InteractionTracker
。這一全新設計的特性在給予你控制操作觀感方方面面的靈活性的同時,保證了手指操作低延遲的體驗。在 Windows 的 UI 平臺上,我們時常談到便利性與和可行性之間的權衡。常規 UX 和調用模式通常被包裝成簡單易用的高級控制項和特性。這確實使得他們簡單易用,但也在一定程度上損失了靈活操控性。而尺度的另一端則是如圖形層(Graphics Layer) 的這類封裝。它們使你能夠完全控制每個像素在屏幕上的呈現,但也帶來了更大的複雜性。在輸入處理的設計中,InteractionTracker
更多地傾向於可行性這一側。如今在 Windows UI 平臺上,你首次能夠描述性地將輸入到輸出映射為具體的動效。
這裡我們以一個簡單的示例,通過修改慣性結束的位置來演示這種全新的靈活性。過去,你通過指定四種 對齊點(Snap-points) 類型中的一種來修改 XAML 中 ScrollViewer
的慣性表現。現在,有了 InteractionTracker
提供的更多種可能性,你可以使用表達式動畫來定義慣性在哪結束。下麵是一個例子,基於慣性自然停止的位置創建了三種不同的對齊點方案:
// 創建一個慣性端點,其條件與結束點在面板近側。 // 變數在稍後公有變數更新後填入。 var snapNearConditionExpression = s_compositor.CreateExpressionAnimation("target.Position.X < - target.CompletionThreshold"); var snapNearValueExpression = s_compositor.CreateExpressionAnimation("-target.CompletedOffset"); var snapNearEndpoint = InteractionTrackerInertiaRestingValue.Create(s_compositor); snapNearEndpoint.Condition = snapNearConditionExpression; snapNearEndpoint.RestingValue = snapNearValueExpression; // 創建一個慣性端點,其條件與結束點在面板遠側。 // 變數在稍後公有變數更新後填入。 var snapFarConditionExpression = s_compositor.CreateExpressionAnimation("target.Position.X > target.CompletionThreshold"); var snapFarValueExpression = s_compositor.CreateExpressionAnimation("target.CompletedOffset"); var snapFarEndpoint = InteractionTrackerInertiaRestingValue.Create(s_compositor); snapFarEndpoint.Condition = snapFarConditionExpression; snapFarEndpoint.RestingValue = snapFarValueExpression; // 創建一個總慣性控制端點,用於控制如果沒有其它慣性修改器生效則歸為至靜息狀態。 var snapHomeEndpoint = InteractionTrackerInertiaRestingValue.Create(s_compositor); snapHomeEndpoint.Condition = s_compositor.CreateExpressionAnimation("true"); snapHomeEndpoint.RestingValue = s_compositor.CreateExpressionAnimation("0"); // 插入慣性端點表達式引用的屬性。 s_interactionTracker.Properties.InsertScalar(nameof(CompletedOffset), (float)m_completedOffset); s_interactionTracker.Properties.InsertScalar(nameof(CompletionThreshold), (float)m_completionThreshold); s_interactionTracker.ConfigurePositionXInertiaModifiers( new InteractionTrackerInertiaModifier[] { snapNearEndpoint, snapFarEndpoint, snapHomeEndpoint });
實際上你不僅能夠如示例中一樣修改慣性結束的位置,還能夠修改慣性動效的軌跡。InteractionTracker
使你能夠精准定制出體現標誌性體驗的觀感。要瞭解更多有關 InteractionTracker
潛力與使用的內容,可參見:
如何進一步深入?
如果你還未查看 WindowsUIDevLabs 代碼倉庫,你絕對應該馬上去看看。該倉庫的簡介是這樣的:
歡迎來到 Windows UI 開發實驗室的代碼倉庫,本庫包含了最新的示例代碼、示例項目以及來自使用 Windows UI 開發各種精美 UWP 應用的開發者的反饋。
作為深入理解學習 Windows UI 的下一站,該代碼倉庫是獲取深入理解平臺與各種協助代碼的好地方。
譯者註:
-
平納緩動函數(Penner’s Easing Functions):由 Robert Penner 定義的一組流行的緩動函數,被各種動效實現廣泛使用。 ↩
-
福特 T 型車是福特於1908年至1927年推出的一款價格低廉廣受歡迎的汽車。福特在其自傳第四捲中提到他曾對銷售人員說“只要車是黑色的,顧客可以把它塗成任何自己喜歡的顏色。”由於黑色塗料廉價耐用,出於提高生產效率的考慮福特作出了只出產黑色車型的決定。但這一決定使得福特後續的份額被競爭對手蠶食。 ↩
關於作者
本文原作者 Nick Waggoner 供職於微軟 native Windows UI platform(@WindowsUI)。
原作者博客:http://www.nickwaggoner.com/www.nickwaggoner.com/
原作者 Twitter:@nrwaggs
本文已獲原作者授權進行翻譯。我後續會持續翻譯 Nick Waggoner 在個人博客或其它位置發表的有關 UWP、 Windows UI 的文章。