1. 程式人生 > >【Win 10 應用開發】UI Composition 劄記(六):動畫

【Win 10 應用開發】UI Composition 劄記(六):動畫

onclick 相對 行修改 log review asset 是你 express iteration

動畫在 XAML 中也有,而且基本上與 WPF 中的用法一樣。不過,在 UWP 中,動畫還有一種表現方式—— 通過 UI Composition 來創建。

基於 UI Composition 的動畫,相對於 XAML 動畫,有以下優點:

1、不使用 UI 線程,XAML 動畫是共享 UI 線程的,而 Composition 中的動畫是使用輔助線程的。

2、Composition 動畫支持表達式(計算公式)來產生動畫,相對靈活。

老周的建議是:兩者都用,因為基於 XAML 和基於 Composition 的動畫各有特點,在應用程序中都可以混合來用。我們不要被一些不健康的思想所毒害,世界上沒有什麽技術可以取代和不取代,只要用得上,哪怕是 1000 年前的技術也同樣適用(事實也表明有些東西我們現在科技這麽發達竟然做不到,可咱們祖先在 N 千萬年前反而能做到)。所以,我們應該向莊子先生學習,思維要靈活,合理應用一切可用的資源。

對於動畫,不管是啥類型的,其實基本要素都一樣,首先,動畫是基於時間變化而產生的“眼球欺騙”技術,只是一個個幀隨著時間變化不斷改變,利用人眼的視覺延時誤差,讓我們覺得目標好像在動。其實,人看著在動,但是貓的眼睛看就不見得是這樣了。故,動畫會有時間線,可以說是動畫的時長。

其次是值,比如,你要讓綠色變成紅色,那麽在特定的時間點上,你就應該給一個顏色值;再比如,一只豬從屏幕左邊滑到右邊,那麽在對應的時間上,你要給出一個坐標值,表明這頭豬滑行了多長距離。

然後就是動畫的作用目標,就是你要把動畫應用到哪個對象的哪個屬性上,要是想改變不透明度,就會選擇應用到 K 對象的 Opacity 屬性上。

在 Composition API 中,Visual 類的屬性都支持動畫,如 Offset,Size 等屬性。

下面我們先介紹一種最經典的動畫類型——關鍵幀。

所謂關鍵幀動畫,就是在時間線上添加 N 個(N 肯定是有效數字)時間點,這些時間點會與一個目標值對應,當動畫播放到這個關鍵幀時,會改變目標值。而關鍵幀之間的部分,就交給某些算法去計算過度動畫。

舉個例子,用關鍵幀動畫改變某對象的 Opacity 屬性(不透明度),時間線總長為 10 秒,在第 0 秒時設定值為 0,即全透明,然後,在第 5 秒時設定值為 0.5,即半透明,最後在第 10 秒處將值設定為 1,表示完全不透明。

下面咱們玩一個例子。

XAML 代碼如下。

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition Height="auto"/>
        </Grid.RowDefinitions>
        <Canvas>
            <Image Name="img" Height="200" Source="Assets/1.png"/>
        </Canvas>
        <StackPanel Grid.Row="1" Margin="8" Orientation="Horizontal" HorizontalAlignment="Center">
            <Button Content="開始" Click="OnStart"/>
            <Button Content="停止" Margin="20,0,0,0" Click="OnStop"/>
        </StackPanel>
    </Grid>

由於老周比較窮,所以界面放的東西不多。Image 控件用來顯示多拉 B 夢的照片,然後,對,下面兩個按鈕,一個用來啟動播放動畫,另一個用來停止動畫。

Image 為啥要放到 Canvas 容器中呢,因為這個容器,你懂的,它是絕對定位。如果是一個 Grid,可能會受到對齊方式的影響,這樣後面我們要對這個對象的位置進行修改時就很不好弄。

切換到代碼視圖,在頁面類中聲明兩個變量。

        Vector3KeyFrameAnimation Animation = null;
        Visual imageVs = null;

