Windows Community Toolkit 7.0 のアニメーション周りを紹介



## はじめに

この記事では Windows Community Toolkit(WCT)の`Microsoft.Toolkit.Uwp.UI.Animations` から提供されているアニメーション関係の機能を紹介していきます。

Windows Community Toolkit
https://github.com/windows-toolkit/WindowsCommunityToolkit


Windows Community Toolkitのサンプルアプリ（コードとドキュメントも付いてるのでオススメ）

https://www.microsoft.com/ja-jp/p/windows-community-toolkit-sample-app/9nblggh4tlcq?rtc=1&activetab=pivot:overviewtab


### 利用方法

nugetで`toolkit animation`で検索すると出てきます。

https://www.nuget.org/packages/Microsoft.Toolkit.Uwp.UI.Animations/

### 基礎知識

アニメーション等の主たる実行レイヤーにあたる`Visual Layer`について知りたい場合はこちら

https://docs.microsoft.com/en-us/windows/uwp/composition/visual-layer






## WCT 7.0におけるアニメーション機能の特徴

WCTは以前のバージョンからWindows.UI.Compositionを取り込んだアニメーション作成・再生機能が実装されていましたが、WCT 7.0ではさらにC#とXamlからの使い勝手がパワーアップしました。具体的には…

* Xaml上でUIElementに対するアタッチプロパティとしてアニメーションを定義できるように
* C#コード上からAnimationBuilderによるメソッドチェーンの形でアニメーション作成が容易に
* アニメーション定義と実行の分離により、定義済みアニメーションを異なるUIElementで実行できるように

一方で以前あったUIElementに対する`.Fade()`などのアニメーション用拡張メソッドは廃止されており、やや高コストなアニメーション実行に関して明示的な定義を求めるようになったのかなと思います。

また「Blur（ぼかし）」など画像処理に当たるものは別パッケージ（Microsoft.Toolkit.Uwp.UI.Media）にまとめられました。こちらも軽くですが記事後半で紹介します。

## アニメーションの定義と実行方法

アニメーションの実行方法は３種類あり、

* (1) XamlでExplicit（明示的な）アニメーションを定義して、コードビハインドや`StartAnimationActivity` などを通じて手動でアニメーションを実行する。
* (2) XamlでImplicit（暗黙的な）アニメーションを定義して、プロパティ変更に応じて自動的にアニメーションを実行させる
* (3) コードビハインドで`AnimationBuilder`を用いて、手動でアニメーションを実行する。

さらに (4) アニメーションの値の指定はExpression（式）文字列が指定可能であり、変数を取り込んだアニメーションを組み立てることも出来ます。

### 1. 明示的なアニメーション

アニメーションの開始を「コードビハインドから」または「ビヘイビア」や「他のアニメーションからの開始をトリガー」するなど明示的に実行するものです。

例えば「Buttonをクリックしたらアニメーションを開始する」ような場合、

```xml
<Button Content="MoveAnimationTest">
  <ani:Explicit.Animations>
    <ani:AnimationSet x:Name="MoveAnimation">
      <ani:TranslationAnimation Duration="0:0:3" To="0,32,0" From="0,0,0" />
    </ani:AnimationSet>
  </ani:Explicit.Animations>

  <interactivity:Interaction.Behaviors>
    <interactions:EventTriggerBehavior EventName="Click">
      <behaviors:StartAnimationAction Animation="{Binding ElementName=MoveAnimation}"/>
    </interactions:EventTriggerBehavior>
  </interactivity:Interaction.Behaviors>
</Button>
```

のように記述します。


### 2. 暗黙的なアニメーション

Xaml上でUIElementに`Implicit:Animations` として定義したアニメーションは紐づくプロパティが変更されるたびに自動で実行されます。

```xml
<animations:Implicit.Animations>
  <animations:OffsetAnimation Duration="0:0:1"/>
  <animations:RotationInDegreesAnimation ImplicitTarget="Offset" Duration="0:0:1.2" From="0" To="0">
    <animations:ScalarKeyFrame Key="0.9" Value="80"/>
  </animations:RotationInDegreesAnimation>
  <animations:ScaleAnimation Duration="0:0:1"/>
</animations:Implicit.Animations>
```

上記の中で例えば`<animations:OffsetAnimation Duration="0:0:1"/>` は「Offsetプロパティが変更された場合は 1秒掛けてアニメーションしてね」という意味になります。



また、`Implicit.ShowAnimations` や `Implicit.HideAnimations` にアニメーションを設定した場合は、親UIのVisibilityの変更に応じてアニメーションを実行してくれます。

```xml
<animations:Implicit.ShowAnimations>
  <animations:OpacityAnimation Duration="0:0:1" From="0" To="1.0"/>
</animations:Implicit.ShowAnimations>

<animations:Implicit.HideAnimations>
  <animations:OpacityAnimation Duration="0:0:1" To="0.0"/>
</animations:Implicit.HideAnimations>
```



### 3. AnimationBuilderでコードからアニメーション実行

```xml
// XAML UIElement
<Image x:Name="ToolkitLogo" Source="ms-appx:///Assets/Square150x150Logo.png" Height="100" Width="100" >
```

