真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

如何使用Shape做動畫

這篇文章主要介紹如何使用Shape做動畫,文中介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們一定要看完!

成都創(chuàng)新互聯(lián)公司長期為成百上千客戶提供的網(wǎng)站建設(shè)服務(wù),團(tuán)隊從業(yè)經(jīng)驗10年,關(guān)注不同地域、不同群體,并針對不同對象提供差異化的產(chǎn)品和服務(wù);打造開放共贏平臺,與合作伙伴共同營造健康的互聯(lián)網(wǎng)生態(tài)環(huán)境。為平泉企業(yè)提供專業(yè)的成都做網(wǎng)站、網(wǎng)站設(shè)計,平泉網(wǎng)站改版等技術(shù)服務(wù)。擁有十多年豐富建站經(jīng)驗和眾多成功案例,為您定制開發(fā)。

1. 使用StrokeDashOffset做等待提示動畫

圓形的等待提示動畫十分容易做,只要讓它旋轉(zhuǎn)就可以了:

如何使用Shape做動畫

但是圓形以外的形狀就不容易做了,例如三角形,總不能讓它單純地旋轉(zhuǎn)吧:

如何使用Shape做動畫

要解決這個問題可以使用StrokeDashOffset。StrokeDashOffset用于控制虛線邊框的第一個短線相對于Shape開始點(diǎn)的位移,使用動畫控制這個數(shù)值可以做出邊框滾動的效果:

如何使用Shape做動畫

需要注意的是Shape的邊長要正好能被StrokeDashArray中短線和缺口的和整除,即 滿足邊長 / StrokeThickness % Sum( StrokeDashArray ) = 0,這是因為在StrokeDashOffset=0的地方會截斷短線,如下圖所示:

如何使用Shape做動畫

另外注意的是邊長的計算,如Rectangle,邊長并不是(Height + Width) * 2,而是(Height - StrokeThickness) * 2 + (Width- StrokeThickness) * 2,如下圖所示,邊長應(yīng)該從邊框正中間開始計算:

如何使用Shape做動畫

有一些Shape的邊長計算還會受到Stretch影響,如上一篇中自定義的Triangle:

如何使用Shape做動畫

2. 使用StrokeDashArray做進(jìn)度提示動畫

StrokeDashArray用于將Shape的邊框變成虛線,StrokeDashArray的值是一個double類型的有序集合,里面的數(shù)值指定虛線中每一段以StrokeThickness為單位的長度。用StrokeDashArray做進(jìn)度提示的基本做法就是將進(jìn)度Progress通過Converter轉(zhuǎn)換為分成兩段的StrokeDashArray,第一段為實線,表示當(dāng)前進(jìn)度,第二段為空白。假設(shè)一個Shape的邊長是100,當(dāng)前進(jìn)度為50,則將StrokeDashArray設(shè)置成{50,double.MaxValue}兩段。

做成動畫如下圖所示:

如何使用Shape做動畫


其中ProgressToStrokeDashArrayConverter和ProgressToStrokeDashArrayConverter2的代碼如下:

