首页后端开发ASP.NET用Shape做动画实例代码

用Shape做动画实例代码

时间2024-01-30 21:03:03发布访客分类ASP.NET浏览612
导读:收集整理的这篇文章主要介绍了用Shape做动画实例代码,觉得挺不错的,现在分享给大家,也给大家做个参考。相对于WPF/Silverlight,UWP的动画系统可以说有大幅提高,不过本文无意深入讨论这些动画API,本文将介绍使用Shape做一...
收集整理的这篇文章主要介绍了用Shape做动画实例代码,觉得挺不错的,现在分享给大家,也给大家做个参考。相对于WPF/Silverlight,UWP的动画系统可以说有大幅提高,不过本文无意深入讨论这些动画API,本文将介绍使用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

若转载请注明出处: 用Shape做动画实例代码
本文地址: https://pptw.com/jishu/593035.html
jquery怎样给div增加内容 asp.net mvc中实现Forms身份验证身份验证流程的实例

游客 回复需填写必要信息