之所以在類級別聲明它們,因為稍後要用。這裏,Vector3KeyFrameAnimation 表示關鍵幀動畫是針對 Vector3 這種值進行處理的,待會我們要讓 Image 控件中的 多拉 B 夢 移動。通過老周前面的介紹,大夥應該記得,Offset 屬性表示對象的位置,它有三個值:X、Y、Z,所以,我們要用 Vector3 而不是 Vector2,Vector2 只有兩個值,適用於 Size 屬性。

如果你要對顏色做動畫處理,那就用 ColorKeyFrameAnimation,道理一樣,它使用的值就是 Color 結構類型。如果你進行動畫處理的目標屬性只有一個值,比如 Opacity ,只是一個 float 值,那麽,你就可以選用 ScalarKeyFrameAnimation。

在頁面的構造函數中,我們初始化一下各個對象。

        public MainPage()
        {
            this.InitializeComponent();

            // 獲取可視化對象
            imageVs = ElementCompositionPreview.GetElementVisual(img);
            var compos = imageVs.Compositor;
            // 創建關鍵幀動畫
            Animation = compos.CreateVector3KeyFrameAnimation();
            // 時長為 4 秒
            Animation.Duration = TimeSpan.FromSeconds(4d);
            // 插入關鍵幀
            Animation.InsertKeyFrame(0f, new Vector3(0f, 0f, 0f));
            Animation.InsertKeyFrame(0.5f, new Vector3(500f, 360f, 30f));
            Animation.InsertKeyFrame(0.7f, new Vector3(260f, 125f, 45f));
            Animation.InsertKeyFrame(1f, new Vector3(20f, 20f, 60f));
        }

老周在前面的博文中說過,Composition 要用到的各種資源,都可以通過 Compositor 實例的 CreateXXX 方法來創建,動畫也是如此。關鍵幀動畫一定要記得添加關鍵幀,InsertKeyFrame 方法的第一個參數是關鍵幀在時間線上的位置,註意,它采用的是相對值(百分比),從 0.0 到 1.0,如果是 1 則表示關鍵幀在時間線 100% 處,如果是 0.5,關鍵幀正好位於時間線中央。

插入關鍵幀時要記得,它是用百分比來計算的。另外,不要忘了設置一下 Duration 屬性,就是動畫時間線的長度。

接下來,處理一下那兩個按鈕的 Click 事件,分別啟動和停止動畫。

        private void OnStart(object sender, RoutedEventArgs e)
        {
            imageVs?.StartAnimation(nameof(Visual.Offset), Animation);
        }

        private void OnStop(object sender, RoutedEventArgs e)
        {
            imageVs?.StopAnimation(nameof(Visual.Offset));
        }

要讓動畫對象與目標屬性關聯,可以調用可視化對象的 StartAnimation 方法,第一個參數要指定要應用到的屬性名字,本示例是應用到 Offset 屬性上。要停止正在播放的動畫,只需要把屬性名傳給 StopAnimation 方法即可。

一起來看看效果,多拉B夢在家裏經常這樣鍛煉身體的。

技術分享圖片

由於 gif 動畫的幀率問題,所以你看到截圖上的動畫是不流暢的,想實際體驗就自己動手吧。

下面,老周再給大夥伴們演示一個基於顏色值的動畫。

XAML 代碼很簡單,就放一個 Canvas 就行了。

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <Canvas Name="cvs"/>
    </Grid>

