[UWP]用Shape做动画(2):使用与扩展PointAnimation
上一篇幾乎都在說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 < T >,提供PointCollection以外數據類型的支持。
結合上一篇文章再發散一下,總覺得將來遇到什么UWP沒有提供的功能都可以通過變通的方法實現,Binding和DependencyProperty真是UWP開發者最好的朋友。
轉載于:https://blog.51cto.com/yexuanke/1942112
總結
以上是生活随笔為你收集整理的[UWP]用Shape做动画(2):使用与扩展PointAnimation的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 手工安装kubernetes
- 下一篇: day26 re正则表达式