public class ProgressToStrokeDashArrayConverter : DependencyObject, IValueConverter
{/// /// 獲取或設(shè)置TargetPath的值///   public Path TargetPath
    {
    get { return (Path)GetValue(TargetPathProperty);
     }
    set {
     SetValue(TargetPathProperty, value); 
    }
    }/// /// 標(biāo)識 TargetPath 依賴屬性。/// 
    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
{/// /// 獲取或設(shè)置Progress的值///   public double Progress
    {get { return (double)GetValue(ProgressProperty); }set { SetValue(ProgressProperty, value); }
    }/// /// 標(biāo)識 Progress 依賴屬性。/// 
    public static readonly DependencyProperty ProgressProperty =
        DependencyProperty.Register("Progress", typeof(double), typeof(ProgressWrapper), new PropertyMetadata(0d));
}

因為這里沒有可供Storyboard操作的double屬性,所以用這個類充當(dāng)Storyboard和StrokeDashArray的橋梁。UWPCommunityToolkit中也有一個差不多用法的類BindableValueHolder,這個類通用性比較強(qiáng),可以參考它的用法。

3. 使用Behavior改進(jìn)進(jìn)度提示動畫代碼

只是做個動畫而已,又是Converter,又是Wrapper,又是Binding,看起來十分復(fù)雜,如果Shape上面有Progress屬性就方便多了。這時候首先會考慮附加屬性,在XAML用法如下:


  
  

但其實這是行不通的,XAML有一個存在了很久的限制:However, an existing limitation of the Windows Runtime XAML implementation is that you cannot animate a custom attached property.。這個限制決定了XAML不能對自定義附加屬性做動畫。不過,這個限制只限制了不能對自定義附加屬性本身做動畫,但對附加屬性中的類的屬性則可以,例如以下這種寫法應(yīng)該是行得通的:


  
      

更優(yōu)雅的寫法是利用XamlBehaviors,這篇文章很好地解釋了XamlBehaviors的作用:

XAML Behaviors非常重要,因為它們提供了一種方法,讓開發(fā)人員能夠以一種簡潔、可重復(fù)的方式輕松地向UI對象添加功能。
他們無需創(chuàng)建控件的子類或重復(fù)編寫邏輯代碼,只要簡單地增加一個XAML代碼片段。

要使用Behavior改進(jìn)現(xiàn)有代碼,只需實現(xiàn)一個PathProgressBehavior:

public class PathProgressBehavior : Behavior
{protected override void OnAttached()
    {base.OnAttached();UpdateStrokeDashArray();
    }/// /// 獲取或設(shè)置Progress的值///   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中如下使用:


  
  
    
  
  

這樣看起來就清爽多了。

4. 模仿背景填充動畫

先看看效果:

如何使用Shape做動畫

其實這篇文章里并不會討論填充動畫,不過首先聲明做填充動畫會更方便快捷,這一段只是深入學(xué)習(xí)過程中的產(chǎn)物,實用價值不高。
上圖三角形的填充的效果只需要疊加兩個同樣大小的Shape,前面那個設(shè)置Stretch="Uniform",再通過DoubleAnimation改變它的高度就可以了。文字也是相同的原理,疊加兩個相同的TextBlock,將前面那個放在一個無邊框的ScrollViewer里再去改變ScrollViewer的高度。

ProgressToHeightConverter和ReverseProgressToHeightConverter的代碼如下:

public class ProgressToHeightConverter : DependencyObject, IValueConverter
{/// /// 獲取或設(shè)置TargetContentControl的值///   public ContentControl TargetContentControl
    {
    get { 
    return (ContentControl)GetValue(TargetContentControlProperty); 
    }
    set { 
    SetValue(TargetContentControlProperty, value); 
    }
    }/// /// 標(biāo)識 TargetContentControl 依賴屬性。///
     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
{/// /// 獲取或設(shè)置TargetContentControl的值///   
public ContentControl TargetContentControl
    {
    get { 
    return (ContentControl)GetValue(TargetContentControlProperty);
     }
    set { 
    SetValue(TargetContentControlProperty, value); 
    }
    }/// /// 標(biāo)識 TargetContentControl 依賴屬性。/// 
    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. 將動畫應(yīng)用到Button的ControlTemplate

同樣的技術(shù),配合ControlTemplate可以制作很有趣的按鈕:

如何使用Shape做動畫

PointerEntered時,按鈕的邊框從進(jìn)入點(diǎn)向反方向延伸。PointerExited時,邊框從反方向向移出點(diǎn)消退。要做到這點(diǎn)需要在PointerEntered時改變邊框的方向,使用了ChangeAngleToEnterPointerBehavior:

public class ChangeAngleToEnterPointerBehavior : Behavior
{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
{/// /// 獲取或設(shè)置Progress的值///   
public double Progress
    {
    get { 
    return (double)GetValue(ProgressProperty); 
    }
    set { 
    SetValue(ProgressProperty, value); 
    }
    }/// /// 標(biāo)識 Progress 依賴屬性。/// 
    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如下:

注意:我沒有鼓勵任何人自定義按鈕外觀的意思,能用系統(tǒng)自帶的動畫或樣式就盡量用系統(tǒng)自帶的,沒有設(shè)計師的情況下
又想UI做得與眾不同通常會做得很難看。想要UI好看,合理的布局、合理的顏色、合理的圖片就足夠了。

6. 結(jié)語

在學(xué)習(xí)Shape的過程中覺得好玩就做了很多嘗試,因為以前工作中做過不少等待、進(jìn)度的動畫,所以這次就試著做出本文的動畫。
XAML的傳統(tǒng)動畫并沒有提供太多功能,主要是ColorAnimation、DoubleAnimation、PointAnimation三種,不過靠Binding和Converter可以彌補(bǔ)這方面的不足,實現(xiàn)很多需要的功能。
本文的一些動畫效果參考了SVG的動畫。話說回來,Windows 10 1703新增了SvgImageSource,不過看起來只是簡單地將SVG翻譯成對應(yīng)的Shape,然后用Shape呈現(xiàn),不少高級特性都不支持(如下圖陰影的濾鏡),用法如下:

SvgImageSource:
如何使用Shape做動畫

原本的Svg:
如何使用Shape做動畫

以上是“如何使用Shape做動畫”這篇文章的所有內(nèi)容,感謝各位的閱讀!希望分享的內(nèi)容對大家有幫助,更多相關(guān)知識,歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道!


名稱欄目:如何使用Shape做動畫
文章來源:http://weahome.cn/article/peppdd.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部