然後轉到頁面代碼,初始化一下動畫。

        public MainPage()
        {
            this.InitializeComponent();

            Visual cvsv = ElementCompositionPreview.GetElementVisual(cvs);
            Compositor compos = cvsv.Compositor;
            // 創建顏色關鍵幀動畫
            ColorKeyFrameAnimation animat = compos.CreateColorKeyFrameAnimation();
            // 時間長度
            animat.Duration = TimeSpan.FromSeconds(6d);
            // 讓它永遠循環播放
            animat.IterationBehavior = AnimationIterationBehavior.Forever;
            // 插入關鍵幀
            animat.InsertKeyFrame(0f, Colors.Red);
            animat.InsertKeyFrame(0.6f, Colors.Blue);
            animat.InsertKeyFrame(1f, Colors.Yellow);
            // 顏色變化模式
            animat.InterpolationColorSpace = CompositionColorSpace.Rgb;
            // 創建顏色畫刷
            CompositionColorBrush brush = compos.CreateColorBrush(Colors.Black);
            // 創建可視化對象
            SpriteVisual sv = compos.CreateSpriteVisual();
            // 設置大小和位置
            sv.Size = new Vector2(360f, 250f);
            sv.Offset = new Vector3(150f, 140f, 0f);
            // 關聯畫刷
            sv.Brush = brush;
            // 把可視化對象插入 XAML 可視化樹
            ElementCompositionPreview.SetElementChildVisual(cvs, sv);
            // 啟動動畫
            brush.StartAnimation(nameof(CompositionColorBrush.Color), animat);
        }

代碼比較長,但有些我前面文章中已經介紹過,我們重點看這段。

            // 創建顏色關鍵幀動畫
            ColorKeyFrameAnimation animat = compos.CreateColorKeyFrameAnimation();
            // 時間長度
            animat.Duration = TimeSpan.FromSeconds(6d);
            // 讓它永遠循環播放
            animat.IterationBehavior = AnimationIterationBehavior.Forever;
            // 插入關鍵幀
            animat.InsertKeyFrame(0f, Colors.Red);
            animat.InsertKeyFrame(0.6f, Colors.Blue);
            animat.InsertKeyFrame(1f, Colors.Yellow);
            // 顏色變化模式
            animat.InterpolationColorSpace = CompositionColorSpace.Rgb;

首先,當然要創建基於顏色的關鍵幀動畫對象,然後設置一下參數,插入關鍵幀相信你都會了,跟前面那個多拉B夢移動的例子差不多,只是值的類型變成 Color 值而已。

IterationBehavior 屬性用來設置動畫的循環次數,如果你設置為 Count,那麽,就要為動畫的 IterationCount 屬性指定一個數值,比如3表示播放三次。這裏我設置為 Forever,表示動畫永久循環播放。

InterpolationColorSpace 屬性是個很好玩的東西,主要設置顏色在進行動畫過程如何過度。它用 CompositionColorSpace 枚舉來規範幾個值。經過測試發現,貌似使用 RGB 形式動畫比較正常, RgbLinear 會發生錯誤,但 Rgb 是正常的,所以我就選用 Rgb 模式了。

最後在啟動動畫時要註意,動畫的作用是改變顏色,所以它的應用對象應該是畫刷 CompositionColorBrush 的 Color 屬性,所以,調用 StartAnimation 方法應該在畫刷對象上,而不是 SpriteVisual 對象。

來,看看效果吧。

技術分享圖片

接下來,我們看一下跳躍式動畫。所謂跳躍式動畫,就是它可以模仿彈簧的物理特性,在動畫停止之前有一個回彈的動作。這個動畫用在控件特效很不錯。

下面我們來個彈球球的實驗。

首先,我們在 XAML 中放一個藍色的球。

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <Canvas>
            <Ellipse Name="ell" Width="100" Height="100" Fill="Blue" Canvas.Top="40" Canvas.Left="20"/>
        </Canvas>
    </Grid>

隨後,轉到代碼視圖,輸入以下代碼。

        public MainPage()
        {
            this.InitializeComponent();

            Visual ellVisual = ElementCompositionPreview.GetElementVisual(ell);
            var compositor = ellVisual.Compositor;
            var springAnmt = compositor.CreateSpringScalarAnimation();
            springAnmt.InitialValue = 0f;
            springAnmt.FinalValue = 400f;
            springAnmt.Period = TimeSpan.FromMilliseconds(60d);
            springAnmt.DampingRatio = 0.2f;
            springAnmt.StopBehavior = AnimationStopBehavior.SetToInitialValue;

            Windows.System.Threading.ThreadPoolTimer.CreateTimer(timer =>
            {
                ellVisual?.StartAnimation("Offset.X", springAnmt);
            }, TimeSpan.FromSeconds(3d));
        }

