【UWP】對 Thickness 類型屬性進行動畫


好幾個月沒寫 blog 了,一個是在忙新版的碧影壁紙,另一方面是等(觀望)周年更新的 api(不過現在還是比較失望,仍然沒法支持矩形以外的 Clip)。閑話少說,進入主題。

 

在 UWP 中,出於性能考慮,微軟是不建議、不推薦對會影響布局的屬性進行動畫的。例如 Width 和 Height 這種,如果真的需要對這些屬性進行動畫的話(畢竟需求就擺在那里),可以將 Animation 的 EnableDependentAnimation 屬性設置為 true 來對這些屬性進行動畫的。

但是,對於 Thickness 類型來說,這是行不通的,因為 UWP 中並沒有 ThicknessAnimation 這種動畫類型(PS:WPF 里是有這種動畫類型的說)。

不過既然我標題都寫了出來,那辦法肯定是有的。Thickness 就是四個方向分量,也就是說,對這四個方向分量進行動畫就等於對這個 Thickness 進行了動畫。

還有另外一點要注意的是,Thickness 類型的四個屬性並不是依賴屬性。

例如:

control.Margin.Left = 10;

這一句是沒有效果的。

要實現效果,只能對 Margin 屬性從新賦一個值:

var margin = control.Margin;
margin.Left = 10;
control.Margin = margin;

也就是說,我們需要一個可綁定的 Margin。(我就叫它 BindableMargin)

新建一個用戶控件

sp160831_203229

為什么是用戶控件?因為經過我的發現,我們自定義的類的依賴屬性,得有 xaml 文件才能進行動畫。(不信你可以試試^-^)

然后修改 BindableMargin.xaml 如下:

<DependencyObject x:Class="AnimateThicknessDemo.BindableMargin"
                  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                  xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
                  xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
                  mc:Ignorable="d" />

相當簡單的一段 xaml,設計器就無視好了。重點在 BindableMargin.xaml.cs 里,修改代碼:

    public partial class BindableMargin
    {
        public static readonly DependencyProperty BottomProperty = DependencyProperty.Register(nameof(Bottom), typeof(double), typeof(BindableMargin), new PropertyMetadata(default(double), BottomChanged));

        public static readonly DependencyProperty LeftProperty = DependencyProperty.Register(nameof(Left), typeof(double), typeof(BindableMargin), new PropertyMetadata(default(double), LeftChanged));

        public static readonly DependencyProperty RightProperty = DependencyProperty.Register(nameof(Right), typeof(double), typeof(BindableMargin), new PropertyMetadata(default(double), RightChanged));

        public static readonly DependencyProperty TopProperty = DependencyProperty.Register(nameof(Top), typeof(double), typeof(BindableMargin), new PropertyMetadata(default(double), TopChanged));

        private readonly FrameworkElement _owner;

        public BindableMargin(FrameworkElement owner)
        {
            if (owner == null)
            {
                throw new ArgumentNullException(nameof(owner));
            }

            _owner = owner;
        }

        public double Bottom
        {
            get
            {
                var ownerBottom = _owner.Margin.Bottom;
                var bottom = (double)GetValue(BottomProperty);
                if (ownerBottom.Equals(bottom) == false)
                {
                    SetValue(BottomProperty, ownerBottom);
                }
                return ownerBottom;
            }
            set
            {
                SetValue(BottomProperty, value);
            }
        }

        public double Left
        {
            get
            {
                var ownerLeft = _owner.Margin.Left;
                var left = (double)GetValue(LeftProperty);
                if (ownerLeft.Equals(left) == false)
                {
                    SetValue(LeftProperty, ownerLeft);
                }
                return ownerLeft;
            }
            set
            {
                SetValue(LeftProperty, value);
            }
        }

        public double Right
        {
            get
            {
                var ownerRight = _owner.Margin.Right;
                var right = (double)GetValue(RightProperty);
                if (ownerRight.Equals(right) == false)
                {
                    SetValue(RightProperty, ownerRight);
                }
                return ownerRight;
            }
            set
            {
                SetValue(RightProperty, value);
            }
        }

        public double Top
        {
            get
            {
                var ownerTop = _owner.Margin.Top;
                var top = (double)GetValue(TopProperty);
                if (ownerTop.Equals(top) == false)
                {
                    SetValue(TopProperty, ownerTop);
                }
                return ownerTop;
            }
            set
            {
                SetValue(TopProperty, value);
            }
        }

        private static void BottomChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var obj = (BindableMargin)d;
            var value = (double)e.NewValue;

            var owner = obj._owner;
            var margin = owner.Margin;
            margin.Bottom = value;
            owner.Margin = margin;
        }

        private static void LeftChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var obj = (BindableMargin)d;
            var value = (double)e.NewValue;

            var owner = obj._owner;
            var margin = owner.Margin;
            margin.Left = value;
            owner.Margin = margin;
        }

        private static void RightChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var obj = (BindableMargin)d;
            var value = (double)e.NewValue;

            var owner = obj._owner;
            var margin = owner.Margin;
            margin.Right = value;
            owner.Margin = margin;
        }

        private static void TopChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var obj = (BindableMargin)d;
            var value = (double)e.NewValue;

            var owner = obj._owner;
            var margin = owner.Margin;
            margin.Top = value;
            owner.Margin = margin;
        }
    }

別看代碼這么多,其實不復雜。構造函數傳入需要動畫的控件。然后四個方向的依賴屬性,值發生改變時回寫到控件上。

然后動畫的例子代碼:

BindableMargin margin = new BindableMargin(control);
DoubleAnimation animation = new DoubleAnimation();
animation.EnableDependentAnimation = true;
animation.From = 0;
animation.To = 100;
animation.Duration = TimeSpan.FromSeconds(1);
Storyboard.SetTarget(animation, margin);
Storyboard.SetTargetProperty(animation, "Left");
await storyboard.BeginAsync();// WinRTXamlToolkit 里的擴展方法。
margin.Left = 100;

另外建議在動畫播放完畢后,執行一次常規的賦值操作(一般賦最終值),因為視乎機器的配置,Storyboard 會有一定程度的跳幀,在低端的機器,可能動畫就完全跳過去了。

 

說了這么多,還是說說有啥應用吧。

fq

這是一個類似 IT 之家的通知控件。通過動畫了 Margin 的 Right 來實現的。

Demo 下載地址:AnimateThicknessDemo.zip

當然應用還有很多,例如對 Border 的圓角進行動畫。通過這么一種“橋”的方式,我們可以對很多屬性,並不局限於 Thickness 類型,也進行動畫,這里就留給各位看官發揮想象了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM