天天看點

【Win 10 應用開發】UI Composition 劄記(七):基于表達式的動畫

上一篇爛文中,老周給大夥伴們介紹過了幾個比較好玩的動畫。本篇咱們深化主題,說一說基于表達式的動畫。這名字好了解,就是你可以用公式 / 等式來産生動畫的目标值。比如,你想讓某個可視化對象的高度減半,你的表達可以這樣寫: width / 2,其中,width 表示某對象的寬度。

既然說到基于表達式的動畫了,就得介紹一個重要的類型:ExpressionAnimation,它專用來實作表達式動畫的。它有一個 Expression 屬性,字元串類型,用來設定計算動畫目标值的等式。

有關表達式的文法,老周就不廢話了,其實文法和 C 類語言差不多。大夥在用的時候,也不用去記的,你隻要檢視 ExpressionAnimation 類的文檔,就能看到完整的幫助内容了。不記得怎麼寫的時候檢視一下幫助文檔就可以了。

注意,ExpressionAnimation 産生的動畫,你是不能控制其時間長度的,它會由系統來進行計算,而且這個動畫很好玩,它可以跟蹤參數的變化,當引用參數發生變化時,會更新動畫。這類似于資料綁定的功能。

下面我們舉個例子。

XAML 代碼很簡單。

<Grid Name="root" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <Rectangle Name="rect" Fill="Green" Width="200" Height="200"/>
    </Grid>      

頁面的根元素是 Grid,然後在裡面放一個矩形。

待會兒我們用動畫來旋轉這個矩形,怎麼轉呢,計算公式如下:

    Grid的寬度 ÷ Grid的高度 × 360

這個公式會得出矩形要旋轉的角度(機關為度)。Grid 的寬度和高度不需要我們寫代碼去設定,因為預設是填充對齊的,是以,隻要我們調整視窗的大小,Grid 的大小就會跟着變。

轉到代碼檔案,在頁面類的構造函數中,輸入這些代碼。

public MainPage()
        {
            this.InitializeComponent();

            Visual v_root = ElementCompositionPreview.GetElementVisual(root);
            Visual v_rect = ElementCompositionPreview.GetElementVisual(rect);
            
            var compositor = v_rect.Compositor;

            // 旋轉動畫
            ExpressionAnimation anmitRot = compositor.CreateExpressionAnimation();
            anmitRot.Expression = "360 * container.Size.X / container.Size.Y";
            anmitRot.SetReferenceParameter("container", v_root);

            v_rect.StartAnimation(nameof(Visual.RotationAngleInDegrees), anmitRot);
        }      

請你注意看那個表達式:360 * container.Size.X / container.Size.Y,可能你會疑問,這個 container 是什麼鬼?這個不是鬼,是我随便取的名字,你愛取其他名字都行,比如,你可以寫成 360 * dog.Size.X / dog.Size.Y。這其實是個占位符,它實際上是指向代表 Grid 的可視化對象,在運作時,會用真實的對象替換掉這個占位符。那麼,這個參數占位符怎麼替換呢?

你有沒有發現,CompositionAnimation 類有一堆方法,命名很 TMD 有規律,全是一家人,都叫 Set*****Parameter,看到了沒?

【Win 10 應用開發】UI Composition 劄記(七):基于表達式的動畫

你以前是不是不知道這些方法有毛用,現在你應該猜它們有什麼用了。對啊,就是用來設定替換占位符的實際值的。比如,我們這個示例子,它的表達式是這樣的:

360 * container.Size.X / container.Size.Y

其實這個 container 就是指代碼中的 v_root 對象,是以我們用這一行代碼,就可以在運作階段,用 v_root把 container 占位符替換掉,變成:

360 * v_root.Size.X / v_root.Size.Y

anmitRot.SetReferenceParameter("container", v_root);      

而 v_root 就是 XAML 中的 Grid 對象,是以,這樣就實作了Grid的寬度除以Grid的高度的計算了,再乘以 360 就完事了。

現在你明白了吧,世上很多事情,别想得太複雜,其實人生中很多事情是很簡單,就是人總喜歡搞複雜了。

這個表達式計算出來的結果是 float 類型的值,這個你應該能了解的,因為它計算之後就是一個數值,不可能會産生一個 Vector2 值的。接着,我們把這個動畫與 v_rect,注意是rect,因為我們要旋轉的是矩形,不是Grid,用 StartAnimation 方法使之與 RotationAngleInDegrees 屬性綁定就好了,RotationAngleInDegrees 表示的角度,不是弧度。

v_rect.StartAnimation(nameof(Visual.RotationAngleInDegrees), anmitRot);      