InitialValue 和 FinalValue 屬性分別用於指定動畫的初始值和最終值。如果不指定初始值,那就默認使用當前的值作為初始值。這裏有兩個屬性我們要重點關註的。第一個是 DampingRatio ,它是一個大於 0 的值,它表示對象在完成動畫時振動的衰減程度,就像一個球,它落到地面上會彈起來,可是,它不可能永遠都在那裏彈,可能彈幾下它就落地不動了。彈性勢能會不斷地衰減。

如果你把 DampingRatio 屬性設置為 0 ,那麽,物體就會不停地在彈,而且振幅很大,這是不符合現實物理現象的,因此,這個值你不能用0,一般是用大於0小於1之間,如果大於/等於1,物體幾乎不會振動,非但不振動,反而速度會逐漸變慢。所以,這個 DampingRatio 屬性值,當值小於 1 時,就像在彈簧上彈起來,而當其大於或等於 1 時,就等同於用手按彈簧,越往下按,阻力越大。

還有一個屬性,是配合 DampingRatio 使用的,它就是 Period,它表示每一輪振動的時間,時間越短,物體振動就越快。

本例的設置如下。

  springAnmt.Period = TimeSpan.FromMilliseconds(60d);
  springAnmt.DampingRatio = 0.2f;

表示振動周期為 60 毫秒,振動衰減系數為 0.2,這個值振感明顯,但不會振個不停。

看看效果吧。

技術分享圖片

其他的跳躍式動畫的用法也一樣,本例所針對的值是可視化對象的Offset 屬性的 X 值,所以是單個 float 值,因此使用 SpringScalarNaturalMotionAnimation。如果處理動畫的目標是其他復雜的值,可以用 SpringVector2NaturalMotionAnimation 或 SpringVector3NaturalMotionAnimation,用法都是一樣的,我就不廢話了,有興趣的夥伴可以試試。

本文最後,我們看一下隱式動畫。啥叫隱式動畫?就是你不必調用 StartAnimation 方法來啟動動畫,當一些支持動畫的屬性更改時,會自動產生動畫。比如,Visual 類的屬性基本支持動畫,像 Opacity、Offset、Orientation、Size 等。

Composition 對象都從 CompositionObject 類上繼承了一個叫 ImplicitAnimations屬性,它是一個集合,我們可以將多個動畫對象加進去,然後,當指定的對象屬性更改時,會自動產生動畫。

ImplicitAnimationCollection 集合是以字典數據形式來存儲的,Key 是要進行動畫處理的屬性名,Value 是對應的動畫實例。這裏你可以用關鍵幀動畫,或者上面講到過的跳躍式動畫都可以。為了最大限度保證動畫的兼容性,隱式動畫會存在一定的自動轉換功能。比如,一個針對 Vector3 的動畫可以用於 Vector2 值的屬性,它會從X,Y,Z中取兩個值來填充 Vector2 值。

下面,我們還是用示例來說明吧。我們在 XAML 中放一個物體。

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition Height="auto"/>
        </Grid.RowDefinitions>
        <Canvas>
            <Ellipse Name="ell" Fill="Green" Width="150" Height="150"/>
        </Canvas>
        <StackPanel Grid.Row="1" Margin="2,12" Orientation="Horizontal" HorizontalAlignment="Center">
            <Button Content="動作 1" Margin="0,0,24,0" Click="OnClick1"/>
            <Button Content="動作 2" Margin="0,0,24,0" Click="OnClick2"/>
            <Button Content="動作 3" Margin="0,0,24,0" Click="OnClick3"/>
            <Button Content="動作 4" Click="OnClick4" />
        </StackPanel>
    </Grid>

下面的四個按鈕的作用是修改上面那個圓的 Offset,Opacity 屬性,說白了,就是修改它的位置和不透明度。

