上一篇幾乎都在說DoubleAnimation的應用,這篇說說PointAnimation。
1. 使用PointAnimation
使用PointAnimation可以讓Shape變形,但其實沒看到多少人會這麼用,畢竟WPF做的軟體多數不需要這麼花俏。
1.1 在XAML上使用PointAnimation
<Storyboard x:Name="Storyboard2" RepeatBehavior="Forever" AutoReverse="True" Duration="0:0:4"><PointAnimation Storyboard.TargetProperty="(Path.Data).(PathGeometry.Figures)[0].(PathFigure.StartPoint)" Storyboard.TargetName="Path2" To="0,0" EnableDependentAnimation="True" /><PointAnimation Storyboard.TargetProperty="(Path.Data).(PathGeometry.Figures)[0].(PathFigure.Segments)[0].(LineSegment.Point)" Storyboard.TargetName="Path2" To="100,0" EnableDependentAnimation="True" /><ColorAnimation To="#FF85C82E" Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)" Storyboard.TargetName="Path2" /></Storyboard>…<Path Margin="0,20,0,0" x:Name="Path2" Fill="GreenYellow"><Path.Data><PathGeometry><PathFigure StartPoint="50,0"><LineSegment Point="50,0" /><LineSegment Point="0,100" /><LineSegment Point="0,100" /><LineSegment Point="100,100" /><LineSegment Point="100,100" /></PathFigure></PathGeometry></Path.Data></Path>
在這個範例裡最頭痛的地方是Property-path 語法,如果不能熟記的話最好依賴Blend生成。
1.2 在程式碼中使用PointAnimation
如果Point數量很多,例如圖表,通常會在C#程式碼中使用PointAnimation:
_storyboard = new Storyboard(); Random random = new Random();for (int i = 0; i < _pathFigure.Segments.Count; i++) {var animation = new PointAnimation { Duration = TimeSpan.FromSeconds(3) }; Storyboard.SetTarget(animation, _pathFigure.Segments[i]); Storyboard.SetTargetProperty(animation, "(LineSegment.Point)"); animation.EnableDependentAnimation = true; animation.EasingFunction = new QuarticEase { EasingMode = EasingMode.EaseOut }; animation.To = new Point((_pathFigure.Segments[i] as LineSegment).Point.X, (i % 2 == 0 ? 1 : -1) * i * 1.2 + 60); _storyboard.Children.Add(animation); } _storyboard.Begin();
#因為可以直接SetTarget
,所以Property-path語法就可以很簡單。
2. 擴充PointAnimation
上面兩個範例的動畫都還算簡單,如果更複雜些,XAML或C#程式碼都需要寫到很複雜。我參考了這個網頁 想做類似的動畫,但發現需要寫很多XAML所以放棄用PointAnimation實作。這個頁面的動畫核心是這段HTML:
<polygon fill="#FFD41D" points="97.3,0 127.4,60.9 194.6,70.7 145.9,118.1 157.4,185.1 97.3,153.5 37.2,185.1 48.6,118.1 0,70.7 67.2,60.9"> <animate id="animation-to-check" begin="indefinite" fill="freeze" attributeName="points" dur="500ms" to="110,58.2 147.3,0 192.1,29 141.7,105.1 118.7,139.8 88.8,185.1 46.1,156.5 0,125 23.5,86.6 71.1,116.7"/> <animate id="animation-to-star" begin="indefinite" fill="freeze" attributeName="points" dur="500ms" to="97.3,0 127.4,60.9 194.6,70.7 145.9,118.1 157.4,185.1 97.3,153.5 37.2,185.1 48.6,118.1 0,70.7 67.2,60.9"/> </polygon>
只需一組Point的集合就可以控制所有Point的動畫,確實比PointAnimation高效很多。 在WPF中可以透過繼承Timeline實作一個PointCollectionAnimamtion,具體可以參考這個項目。可惜的是雖然UWP的Timeline類別並不封閉,但完全不知道如何繼承並衍生出一個自訂的Animation。
這時候需要稍微變通一下思維。可以將DoubleAnimation理解成這樣:Storyboard將TimeSpan傳遞給DoubleAnimation,DoubleAnimation透過這個TimeSpan(有時還需要結合EasingFunction)計算出目標屬性的目前值最後傳遞給目標屬性,如下圖所示:
既然這樣,也可以接收到這個計算出來的Double,再透過Converter計算出目標的PointCollection值:
假設告訴這個Converter當傳入的Double值(命名為Progress)為0的時候,PointCollection是{0,0 1,1 …},Progress為100時PointCollection是{1,1 2,2 …},當Progress處於其中任何值時的計算方法則是:
private PointCollection GetCurrentPoints(PointCollection fromPoints, PointCollection toPoints, double percentage) {var result = new PointCollection();for (var i = 0; i < Math.Min(fromPoints.Count, toPoints.Count); i++) { var x = (1 - percentage / 100d) * fromPoints[i].X + percentage / 100d * toPoints[i].X; var y = (1 - percentage / 100d) * fromPoints[i].Y + percentage / 100d * toPoints[i].Y; result.Add(new Point(x, y)); }return result; }
這樣就完成了從TimeSpan到PointCollection的轉換過程。然後就是定義在XAML上的使用方式。參考上面PointCollectionAnimation,雖然多了個Converter,但XAML也應該夠簡潔:
<local:ProgressToPointCollectionBridge x:Name="ProgressToPointCollectionBridge"><PointCollection>97.3,0 127.4,60.9 194.6,70.7 145.9,118.1 157.4,185.1 97.3,153.5 37.2,185.1 48.6,118.1 0,70.7 67.2,60.9</PointCollection><PointCollection>110,58.2 147.3,0 192.1,29 141.7,105.1 118.7,139.8 88.8,185.1 46.1,156.5 0,125 23.5,86.6 71.1,116.7</PointCollection></local:ProgressToPointCollectionBridge><Storyboard x:Name="Storyboard1" FillBehavior="HoldEnd"><DoubleAnimation Duration="0:0:2" To="100" FillBehavior="HoldEnd" Storyboard.TargetProperty="(local:ProgressToPointCollectionBridge.Progress)" Storyboard.TargetName="ProgressToPointCollectionBridge" EnableDependentAnimation="True"/></Storyboard>…<Polygon x:Name="polygon" Points="{Binding Source={StaticResource ProgressToPointCollectionBridge},Path=Points}" Stroke="DarkOliveGreen" StrokeThickness="2" Height="250" Width="250" Stretch="Fill" />
最後我選擇了將這個Converter命名為ProgressToPointCollectionBridge
。可以看出Polygon 將Points綁定到ProgressToPointCollectionBridge,DoubleAnimation 改變ProgressToPointCollectionBridge.Progress,進而改變Points。 XAML的簡潔程度還算令人滿意,如果需要操作多點的話相對於PointAnimation的優勢就很大。
運行結果如下:
完整的XAML:
<UserControl.Resources><local:ProgressToPointCollectionBridge x:Name="ProgressToPointCollectionBridge"><PointCollection>97.3,0 127.4,60.9 194.6,70.7 145.9,118.1 157.4,185.1 97.3,153.5 37.2,185.1 48.6,118.1 0,70.7 67.2,60.9</PointCollection><PointCollection>110,58.2 147.3,0 192.1,29 141.7,105.1 118.7,139.8 88.8,185.1 46.1,156.5 0,125 23.5,86.6 71.1,116.7</PointCollection></local:ProgressToPointCollectionBridge><Storyboard x:Name="Storyboard1" FillBehavior="HoldEnd"><DoubleAnimation Duration="0:0:2" To="100" FillBehavior="HoldEnd" Storyboard.TargetProperty="(local:ProgressToPointCollectionBridge.Progress)" Storyboard.TargetName="ProgressToPointCollectionBridge" EnableDependentAnimation="True"><DoubleAnimation.EasingFunction><ElasticEase EasingMode="EaseInOut" /></DoubleAnimation.EasingFunction></DoubleAnimation><ColorAnimation Duration="0:0:2" To="#FF48F412" Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)" Storyboard.TargetName="polygon" d:IsOptimized="True"><ColorAnimation.EasingFunction><ElasticEase EasingMode="EaseInOut" /></ColorAnimation.EasingFunction></ColorAnimation></Storyboard></UserControl.Resources><Grid x:Name="LayoutRoot" Background="White"><Polygon x:Name="polygon" Points="{Binding Source={StaticResource ProgressToPointCollectionBridge},Path=Points}" Stroke="DarkOliveGreen" StrokeThickness="2" Height="250" Width="250" Stretch="Fill" Fill="#FFEBF412" /></Grid>
ProgressToPointCollectionBridge:
[ContentProperty(Name = nameof(Children))]public class ProgressToPointCollectionBridge : DependencyObject {public ProgressToPointCollectionBridge() { Children = new ObservableCollection<PointCollection>(); }/// <summary>/// 获取或设置Points的值/// </summary>public PointCollection Points {get { return (PointCollection) GetValue(PointsProperty); }set { SetValue(PointsProperty, value); } }/// <summary>/// 获取或设置Progress的值/// </summary>public double Progress {get { return (double) GetValue(ProgressProperty); }set { SetValue(ProgressProperty, value); } }/// <summary>/// 获取或设置Children的值/// </summary>public Collection<PointCollection> Children {get { return (Collection<PointCollection>) GetValue(ChildrenProperty); }set { SetValue(ChildrenProperty, value); } }protected virtual void OnProgressChanged(double oldValue, double newValue) {UpdatePoints(); }protected virtual void OnChildrenChanged(Collection<PointCollection> oldValue, Collection<PointCollection> newValue) {var oldCollection = oldValue as INotifyCollectionChanged;if (oldCollection != null) oldCollection.CollectionChanged -= OnChildrenCollectionChanged;var newCollection = newValue as INotifyCollectionChanged;if (newCollection != null) newCollection.CollectionChanged += OnChildrenCollectionChanged;UpdatePoints(); }private void OnChildrenCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) {UpdatePoints(); }private void UpdatePoints() {if (Children == null || Children.Any() == false) { Points = null; }else if (Children.Count == 1) {var fromPoints = new PointCollection();for (var i = 0; i < Children[0].Count; i++) fromPoints.Add(new Point(0, 0));var toPoints = Children[0]; Points = GetCurrentPoints(fromPoints, toPoints, Progress); }else{var rangePerSection = 100d / (Children.Count - 1);var fromIndex = Math.Min(Children.Count - 2, Convert.ToInt32(Math.Floor(Progress / rangePerSection))); fromIndex = Math.Max(fromIndex, 0);var toIndex = fromIndex + 1; PointCollection fromPoints;if (fromIndex == toIndex) { fromPoints = new PointCollection();for (var i = 0; i < Children[0].Count; i++) fromPoints.Add(new Point(0, 0)); }else{ fromPoints = Children.ElementAt(fromIndex); }var toPoints = Children.ElementAt(toIndex); var percentage = (Progress / rangePerSection - fromIndex) * 100; Points = GetCurrentPoints(fromPoints, toPoints, percentage); } }private PointCollection GetCurrentPoints(PointCollection fromPoints, PointCollection toPoints, double percentage) {var result = new PointCollection();for (var i = 0; i < Math.Min(fromPoints.Count, toPoints.Count); i++) { var x = (1 - percentage / 100d) * fromPoints[i].X + percentage / 100d * toPoints[i].X; var y = (1 - percentage / 100d) * fromPoints[i].Y + percentage / 100d * toPoints[i].Y; result.Add(new Point(x, y)); }return result; }#region DependencyProperties#endregion}
3. 結語
如果將DoubleAnimation說成“對目標的Double屬性做動畫”,那PointAnimation可以說成“對目標的Point.X和Point.Y兩個Double屬性同時做動畫”,ColorAnimation則是“對目標的Color.A、R、G、B四個Int屬性同時做動畫”。這樣理解的話PointAnimation和ColorAnimation只不過是DoubleAnimation的延伸而已,進一步的說,透過DoubleAnimation應該可以延伸出所有類型屬性的動畫。不過我並不清楚怎麼在UWP上自訂動畫,只能透過本文的折衷方式擴充。雖然XAML需要寫複雜些,但這樣也有它的好處:
不需要了解太多Animation相關類別的知識,只需要有依賴屬性、綁定等基礎知識就夠了。
不會因為動畫API的改變而更改,可以相容於WPF、Silverlight和UWP(大概吧,我沒有真的在WPF上測試這些程式碼)。
程式碼夠簡單,省略了計算TimeSpan及EasingFunction的步驟。 稍微修改下還可以做成泛型的
AnimationBridge
,提供PointCollection以外資料型別的支援。
結合上一篇文章再發散一下,總覺得將來遇到什麼UWP沒有提供的功能都可以透過變通的方法實現,Binding和DependencyProperty真是UWP開發者最好的朋友。
4. 參考
How SVG Shape Morphing Works
Gadal MetaSyllabus
以上是用Shape做動畫的實例詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!

C#和.NET適用於Web、桌面和移動開發。 1)在Web開發中,ASP.NETCore支持跨平台開發。 2)桌面開發使用WPF和WinForms,適用於不同需求。 3)移動開發通過Xamarin實現跨平台應用。

C#.NET生態系統提供了豐富的框架和庫,幫助開發者高效構建應用。 1.ASP.NETCore用於構建高性能Web應用,2.EntityFrameworkCore用於數據庫操作。通過理解這些工具的使用和最佳實踐,開發者可以提高應用的質量和性能。

如何將C#.NET應用部署到Azure或AWS?答案是使用AzureAppService和AWSElasticBeanstalk。 1.在Azure上,使用AzureAppService和AzurePipelines自動化部署。 2.在AWS上,使用AmazonElasticBeanstalk和AWSLambda實現部署和無服務器計算。

C#和.NET的結合為開發者提供了強大的編程環境。 1)C#支持多態性和異步編程,2).NET提供跨平台能力和並發處理機制,這使得它們在桌面、Web和移動應用開發中廣泛應用。

.NETFramework是一個軟件框架,C#是一種編程語言。 1..NETFramework提供庫和服務,支持桌面、Web和移動應用開發。 2.C#設計用於.NETFramework,支持現代編程功能。 3..NETFramework通過CLR管理代碼執行,C#代碼編譯成IL後由CLR運行。 4.使用.NETFramework可快速開發應用,C#提供如LINQ的高級功能。 5.常見錯誤包括類型轉換和異步編程死鎖,調試需用VisualStudio工具。

C#是一種由微軟開發的現代、面向對象的編程語言,.NET是微軟提供的開發框架。 C#結合了C 的性能和Java的簡潔性,適用於構建各種應用程序。 .NET框架支持多種語言,提供垃圾回收機制,簡化內存管理。

C#和.NET運行時緊密合作,賦予開發者高效、強大且跨平台的開發能力。 1)C#是一種類型安全且面向對象的編程語言,旨在與.NET框架無縫集成。 2).NET運行時管理C#代碼的執行,提供垃圾回收、類型安全等服務,確保高效和跨平台運行。

要開始C#.NET開發,你需要:1.了解C#的基礎知識和.NET框架的核心概念;2.掌握變量、數據類型、控制結構、函數和類的基本概念;3.學習C#的高級特性,如LINQ和異步編程;4.熟悉常見錯誤的調試技巧和性能優化方法。通過這些步驟,你可以逐步深入C#.NET的世界,並編寫高效的應用程序。


熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

ZendStudio 13.5.1 Mac
強大的PHP整合開發環境

SublimeText3 英文版
推薦:為Win版本,支援程式碼提示!

記事本++7.3.1
好用且免費的程式碼編輯器

SAP NetWeaver Server Adapter for Eclipse
將Eclipse與SAP NetWeaver應用伺服器整合。

WebStorm Mac版
好用的JavaScript開發工具