[UWP]用Shape做動畫

来源:http://www.cnblogs.com/dino623/archive/2017/06/10/UWP_Shape_Animation.html
-Advertisement-
Play Games

相對於WPF/Silverlight,UWP的動畫系統可以說有大幅提高,不過本文無意深入討論這些動畫API,本文將介紹使用Shape做一些進度、等待方面的動畫,除此之外也會介紹一些相關技巧。 1. 使用StrokeDashOffset做等待提示動畫 圓形的等待提示動畫十分容易做,只要讓它旋轉就可以了 ...


相對於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>
        <

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 在本地安裝webbench,步驟如下: 安裝完畢。 一般我們只用到-c和-t兩個參數,類似下麵這樣: -t表示運行測試的時間,如果不指定預設是30秒,-c表示客戶端數量,也就是併發數。 安裝siege: wget http://soft.vpser.net/test/siege/siege-2.67 ...
  • 邏輯捲管理LVM 一 創建邏輯捲 1準備分區或硬碟 這裡使用/dev/sdb、/dev/sdc兩塊硬碟和/dev/sda9、/dev/sda10兩個分區,大小都為1G,磁碟有限,我也不想這麼摳的。 添加分區/dev/sda9、/dev/sda10 註意,要修改分區類型為Linux LVM 同樣的方法 ...
  • 本文快速分享一下快速零配置遷移 API 適配 iOS 對 IPv6 以及 HTTPS 的要求的方法,供大家參考。 <! more 零配置方案 最新的蘋果審核政策對 API 的 IPv6 以及 HTTPS 都作了要求,那麼如何快速進行適配呢? 這裡就快速給大家分享一個站點: "https://www. ...
  • 1.ngx_http_stub_status_module 是一個 Nginx 的內置 HTTP 模塊,該模塊可以提供 Nginx 的狀態信息。預設情況下這個模塊是不被編譯進來的,所以在編譯 Nginx 時要指定載入該模塊--with-http_stub_status_module 2.首先檢查ng ...
  • 本文介紹基於.net的證券公司宣傳微網站手機網頁的設計與實現方法。 隨著電腦技術的快速發展,基於Web的電腦網路金融、證券宣傳或交易網站已成為現代金融理財發展的熱點,B/S(Browser/Server)結構的互聯網宣傳也逐步在各大金融證券中得到了廣泛的應用[1]。互聯網金融(ITFIN)是指傳 ...
  • 第一種方法 string s=abcdeabcdeabcde; string[] sArray=s.Split('c') ; foreach(string i in sArray) Console.WriteLine(i.ToString()); 輸出下麵的結果: ab deab deab de 第 ...
  • 看過的相當不錯的一篇文章,但是對基架還時不太理解,大神們看到,希望指點一二,能告訴點學習資源就更好了! 本篇文章不是出自本人之手,轉載完全處於膜拜以及學習! 歡迎加我微信:jkxx123321 備註博客加就可以了! 最近由於需要在框架中提供一些自定義模板的功能,找到了一篇博客,可惜似乎是翻譯工具直接 ...
  • 一、前言 在日常的界面開發中,我們大多使用MVVM模式進行開發。通常情況下,一個PropertyGridControl或者DataGrid的ItemsSource設置好, 然後每一列綁定好某一條ItemsSource中的某一個欄位就可以跑起來了。 但是也有另一種情況: 假設一個界面Temp.xaml ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...