現在,我們轉到代碼視圖,先在類級別聲明一個 Visual 類型的變量,它表示上面的 Ellipse 對象的可視化對象引用,應用我們在四個按鈕的 Click 事件處理代碼中要訪問它,所以把其作為類級別的字段。

Visual ell_vs;

然後,在頁面類的構造函數中初始化。

        public MainPage()
        {
            this.InitializeComponent();

            // 設置動畫
            ell_vs = ElementCompositionPreview.GetElementVisual(ell);
            Compositor compos = ell_vs.Compositor;
            ImplicitAnimationCollection implicitAnmts = compos.CreateImplicitAnimationCollection();

            ScalarKeyFrameAnimation opacityAnmt = compos.CreateScalarKeyFrameAnimation();
            opacityAnmt.InsertExpressionKeyFrame(0f, "this.StartingValue");
            opacityAnmt.InsertExpressionKeyFrame(1f, "this.FinalValue");
            opacityAnmt.Duration = TimeSpan.FromSeconds(1d);
            opacityAnmt.Target = nameof(Visual.Opacity);

            Vector3KeyFrameAnimation offsetAnmt = compos.CreateVector3KeyFrameAnimation();
            offsetAnmt.InsertExpressionKeyFrame(0f, "this.StartingValue");
            offsetAnmt.InsertExpressionKeyFrame(1f, "this.FinalValue");
            offsetAnmt.Duration = TimeSpan.FromSeconds(1d);
            offsetAnmt.Target = nameof(Visual.Offset);

            implicitAnmts.Add(nameof(Visual.Offset), offsetAnmt);
            implicitAnmts[nameof(Visual.Opacity)] = opacityAnmt;

            ell_vs.ImplicitAnimations = implicitAnmts;
        }

請註意,在為動畫插入關鍵幀時,使用的是表達式的方法,因為我們後面是對對象的不透明度和位置進行動態調整,所以,這裏的代碼並不能準確知道動畫的最終值是什麽,所以,使用了這兩個關鍵字:

this.StartingValue:表示動畫的初始值,它會根據實際情況自動填充值。

this.FinalValue:指的是動畫的最終值,它會自動填充。

在這個例子中,StartingValue 就是對象上一次被修改後的值,比如,第一次把 Opacity 改為 0.5,那麽下一輪動畫時的初始就是這個 0.5。FinalValue就是屬性的最新值,比如Opacity 原來是 1,現在你改為 0.6,那麽對本次動畫來說,StartingValue 就是 1,FinalValue 就是 0.6 了。

對了,還有一點,你得為動畫的 Target 屬性賦值,比如動畫是作用於 Opacity 屬性上的,就賦 Opacity 。這個隱式動畫比較特殊,一定要這樣賦值。

然後,我們給四個按鈕弄弄 Click 事件。

        private void OnClick1(object sender, RoutedEventArgs e)
        {
            ell_vs.Opacity = 0.2f;
            ell_vs.Offset = new Vector3(300f, 250f, -30f);
        }

        private void OnClick2(object sender, RoutedEventArgs e)
        {
            ell_vs.Opacity = 0.8f;
            ell_vs.Offset = new Vector3(400f, 320f, 130f);
        }

        private void OnClick3(object sender, RoutedEventArgs e)
        {
            ell_vs.Opacity = 1f;
            ell_vs.Offset = new Vector3(150f, 60f, -70f);
        }

        private void OnClick4(object sender, RoutedEventArgs e)
        {
            ell_vs.Offset = new Vector3(20f, 200f, 50f);
            ell_vs.Opacity = 0.5f;
        }

好,現在可以看效果了。運行應用,分別點四個按鈕,看看它們這樣修改對象的屬性會不會更生動。

技術分享圖片

好了,本文就講到這裏吧。

你一定會記得,還有一個表達式動畫,那個咱們留到下一篇文章再聊,本篇就先聊到這裏。示例的代碼我都基本貼上了,所以我就不上傳示例了。

【Win 10 應用開發】UI Composition 劄記(六):動畫