用Shape做动画实例代码
1. 使用strokeDashOffset做等待提示动画
圆形的等待提示动画十分容易做,只要让它旋转就可以了:
但是圆形以外的形状就不容易做了,例如三角形,总不能让它单纯地旋转吧:
要解决这个问题可以使用StrokeDashOffset。StrokeDashOffset用于控制虚线边框的第一个短线相对于ShaPE开始点的位移,使用动画控制这个数值可以做出边框滚动的效果:
Page.Resources> Storyboard x:Name="PRogressStoryboard"> DoubleAnimationUsingKeyFrames EnableDependentAnimation="True" Storyboard.TargetProperty="(Shape.StrokeDashOffset)" Storyboard.TargetName="triangle"> EasingDoubleKeyFrame KeyTime="0:1:0" Value="-500" /> /DoubleAnimationUsingKeyFrames> /Storyboard> /Page.Resources> Grid Background="#FfcCCCCC"> Grid Height="100" HorizontalAlignment="center"> StackPanel orientation="Horizontal" Vert@R_126_2411@Alignment="Center"> TextBlock Text="L" FontSize="55" Margin="0,0,5,4" /> local:Triangle x:Name="triangle" Height="40" Width="40" StrokeThickness="2" Stroke="RoyalBlue" StrokeDashArray="4.045 4.045" StrokeDashOffset="0.05" StrokeDashCap="Round" /> TextBlock Text="ading..." FontSize="55" Margin="5,0,0,4" /> /StackPanel> /Grid> /Grid>
需要注意的是Shape的边长要正好能被StrokeDashArray中短线和缺口的和整除,即 满足边长 / StrokeThickness % Sum( StrokeDashArray ) = 0
,这是因为在StrokeDashOffset=0的地方会截断短线,如下图所示:
另外注意的是边长的计算,如Rectangle,边长并不是(Height + Width) * 2
,而是(Height - StrokeThickness) * 2 + (Width- StrokeThickness) * 2
,如下图所示,边长应该从边框正中间开始计算:
有一些Shape的边长计算还会受到Stretch影响,如上一篇中自定义的Triangle:
StackPanel Orientation="Horizontal" HorizontalAlignment="Center"> Grid Height="50" Width="50"> local:Triangle Stretch="Fill" StrokeThickness="5" Stroke="RoyalBlue" /> /Grid> Grid Height="50" Width="50" Margin="10,0,0,0"> local:Triangle Stretch="None" StrokeThickness="5" Stroke="RoyalBlue" /> /Grid> /StackPanel>
2. 使用StrokeDashArray做进度提示动画
StrokeDashArray用于将Shape的边框变成虚线,StrokeDashArray的值是一个double类型的有序集合,里面的数值指定虚线中每一段以StrokeThickness为单位的长度。用StrokeDashArray做进度提示的基本做法就是将进度Progress通过Converter转换为分成两段的StrokeDashArray,第一段为实线,表示当前进度,第二段为空白。假设一个Shape的边长是100,当前进度为50,则将StrokeDashArray设置成{ 50,double.MaxValue} 两段。
做成动画如下图所示:
Page.Resources> Style TargetType="TextBlock"> Setter Property="FontSize" Value="12" /> /Style> local:ProgressToStrokeDashArrayConverter x:Key="ProgressToStrokeDashArrayConverter" TargetPath="{ Binding ElementName=Triangle} " /> local:ProgressToStrokeDashArrayConverter2 x:Key="ProgressToStrokeDashArrayConverter2" TargetPath="{ Binding ElementName=Triangle} " /> toolkIT:StringFormatConverter x:Key="StringFormatConverter" /> local:ProgressWrapper x:Name="ProgressWrapper" /> Storyboard x:Name="Storyboard1"> DoubleAnimation Duration="0:0:5" To="100" Storyboard.TargetProperty="Progress" Storyboard.TargetName="ProgressWrapper" EnableDependentAnimation="True" /> /Storyboard> /Page.Resources> Grid Background="{ ThemeResource ApplicationPageBackgroundThemebrush} "> Viewbox Height="150"> StackPanel Orientation="Horizontal"> Grid> local:Triangle Height="40" Width="40" StrokeThickness="2" Stroke="DarkGray" /> local:Triangle x:Name="Triangle" Height="40" Width="40" StrokeThickness="2" Stroke="RoyalBlue" StrokeDashArray="{ Binding Progress,Source={ staticResource ProgressWrapper} ,Converter={ StaticResource ProgressToStrokeDashArrayConverter} } " /> TextBlock Text="{ Binding Progress,Source={ StaticResource ProgressWrapper} ,Converter={ StaticResource StringFormatConverter} ,ConverterParameter='{ } { 0:0} '} " HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0,15,0,0" /> /Grid> Grid Margin="20,0,0,0"> local:Triangle Height="40" Width="40" StrokeThickness="2" Stroke="DarkGray" /> local:Triangle x:Name="Triangle2" Height="40" Width="40" StrokeThickness="2" Stroke="RoyalBlue" StrokeDashArray="{ Binding Progress,Source={ StaticResource ProgressWrapper} ,Converter={ StaticResource ProgressToStrokeDashArrayConverter2} } " /> TextBlock Text="{ Binding Progress,Source={ StaticResource ProgressWrapper} ,Converter={ StaticResource StringFormatConverter} ,ConverterParameter='{ } { 0:0} '} " HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0,15,0,0" /> /Grid> /StackPanel> /Viewbox> /Grid>
其中ProgressToStrokeDashArrayConverter和ProgressToStrokeDashArrayConverter2的代码如下:
public class ProgressToStrokeDashArrayConverter : DependencyObject, IValueConverter{ /// summary> /// 获取或设置TargetPath的值/// /summary> public Path TargetPath { get { return (Path)GetValue(TargetPathProperty); } set { SetValue(TargetPathProperty, value); } } /// summary> /// 标识 TargetPath 依赖属性。/// /summary> public static readonly DependencyProperty TargetPathProperty = DependencyProperty.Register("TargetPath", typeof(Path), typeof(ProgressToStrokeDashArrayConverter), new PropertyMetadata(null)); public virtual object Convert(object value, Type targetType, object parameter, string language) { if (value is double == false)return null; VAR progress = (double)value; if (TargetPath == null)return null; var totalLength = GetTotalLength(); var FirstSection = progress * totalLength / 100 / TargetPath.StrokeThickness; if (progress == 100) firstSection = Math.Ceiling(firstSection); var result = new DoubleCollection { firstSection, double.MaxValue } ; return result; } public object ConvertBack(object value, Type targetType, object parameter, string language) { throw new NotImplementedException(); } protected double GetTotalLength() { var geometry = TargetPath.Data as PathGeometry; if (geometry == null) return 0; if (geometry.Figures.Any() == false)return 0; var figure = geometry.Figures.FirstOrDefault(); if (figure == null) return 0; var totalLength = 0d; var point = figure.StartPoint; foreach (var item in figure.Segments) { var segment = item as LineSegment; if (segment == null) return 0; totalLength += Math.Sqrt(Math.Pow(point.X - segment.Point.X, 2) + Math.Pow(point.Y - segment.Point.Y, 2)); point = segment.Point; } totalLength += Math.Sqrt(Math.Pow(point.X - figure.StartPoint.X, 2) + Math.Pow(point.Y - figure.StartPoint.Y, 2)); return totalLength; } } public class ProgressToStrokeDashArrayConverter2 : ProgressToStrokeDashArrayConverter{ public override object Convert(object value, Type targetType, object parameter, string language) { if (value is double == false)return null; var progress = (double)value; if (TargetPath == null) return null; var totalLength = GetTotalLength(); totalLength = totalLength / TargetPath.StrokeThickness; var thirdsection = progress * totalLength / 100; if (progress == 100) thirdSection = Math.Ceiling(thirdSection); var secondSection = (totalLength - thirdSection) / 2; var result = new DoubleCollection { 0, secondSection, thirdSection, double.MaxValue } ; return result; } }
由于代码只是用于演示,protected double GetTotalLength()
写得比较将就。可以看到这两个Converter继承自DependencyObject,这是因为这里需要通过绑定为TargetPath赋值。
这里还有另一个类ProgressWrapper:
public class ProgressWrapper : DependencyObject{ /// summary> /// 获取或设置Progress的值/// /summary> public double Progress { get { return (double)GetValue(ProgressProperty); } set { SetValue(ProgressProperty, value); } } /// summary> /// 标识 Progress 依赖属性。/// /summary> public static readonly DependencyProperty ProgressProperty = DependencyProperty.Register("Progress", typeof(double), typeof(ProgressWrapper), new PropertyMetadata(0d)); }
因为这里没有可供Storyboard操作的double属性,所以用这个类充当Storyboard和StrokeDashArray的桥梁。UWPCommUnityToolkit中也有一个差不多用法的类BindableValueHolder,这个类通用性比较强,可以参考它的用法。
3. 使用Behavior改进进度提示动画代码
只是做个动画而已,又是Converter,又是Wrapper,又是Binding,看起来十分复杂,如果Shape上面有Progress属性就方便多了。这时候首先会考虑附加属性,在XAML用法如下:
UserControl.Resources> Storyboard x:Name="Storyboard1"> DoubleAnimation Duration="0:0:5" To="100" Storyboard.TargetProperty="(local:PathExtention.Progress)" Storyboard.TargetName="Triangle" /> /Storyboard> /UserControl.Resources> Grid x:Name="LayoutRoot" Background="White"> local:Triangle x:Name="Triangle" Height="40" local:PathExtention.Progress="0" Width="40" StrokeThickness="2" Stroke="RoyalBlue" > /local:Triangle> /Grid>
但其实这是行不通的,XAML有一个存在了很久的限制:However, an existing limitation of the Windows Runtime XAML implementation is that you cannot aniMATE a custom attached property.。这个限制决定了XAML不能对自定义附加属性做动画。不过,这个限制只限制了不能对自定义附加属性本身做动画,但对附加属性中的类的属性则可以,例如以下这种写法应该是行得通的:
UserControl.Resources> Storyboard x:Name="Storyboard1"> DoubleAnimation Duration="0:0:5" To="100" Storyboard.TargetProperty="(local:PathExtention.Progress)" Storyboard.TargetName="TrianglePathExtention" /> /Storyboard> /UserControl.Resources> Grid x:Name="LayoutRoot" Background="White"> local:Triangle x:Name="Triangle" Height="40" Width="40" StrokeThickness="2" Stroke="RoyalBlue" > local:PathHelper> local:PathExtention x:Name="TrianglePathExtention" Progress="0" /> /local:PathHelper> /local:Triangle> /Grid>
更优雅的写法是利用XamlBehaviors,这篇文章很好地解释了XamlBehaviors的作用:
XAML Behaviors非常重要,因为它们提供了一种方法,让开发人员能够以一种简洁、可重复的方式轻松地向UI对象添加功能。他们无需创建控件的子类或重复编写逻辑代码,只要简单地增加一个XAML代码片段。
要使用Behavior改进现有代码,只需实现一个PathProgressBehavior:
public class PathProgressBehavior : BehaviorUIElement> { protected override void OnAttached() { base.OnAttached(); UpdatestrokeDashArray(); } /// summary> /// 获取或设置Progress的值/// /summary> public double Progress { get { return (double)GetValue(ProgressProperty); } set { SetValue(ProgressProperty, value); } } /*Progress DependencyProperty*/protected virtual void OnProgressChanged(double oldValue, double newValue) { UpdateStrokeDashArray(); } protected virtual double GetTotalLength(Path path) { /*some code*/} private void UpdateStrokeDashArray() { var target = AssociatedObject as Path; if (target == null)return; double progress = Progress; //if (target.ActualHeight == 0 || target.ActualWidth == 0)// return; if (target.StrokeThickness == 0) return; var totalLength = GetTotalLength(target); var firstSection = progress * totalLength / 100 / target.StrokeThickness; if (progress == 100) firstSection = Math.Ceiling(firstSection); var result = new DoubleCollection { firstSection, double.MaxValue } ; target.StrokeDashArray = result; } }
XAML中如下使用:
UserControl.Resources> Storyboard x:Name="Storyboard1"> DoubleAnimation Duration="0:0:5" To="100" Storyboard.TargetProperty="Progress" Storyboard.TargetName="PathProgressBehavior" EnableDependentAnimation="True"/> /Storyboard> /UserControl.Resources> Grid x:Name="LayoutRoot" Background="White"> local:Triangle x:Name="Triangle" Height="40" local:PathExtention.Progress="0" Width="40" StrokeThickness="2" Stroke="RoyalBlue" > interactivity:Interaction.Behaviors> local:PathProgressBehavior x:Name="PathProgressBehavior" /> /interactivity:Interaction.Behaviors> /local:Triangle> /Grid>
这样看起来就清爽多了。
4. 模仿背景填充动画
先看看效果:
其实这篇文章里并不会讨论填充动画,不过首先声明做填充动画会更方便快捷,这一段只是深入学习过程中的产物,实用价值不高。
上图三角形的填充的效果只需要叠加两个同样大小的Shape,前面那个设置Stretch="Uniform"
,再通过DoubleAnimation改变它的高度就可以了。文字也是相同的原理,叠加两个相同的TextBlock,将前面那个放在一个无边框的ScrollViewer里再去改变ScrollViewer的高度。
Page.Resources> Style TargetType="TextBlock"> Setter Property="FontSize" Value="12" /> /Style> local:ProgressToHeightConverter x:Key="ProgressToHeightConverter" TargetContentControl="{ Binding ElementName=ContentControl} " /> local:reverseProgressToHeightConverter x:Key="ReverseProgressToHeightConverter" TargetContentControl="{ Binding ElementName=ContentControl2} " /> toolkit:StringFormatConverter x:Key="StringFormatConverter" /> local:ProgressWrapper x:Name="ProgressWrapper" /> Storyboard x:Name="Storyboard1"> DoubleAnimation Duration="0:0:5" To="100" Storyboard.TargetProperty="Progress" Storyboard.TargetName="ProgressWrapper" EnableDependentAnimation="True" /> /Storyboard> /Page.Resources> Grid Background="{ ThemeResource ApplicationPageBackgroundThemeBrush} "> Grid> local:Triangle Height="40" Width="40" StrokeThickness="2" Fill="LightGray" /> local:Triangle Height="40" Width="40" Stretch="Fill" StrokeThickness="2" Stroke="RoyalBlue" /> ContentControl x:Name="ContentControl" VerticalAlignment="Bottom" HorizontalAlignment="Center" Height="{ Binding Progress,Source={ StaticResource ProgressWrapper} ,Converter={ StaticResource ProgressToHeightConverter} } "> local:Triangle x:Name="Triangle3" Height="40" Width="40" StrokeThickness="2" Fill="RoyalBlue" Stretch="Uniform" VerticalAlignment="Bottom" /> /ContentControl> TextBlock Text="{ Binding Progress,Source={ StaticResource ProgressWrapper} ,Converter={ StaticResource StringFormatConverter} ,ConverterParameter='{ } { 0:0} '} " HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0,12,0,0" Foreground="White" /> ContentControl x:Name="ContentControl2" Height="{ Binding Progress,Source={ StaticResource ProgressWrapper} ,Converter={ StaticResource ReverseProgressToHeightConverter} } " VerticalAlignment="Top" HorizontalAlignment="Center"> ScrollViewer BorderThickness="0" Padding="0,0,0,0" VerticalScrollBarVisibility="Disabled" HorizontalScrollBarVisibility="Disabled" VerticalAlignment="Top" Height="40"> Grid Height="40"> TextBlock Text="{ Binding Progress,Source={ StaticResource ProgressWrapper} ,Converter={ StaticResource StringFormatConverter} ,ConverterParameter='{ } { 0:0} '} " HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0,12,0,0" /> /Grid> /ScrollViewer> /ContentControl> /Grid> /Grid>
ProgressToHeightConverter和ReverseProgressToHeightConverter的代码如下:
public class ProgressToHeightConverter : DependencyObject, IValueConverter{ /// summary> /// 获取或设置TargetContentControl的值/// /summary> public ContentControl TargetContentControl { get { return (ContentControl)GetValue(TargetContentControlProperty); } set { SetValue(TargetContentControlProperty, value); } } /// summary> /// 标识 TargetContentControl 依赖属性。/// /summary> public static readonly DependencyProperty TargetContentControlProperty = DependencyProperty.Register("TargetContentControl", typeof(ContentControl), typeof(ProgressToHeightConverter), new PropertyMetadata(null)); public object Convert(object value, Type targetType, object parameter, string language) { if (value is double == false) return 0d; var progress = (double)value; if (TargetContentControl == null) return 0d; var element = TargetContentControl.Content as FrameworkElement; if (element == null) return 0d; return element.Height * progress / 100; } public object ConvertBack(object value, Type targetType, object parameter, string language) { throw new NotImplementedException(); } } public class ReverseProgressToHeightConverter : DependencyObject, IValueConverter{ /// summary> /// 获取或设置TargetContentControl的值/// /summary> public ContentControl TargetContentControl { get { return (ContentControl)GetValue(TargetContentControlProperty); } set { SetValue(TargetContentControlProperty, value); } } /// summary> /// 标识 TargetContentControl 依赖属性。/// /summary> public static readonly DependencyProperty TargetContentControlProperty = DependencyProperty.Register("TargetContentControl", typeof(ContentControl), typeof(ReverseProgressToHeightConverter), new PropertyMetadata(null)); public object Convert(object value, Type targetType, object parameter, string language) { if (value is double == false) return double.NaN; var progress = (double)value; if (TargetContentControl == null)return double.NaN; var element = TargetContentControl.Content as FrameworkElement; if (element == null)return double.NaN; return element.Height * (100 - progress) / 100; } public object ConvertBack(object value, Type targetType, object parameter, string language) { throw new NotImplementedException(); } }
再提醒一次,实际上老老实实做填充动画好像更方便些。
5. 将动画应用到Button的ControlTemplate
同样的技术,配合ControlTemplate可以制作很有趣的按钮:
PointerEntered时,按钮的边框从进入点向反方向延伸。PointerExited时,边框从反方向向移出点消退。要做到这点需要在PointerEntered时改变边框的方向,使用了ChangeAngleToEnterPointerBehavior:
public class ChangeAngleToEnterPointerBehavior : BehaviorEllipse> { protected override void OnAttached() { base.OnAttached(); AssociatedObject.PointerEntered += OnAssociatedObjectPointerEntered; AssociatedObject.PointerExited += OnAssociatedObjectPointerExited; } protected override void OnDetaching() { base.OnDetaching(); AssociatedObject.PointerEntered -= OnAssociatedObjectPointerEntered; AssociatedObject.PointerExited -= OnAssociatedObjectPointerExited; } private void OnAssociatedObjectPointerExited(object sender, PointerRoutedEventArgs e) { UpdateAngle(e); } private void OnAssociatedObjectPointerEntered(object sender, PointerRoutedEventArgs e) { UpdateAngle(e); } private void UpdateAngle(PointerRoutedEventArgs e) { if (AssociatedObject == null || AssociatedObject.StrokeThickness == 0)return; AssociatedObject.RenderTransformOrigin = new Point(0.5, 0.5); var rotateTransform = AssociatedObject.RenderTransform as RotateTransform; if (rotateTransform == null) { rotateTransform = new RotateTransform(); AssociatedObject.RenderTransform = rotateTransform; } var point = e.GetcurrentPoint(AssociatedObject.Parent as UIElement).Position; var centerPoint = new Point(AssociatedObject.ActualWidth / 2, AssociatedObject.ActualHeight / 2); var angleOfLine = Math.Atan2(point.Y - centerPoint.Y, point.X - centerPoint.X) * 180 / Math.PI; rotateTransform.Angle = angleOfLine + 180; } }
这个类命名不是很好,不过将就一下吧。
为了做出边框延伸的效果,另外需要一个类EllipseProgressBehavior:
public class EllipseProgressBehavior : BehaviorEllipse> { /// summary> /// 获取或设置Progress的值/// /summary> public double Progress { get { return (double)GetValue(ProgressProperty); } set { SetValue(ProgressProperty, value); } } /// summary> /// 标识 Progress 依赖属性。/// /summary> public static readonly DependencyProperty ProgressProperty = DependencyProperty.Register("Progress", typeof(double), typeof(EllipseProgressBehavior), new PropertyMetadata(0d, OnProgressChanged)); private static void OnProgressChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args) { var target = obj as EllipseProgressBehavior; double oldValue = (double)args.OldValue; double newValue = (double)args.NewValue; if (oldValue != newValue) target.OnProgressChanged(oldValue, newValue); } protected virtual void OnProgressChanged(double oldValue, double newValue) { UpdateStrokeDashArray(); } protected virtual double GetTotalLength() { if (AssociatedObject == null)return 0; return (AssociatedObject.ActualHeight - AssociatedObject.StrokeThickness) * Math.PI; } private void UpdateStrokeDashArray() { if (AssociatedObject == null || AssociatedObject.StrokeThickness == 0) return; //if (target.ActualHeight == 0 || target.ActualWidth == 0)// return; var totalLength = GetTotalLength(); totalLength = totalLength / AssociatedObject.StrokeThickness; var thirdSection = Progress * totalLength / 100; var secondSection = (totalLength - thirdSection) / 2; var result = new DoubleCollection { 0, secondSection, thirdSection, double.MaxValue } ; AssociatedObject.StrokeDashArray = result; } }
套用到ControlTemplate如下:
ControlTemplate TargetType="Button"> Grid x:Name="RootGrid"> VisualStateManager.VisualStateGroups> VisualStateGroup x:Name="CommonStates"> VisualStateGroup.Transitions> VisualTransition GeneratedDuration="0:0:1" To="Normal"> Storyboard> DoubleAnimationUsingKeyFrames EnableDependentAnimation="True" Storyboard.TargetProperty="(local:EllipseProgressBehavior.Progress)" Storyboard.TargetName="EllipseProgressBehavior"> EasingDoubleKeyFrame KeyTime="0:0:1" Value="0"> EasingDoubleKeyFrame.EasingFunction> QuinticEase EasingMode="EaseOut" /> /EasingDoubleKeyFrame.EasingFunction> /EasingDoubleKeyFrame> /DoubleAnimationUsingKeyFrames> /Storyboard> /VisualTransition> VisualTransition GeneratedDuration="0:0:1" To="PointerOver"> Storyboard> DoubleAnimationUsingKeyFrames EnableDependentAnimation="True" Storyboard.TargetProperty="(local:EllipseProgressBehavior.Progress)" Storyboard.TargetName="EllipseProgressBehavior"> EasingDoubleKeyFrame KeyTime="0:0:1" Value="100"> EasingDoubleKeyFrame.EasingFunction> QuinticEase EasingMode="EaseOut" /> /EasingDoubleKeyFrame.EasingFunction> /EasingDoubleKeyFrame> /DoubleAnimationUsingKeyFrames> /Storyboard> /VisualTransition> /VisualStateGroup.Transitions> VisualState x:Name="Normal"> Storyboard> PointerUpThemeAnimation Storyboard.TargetName="RootGrid" /> /Storyboard> /VisualState> VisualState x:Name="PointerOver"> Storyboard> PointerUpThemeAnimation Storyboard.TargetName="RootGrid" /> /Storyboard> VisualState.Setters> Setter Target="EllipseProgressBehavior.(local:EllipseProgressBehavior.Progress)" Value="100" /> /VisualState.Setters> /VisualState> VisualState x:Name="Pressed"> Storyboard> PointerDownThemeAnimation Storyboard.TargetName="RootGrid" /> /Storyboard> /VisualState> VisualState x:Name="Disabled" /> /VisualStateGroup> /VisualStateManager.VisualStateGroups> ContentPresenter x:Name="ContentPresenter" AutomationProperties.AccessibilityView="Raw" ContentTemplate="{ TemplateBinding ContentTemplate} " ContentTransitions="{ TemplateBinding ContentTransitions} " Content="{ TemplateBinding Content} " HorizontalContentAlignment="{ TemplateBinding HorizontalContentAlignment} " Padding="{ TemplateBinding Padding} " VerticalContentAlignment="{ TemplateBinding VerticalContentAlignment} " /> Ellipse Fill="Transparent" Stroke="{ TemplateBinding BorderBrush} " StrokeThickness="2"> interactivity:Interaction.Behaviors> local:ChangeAngleToEnterPointerBehavior /> local:EllipseProgressBehavior x:Name="EllipseProgressBehavior" /> /interactivity:Interaction.Behaviors> /Ellipse> /Grid> /ControlTemplate>
注意:我没有鼓励任何人自定义按钮外观的意思,能用系统自带的动画或样式就尽量用系统自带的,没有设计师的情况下又想UI做得与众不同通常会做得很难看。想要UI好看,合理的布局、合理的颜色、合理的图片就足够了。
6. 结语
在学习Shape的过程中觉得好玩就做了很多尝试,因为以前工作中做过不少等待、进度的动画,所以这次就试着做出本文的动画。
XAML的传统动画并没有提供太多功能,主要是ColorAnimation、DoubleAnimation、PointAnimation三种,不过靠Binding和Converter可以弥补这方面的不足,实现很多需要的功能。
本文的一些动画效果参考了SVG的动画。话说回来,Windows 10 1703新增了SvgImageSource,不过看起来只是简单地将SVG翻译成对应的Shape,然后用Shape呈现,不少高级特性都不支持(如下图阴影的滤镜),用法如下:
Image> Image.Source> SvgImageSource UriSource="feoffset_1.svg" /> /Image.Source> /Image>
SvgImageSource:
原本的Svg:
以上就是用Shape做动画实例代码的详细内容,更多请关注其它相关文章!
声明:本文内容由网友自发贡献,本站不承担相应法律责任。对本内容有异议或投诉,请联系2913721942#qq.com核实处理,我们将尽快回复您,谢谢合作!
若转载请注明出处: 用Shape做动画实例代码
本文地址: https://pptw.com/jishu/593035.html