好,現在這個東東已經可以運作的了,試試看。

隻要調整視窗的大小就可以了,Grid 的大小會自動更新。

你是不是對這個效果不太滿意?你會看到,媽的,旋轉的中心怎麼不是在矩形中央?看着不爽。對的,預設是在左上角的,你懂的。是以,我們可以再加一個 Expression 動畫,把旋轉中心點移到矩形中央。

public MainPage()
        {
            ……

            Visual v_root = ElementCompositionPreview.GetElementVisual(root);
            Visual v_rect = ElementCompositionPreview.GetElementVisual(rect);
            
            var compositor = v_rect.Compositor;

            // 中心點
            ExpressionAnimation anmtCP = compositor.CreateExpressionAnimation("Vector3(this.Target.Size.X / 2, this.Target.Size.Y / 2, 0)");
            v_rect.StartAnimation("CenterPoint", anmtCP);

              ……
        }      

這個表達式裡面,用到了一個函數,叫 Vector3,它類似于調用 Vector3 結構的構造函數 new Vector3(...),是以這個表達式計算後會傳回一個 Vector3 類型的值,它需要 X,Y,Z 三個值,因為Visual 類要修改中心點,是設定 CenterPoint 屬性的,而 CenterPoint 屬性是 Vector3 類型的,是以我們動畫産生的值,必須與目标值的類型比對。

Z軸上我們不必理它,預設 0 就可以了,主要是把矩形的寬度和高度分别除以 2。

各位可能注意到了,表達式中用了 this.Target,它指向的是 v_rect,因為這個動畫是應用到 v_rect 上的(它調用了 StartAnimation 方法),是以,這個target 就是應用動畫的對象,這就等于,CenterPoint 的值取自 Size / 2。

現在,我們再運作一下,旋轉中心就在矩形的中央了。

【Win 10 應用開發】UI Composition 劄記(七):基于表達式的動畫

好玩吧,其實啊,還有一件事要告訴你,表達式不僅在 ExpressionAnimation 動畫中可用,在關鍵幀動畫中也能用的。隻是有個差別,ExpressionAnimation 動畫它會對計算進行跟蹤,能做到 “綁定” 的效果,但關鍵幀動畫中是不會跟蹤計算的,即所設定的表達式是一次性的。

下面給大夥們示範一下如何在關鍵幀動畫中使用表達式。

在 XAML 文檔中,放一個矩形,寬度不要設定得太大,後面咱們用動畫來放大它。

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition Height="auto"/>
        </Grid.RowDefinitions>
        <Rectangle Name="rect" Width="20" Height="120" Fill="Red" HorizontalAlignment="Left"/>
        <Button Grid.Row="1" Margin="12" Content="Play" Width="300" Click="OnClick" HorizontalAlignment="Center"/>
    </Grid>      

現在處理一個按鈕的 Click 事件。用關鍵幀動畫來對矩形進行橫向(X軸)放大。

private void OnClick(object sender, RoutedEventArgs e)
        {
            Visual vs_rect = ElementCompositionPreview.GetElementVisual(rect);
            Compositor compositor = vs_rect.Compositor;

            ScalarKeyFrameAnimation animat = compositor.CreateScalarKeyFrameAnimation();
            animat.Duration = TimeSpan.FromSeconds(1d);
            // 插入關鍵幀
            animat.InsertExpressionKeyFrame(0f, "this.Target.Scale.X");
            animat.InsertExpressionKeyFrame(1f, "this.Target.Scale.X >= max ? min : this.Target.Scale.X + val");
            // max、min、val 都是占位符
            // 替換占位符
            animat.SetScalarParameter("max", 30f);
            animat.SetScalarParameter("min", 1f);
            animat.SetScalarParameter("val", 3f);
            vs_rect.StartAnimation("Scale.X", animat);
        }      

這個關鍵幀動畫有兩個關鍵幀,第一幀位于動畫開始處,目标值就是矩形在X軸上的目前縮放倍數。第二個關鍵幀在動畫的結尾處,表達式有點複雜。老周單獨寫一遍給你看看。

this.Target.Scale.X >= max ? min : this.Target.Scale.X + val      

這裡用到了三目運算符,其實和C類語言一樣,condition ? ifTrue : ifFalse,如果矩形的X軸上的縮放值大于/等于 max 的話,那就直接傳回 min ,否則就把縮放倍數加上 val。Visual 類的 Scale 屬性類型為 Vector3 ,它有 X,Y,Z 三個值,表示對象在三個軸方向上的縮放倍數。在這個例子中,咱們隻處理 X 軸上的縮放。