```csharp
// C# - Fade can be applied to any UIElement. In this case it is an image called ToolkitLogo.
using Microsoft.Toolkit.Uwp.UI.Animations;

// Create and start the animation
await AnimationBuilder.Create().Opacity(to: 0.5, duration: TimeSpan.FromSeconds(3)).StartAsync(ToolkitLogo);
```




### 4. Expressionによる変数を含むValueの指定

Windows Community Toolkitの機能というよりも、コアになっているWindows.UI.Compositionに実装されている機能です。

動的な変数や関数を式に織り込んでアニメーション用の値として指定できる高機能で非常に強力なアニメーション作成機能です。ただし主に式を文字列値として指定するため「IDE補完が効かない」「エラーが実行時までわからない」ことが難点としてあります。

そのためにExpressionBuilderを利用すればIDE補完、型安全、正しい変数名の指定が出来るようになります。ややコードが複雑になりますが確実に指定できます。

https://docs.microsoft.com/en-us/windows/communitytoolkit/animations/expressions

Expressionで利用可能な関数やビルドインの変数は以下のリンクから確認出来るかと思います。

https://docs.microsoft.com/en-us/uwp/api/Windows.UI.Composition.ExpressionAnimation?view=winrt-19041#expression-keywords




## KeyFrame とNormalizedKeyFrame

KeyFrameは一つのプロパティに対して連続的なアニメーションを指定できます。KeyにはDurationを上限とした時間を指定し、ValueにKeyの時間までに変位すべき値を記述します。

https://docs.microsoft.com/en-us/windows/communitytoolkit/animations/compositionanimations#examples


```xml
<animations:OpacityAnimation Duration="0:0:1" From="1" To="0">
    <animations:ScalarKeyFrame Key="0.2" Value="0.5"/>
    <animations:ScalarKeyFrame Key="0.4" Value="0.8"/>
    <animations:ScalarKeyFrame Key="0.6" Value="0.3"/>
    <animations:ScalarKeyFrame Key="0.8" Value="0.6"/>
</animations:OpacityAnimation>
```

NormalizedKeyFrameはKeyFrameの時間指定がDurationに対する絶対値ではなく`0.0~1.0`までの割合で指定できるバージョンになっています。


## ボカシなど高コストな画像処理関係は Microsoft.Toolkit.Uwp.UI.Media を利用

nuget
https://www.nuget.org/packages/Microsoft.Toolkit.Uwp.UI.Media

PipelineVisualFactory
https://github.com/MicrosoftDocs/WindowsCommunityToolkitDocs/blob/master/docs/brushes/PipelineVisualFactory.md

Xaml上からBorderなどのUIElementの直下に`<media:UIElementExtensions.VisualFactory>`と画像エフェクト処理を記述できるようになるようです。

（実際に試したら別記事で書くかもしれません。）

## おまけ

### おまけ１：OffsetとTranslation、どっちを使うべきか

Translationを利用したほうがベター？

[https://stackoverflow.com/a/55000712/7911524](https://stackoverflow.com/a/55000712/7911524)

> Essentially Offset is the absolute X.Y. position of an element relative too it's parent set by the XAML rendering engine, and Translation is akin to a RenderTransform on top of that - the different is XAML will overwrite Composition's Offset with it's values with no care as to what you put in at the Composition level, but it has no "knowledge" of the translation property so it never overwrites it. Translation always applies as an additive to the current Offset. It also only works for Visuals created from FrameworkElements - it's unneeded for any other type of CompositionVisual as their offsets don't get changed by XAML



### おまけ２：VisualStateManagerのStoryboardとの関係性

アニメーションの定義時に`FrameworkLayer.Xaml`を指定した場合にはStoryboardを利用したアニメーションが実行されるようです。ただしCompositionしか対応してないアニメーションプロパティもあるため、全てがXaml上でアニメーション可能というわけでもないです。逆に全てのアニメーションはCompositionレイヤーで実行可能です。

アニメーションの組み立てが`Opactiy()`等コードでされて、この時点でXamlかCompositionに分けてコレクションに詰められ
[https://github.com/windows-toolkit/WindowsCommunityToolkit/blob/58f8166bb7000f2e16002783a79b377831ed55f1/Microsoft.Toolkit.Uwp.UI.Animations/Builders/AnimationBuilder.Default.cs#L82](https://github.com/windows-toolkit/WindowsCommunityToolkit/blob/58f8166bb7000f2e16002783a79b377831ed55f1/Microsoft.Toolkit.Uwp.UI.Animations/Builders/AnimationBuilder.Default.cs#L82)

`Start()``StartAsync()`で各レイヤーごとのアニメーションが実行される。
[https://github.com/windows-toolkit/WindowsCommunityToolkit/blob/58f8166bb7000f2e16002783a79b377831ed55f1/Microsoft.Toolkit.Uwp.UI.Animations/Builders/AnimationBuilder.cs#L65](https://github.com/windows-toolkit/WindowsCommunityToolkit/blob/58f8166bb7000f2e16002783a79b377831ed55f1/Microsoft.Toolkit.Uwp.UI.Animations/Builders/AnimationBuilder.cs#L65)