用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                    
VerticalAlignment=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 : Behavior<UIElement>
{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 : Behavior<Ellipse>
{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 : Behavior<Ellipse>
{/// <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:

相关文章

在要实现单例模式的类当中添加如下代码:实例化的时候:frmC...
1、如果制作圆角窗体,窗体先继承DOTNETBAR的:public parti...
根据网上资料,自己很粗略的实现了一个winform搜索提示,但是...
近期在做DSOFramer这个控件,打算自己弄一个自定义控件来封装...
今天玩了一把WMI,查询了一下电脑的硬件信息,感觉很多代码都...
最近在研究WinWordControl这个控件,因为上级要求在系统里,...