其中,max、min、val 三個值都是我随便命名的占位符。是以要用具體的值去替換。

animat.SetScalarParameter("max", 30f);
            animat.SetScalarParameter("min", 1f);
            animat.SetScalarParameter("val", 3f);      

替換之後,這個表達式在運作時就像這樣:

this.Target.Scale.X >= 30 ? 1 : this.Target.Scale.X + 3      

故,當矩形放大到 30 倍以後,會跳回到 1 倍。可以看看運作效果。

【Win 10 應用開發】UI Composition 劄記(七):基于表達式的動畫

好玩吧。本篇的最後一環,老周必須再給大夥介紹一個類,因為這個類在動畫應用中也相當重要的。它叫 CompositionPropertySet,由于它是從 CompositionObject 類派生,是以,如果調用動畫對象的 SetReferenceParameter 方法去設定命名參數時,ExpressionAnimation 動畫能夠自動跟蹤 CompositionPropertySet 對象的更新。

CompositionPropertySet 用法有點像字典,Key 是字元串,你可以使用 Insert**** 方法來插入各種類型的值,可以用 TryGet**** 方法來檢索。

有些時候,動畫所跟蹤的對象不一定都是 Visual 對象的,可能是一些不可見的對象,比如某個顔色,某個數值,這種情形下,使用 CompositionPropertySet 類是一個很高大上的選擇。

不知道咋用?沒事,下面還是老規矩,動手做,學程式設計不動手,學三輩子也學不會。

先看看 XAML 代碼。

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <Border Width="300" Height="300" BorderBrush="Blue" BorderThickness="5" Name="bd" Padding="15" PointerMoved="bd_PointerMoved">
            <Ellipse Name="ell" Fill="Orange"/>
        </Border>
    </Grid>      

很簡單,當滑鼠在 Border 上移動時,用動畫改變 Ellipse 的不透明度。計算方法很簡單,就是用目前的指針坐标的 X 值除以 Y 值,即 x / y,得出 Opacity 的值。

轉到代碼檔案,要在頁面類級别聲明一個變量。

public sealed partial class MainPage : Page
    {
        CompositionPropertySet compPropset = null;

        ……
    }      

因為後面我們在代碼中要動态修改它,是以要放到類級别。

然後,在頁面類的構造函數中設定一下動畫。

public MainPage()
        {
            this.InitializeComponent();

            // 擷取相應的 Visual 對象
            Visual vs_ell = ElementCompositionPreview.GetElementVisual(ell);
            // 建立 PropertySet 執行個體
            compPropset = vs_ell.Compositor.CreatePropertySet();
            // 設定預設值
            compPropset.InsertVector2("MyValue", new Vector2(1f, 1f));
            // 建立動畫
            ExpressionAnimation animat = vs_ell.Compositor.CreateExpressionAnimation();
            animat.Expression = "p.MyValue.X / p.MyValue.Y";
            // 設定參數
            animat.SetReferenceParameter("p", compPropset);
            // 啟動動畫
            vs_ell.StartAnimation("Opacity", animat);
        }      

調用 Compositor.CreatePropertySet 方法建立 CompositionPropertySet 的執行個體。為了稍後我們在設定動畫表達式時能夠通路到裡面的值,要先設定一個預設的值。

compPropset.InsertVector2("MyValue", new Vector2(1f, 1f));      

這個值的名字就叫 MyValue,在動畫表達式中,可以直接用 . 運算符來通路,如上面代碼中。

p.MyValue.X / p.MyValue.Y

p 稍後會用實際參數替換。

處理 PointerMoved 事件。

private void bd_PointerMoved(object sender, PointerRoutedEventArgs e)
        {
            // 擷取相對于 Border 的目前坐标
            var pt = e.GetCurrentPoint(bd).Position;
            // 更新 CompositionPropertySet 中的值
            compPropset.InsertVector2("MyValue", new Vector2((float)pt.X, (float)pt.Y));
        }      

首先要擷取到指針相對于 Border 元素的坐标,然後更新 PropertySet 中的 MyValue 的值,更新方法直接用 InsertVector2 方法來替換原來的值就可以了,注意,屬性名稱 MyValue 不要寫錯,一定要前後一緻。

隻要在程式代碼中更新 CompositionPropertySet 對象,動畫就能夠自動更新了。

看看效果。

【Win 10 應用開發】UI Composition 劄記(七):基于表達式的動畫

好了,本篇咱們就聊到這裡了,相信你學會使用表達式後,你就能弄出各種強大的動畫效果。