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开发者最好的朋侪。
4. 参考
How SVG Shape Morphing Works
Gadal MetaSyllabus
以上就是用Shape做动画的实例详解的细致内容,更多请关注ki4网别的相干文章!