WPF シンプルなLoadingアニメーションを作る
ソースコード
早速ですが、ソースコードは以下の通りです。
<Window x:Class="LoadingAnimation1.MainWindow" 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:local="clr-namespace:LoadingAnimation1" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:sys="clr-namespace:System;assembly=mscorlib" Title="MainWindow" Width="800" Height="450" mc:Ignorable="d"> <Window.Resources> <!-- アニメーションサイズ --> <sys:Double x:Key="LoadEllipseMinSize">15.0</sys:Double> <sys:Double x:Key="LoadEllipseMaxSize">45.0</sys:Double> <!-- アニメーション時間 --> <KeyTime x:Key="LoadKeyTime1">00:00:00</KeyTime> <KeyTime x:Key="LoadKeyTime2">00:00:0.20</KeyTime> <KeyTime x:Key="LoadKeyTime3">00:00:0.40</KeyTime> <KeyTime x:Key="LoadKeyTime4">00:00:0.60</KeyTime> <KeyTime x:Key="LoadKeyTime5">00:00:0.80</KeyTime> <KeyTime x:Key="LoadKeyTimeEnd">00:00:02</KeyTime> <!-- アニメーション作成 --> <Storyboard x:Key="LoadingAnimation"> <!-- 左から1番目のEllipseアニメーション --> <DoubleAnimationUsingKeyFrames RepeatBehavior="Forever" Storyboard.TargetName="LoadEllipse1" Storyboard.TargetProperty="Width"> <SplineDoubleKeyFrame KeyTime="{StaticResource LoadKeyTime1}" Value="{StaticResource LoadEllipseMinSize}" /> <SplineDoubleKeyFrame KeyTime="{StaticResource LoadKeyTime2}" Value="{StaticResource LoadEllipseMaxSize}" /> <SplineDoubleKeyFrame KeyTime="{StaticResource LoadKeyTime3}" Value="{StaticResource LoadEllipseMinSize}" /> <SplineDoubleKeyFrame KeyTime="{StaticResource LoadKeyTimeEnd}" Value="{StaticResource LoadEllipseMinSize}" /> </DoubleAnimationUsingKeyFrames> <!-- 左から2番目のEllipseアニメーション --> <DoubleAnimationUsingKeyFrames RepeatBehavior="Forever" Storyboard.TargetName="LoadEllipse2" Storyboard.TargetProperty="Width"> <SplineDoubleKeyFrame KeyTime="{StaticResource LoadKeyTime2}" Value="{StaticResource LoadEllipseMinSize}" /> <SplineDoubleKeyFrame KeyTime="{StaticResource LoadKeyTime3}" Value="{StaticResource LoadEllipseMaxSize}" /> <SplineDoubleKeyFrame KeyTime="{StaticResource LoadKeyTime4}" Value="{StaticResource LoadEllipseMinSize}" /> <SplineDoubleKeyFrame KeyTime="{StaticResource LoadKeyTimeEnd}" Value="{StaticResource LoadEllipseMinSize}" /> </DoubleAnimationUsingKeyFrames> <!-- 左から3番目のEllipseアニメーション --> <DoubleAnimationUsingKeyFrames RepeatBehavior="Forever" Storyboard.TargetName="LoadEllipse3" Storyboard.TargetProperty="Width"> <SplineDoubleKeyFrame KeyTime="{StaticResource LoadKeyTime3}" Value="{StaticResource LoadEllipseMinSize}" /> <SplineDoubleKeyFrame KeyTime="{StaticResource LoadKeyTime4}" Value="{StaticResource LoadEllipseMaxSize}" /> <SplineDoubleKeyFrame KeyTime="{StaticResource LoadKeyTime5}" Value="{StaticResource LoadEllipseMinSize}" /> <SplineDoubleKeyFrame KeyTime="{StaticResource LoadKeyTimeEnd}" Value="{StaticResource LoadEllipseMinSize}" /> </DoubleAnimationUsingKeyFrames> </Storyboard> </Window.Resources> <Canvas> <!-- アニメーション対象 --> <Grid Canvas.Left="50" Canvas.Top="30" Height="50"> <Grid.ColumnDefinitions> <ColumnDefinition Width="50" /> <ColumnDefinition Width="50" /> <ColumnDefinition Width="50" /> </Grid.ColumnDefinitions> <Ellipse x:Name="LoadEllipse1" Grid.Column="0" Width="{StaticResource LoadEllipseMinSize}" Height="{Binding ElementName=LoadEllipse1, Path=Width}" Fill="Gray" /> <Ellipse x:Name="LoadEllipse2" Grid.Column="1" Width="{StaticResource LoadEllipseMinSize}" Height="{Binding ElementName=LoadEllipse2, Path=Width}" Fill="Gray" /> <Ellipse x:Name="LoadEllipse3" Grid.Column="2" Width="{StaticResource LoadEllipseMinSize}" Height="{Binding ElementName=LoadEllipse3, Path=Width}" Fill="Gray" /> </Grid> <!-- アニメーションの有効、無効の切り替え --> <Button Canvas.Left="50" Canvas.Top="120" Width="50" Click="Start_Click" Content="Start" /> <Button Canvas.Left="150" Canvas.Top="120" Width="50" Click="Stop_Click" Content="Stop" /> </Canvas>
using System.Windows; using System.Windows.Media.Animation; namespace LoadingAnimation1 { /// <summary> /// MainWindow.xaml の相互作用ロジック /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } private void Start_Click(object sender, RoutedEventArgs e) { var sb = this.Resources["LoadingAnimation"] as Storyboard; sb.Begin(); } private void Stop_Click(object sender, RoutedEventArgs e) { var sb = this.Resources["LoadingAnimation"] as Storyboard; sb?.Stop(); } } }
このソースコードの実行結果は以下の通りです。
解説
それぞれの処理内容について解説します。
サイズや実行時間の定義
ここではまずアニメーションで変化させるプロパティの値を定義しています。
必須ではありませんが、このように定義しておくと後から変更が楽になります。LoadEllipseMinSize、LoadEllipseMaxSizeを変更することで円を任意のサイズに変更できます。
なお、StringやDoubleなどのシステムの値をリソース値として定義する場合は、xmlns:sys="clr-namespace:System;assembly=mscorlib"
を追加する必要があります。
<Window xmlns:sys="clr-namespace:System;assembly=mscorlib" ・・・省略・・・ <Window.Resources> <!-- アニメーションサイズ --> <sys:Double x:Key="LoadEllipseMinSize">15.0</sys:Double> <sys:Double x:Key="LoadEllipseMaxSize">45.0</sys:Double> <!-- アニメーション時間 --> <KeyTime x:Key="LoadKeyTime1">00:00:00</KeyTime> <KeyTime x:Key="LoadKeyTime2">00:00:0.20</KeyTime> <KeyTime x:Key="LoadKeyTime3">00:00:0.40</KeyTime> <KeyTime x:Key="LoadKeyTime4">00:00:0.60</KeyTime> <KeyTime x:Key="LoadKeyTime5">00:00:0.80</KeyTime> <KeyTime x:Key="LoadKeyTimeEnd">00:00:02</KeyTime>
アニメーションの作成
以下の部分で実際のアニメーションの動作を作成しています。WPFでアニメーションを作成する場合は、Storyboard
を利用します。そしてStoryboard
内に具体的な動作を定義します。
アニメーションの作成方法はいくつかありますが、今回のように値を柔軟に遷移させたい場合はKeyFrameを使うのが良いです。
KeyFrameにもいくつかの種類が存在します。それらの違いは、変更するプロパティの型の違いです。ここではEllipseのWightとHeightを変更したいのでDoubleAnimationUsingKeyFrames
を利用します。
他のKeyFrameやアニメーションの実現方法を知りたい場合は、以下に詳しい説明が記載されています。
アニメーションの概要 - WPF .NET Framework | Microsoft Docs
<!-- アニメーション作成 --> <Storyboard x:Key="LoadingAnimation"> <!-- 左から1番目のEllipseアニメーション --> <DoubleAnimationUsingKeyFrames RepeatBehavior="Forever" Storyboard.TargetName="LoadEllipse1" Storyboard.TargetProperty="Width"> <SplineDoubleKeyFrame KeyTime="{StaticResource LoadKeyTime1}" Value="{StaticResource LoadEllipseMinSize}" /> <SplineDoubleKeyFrame KeyTime="{StaticResource LoadKeyTime2}" Value="{StaticResource LoadEllipseMaxSize}" /> <SplineDoubleKeyFrame KeyTime="{StaticResource LoadKeyTime3}" Value="{StaticResource LoadEllipseMinSize}" /> <SplineDoubleKeyFrame KeyTime="{StaticResource LoadKeyTimeEnd}" Value="{StaticResource LoadEllipseMinSize}" /> </DoubleAnimationUsingKeyFrames> <!-- 左から2番目のEllipseアニメーション --> ・・・省略・・・ <!-- 左から3番目のEllipseアニメーション --> ・・・省略・・・ </Storyboard> </Window.Resources>
DoubleAnimationUsingKeyFrames
で設定しているパラメータの説明は以下の通りです。(これ以外にもパラメータは存在します)
パラメータ | 説明 |
---|---|
RepeatBehavior | アニメーションを繰り返す回数。Foreverにすると永遠に繰り返す。 |
Storyboard.TargetName | 対象の名称。x:Nameで定義しておく。 |
Storyboard.TargetProperty | 対象のプロパティ。プロパティの階層が深い場合は、正しい階層で記載する必要がある。 |
DoubleAnimationUsingKeyFrames
内に定義するKeyFrameについても、いくつか種類が存在します。ここでは、値を滑らかに変更したいので、SplineDoubleKeyFrame
を利用しています。
それぞれで設定している値は、上で定義したリソース値です。
<SplineDoubleKeyFrame KeyTime="{StaticResource LoadKeyTime1}" Value="{StaticResource LoadEllipseMinSize}" /> <SplineDoubleKeyFrame KeyTime="{StaticResource LoadKeyTime2}" Value="{StaticResource LoadEllipseMaxSize}" /> <SplineDoubleKeyFrame KeyTime="{StaticResource LoadKeyTime3}" Value="{StaticResource LoadEllipseMinSize}" /> <SplineDoubleKeyFrame KeyTime="{StaticResource LoadKeyTimeEnd}" Value="{StaticResource LoadEllipseMinSize}" />
これに実際の値に当てはめてみると以下のようになります。
<SplineDoubleKeyFrame KeyTime="00:00:00" Value="15.0" /> <SplineDoubleKeyFrame KeyTime="00:00:0.20" Value="45.0" /> <SplineDoubleKeyFrame KeyTime="00:00:0.40" Value="15.0" /> <SplineDoubleKeyFrame KeyTime="00:00:02" Value="15.0" />
これは、以下のような動作になります。
1. 0秒の時点でのWightを15.0とする。
2. 0秒から0.20秒にかけて、Wightを15.0から45.0に変更する。
3. 0.20秒から0.40秒にかけて、Wightを45.0から15.0に変更する。
4. 0.40秒から2秒にかけて、Wightは15.0から変更しない。
一見、3か4が必要ないように思えるかもしれません。しかし3がないと、15.0から45.0への変化が0.20秒間隔で行われるのに対し、45.0から15.0への変化が0.20秒から2秒の1.80秒間隔で行われてしまうため、拡大と縮小の間隔が異なってしまいます。
また、4は3つのEllipseの終了時間を合わせるために利用しています。
<SplineDoubleKeyFrame KeyTime="00:00:00" Value="15.0" /> <SplineDoubleKeyFrame KeyTime="00:00:0.20" Value="45.0" /> <SplineDoubleKeyFrame KeyTime="00:00:0.40" Value="15.0" /> <SplineDoubleKeyFrame KeyTime="00:00:02" Value="15.0" />
ここでは、左から1番目のEllipseについてのみ説明していますが、他についても、実行時間をずらしているだけで、処理の流れは同じです。
アニメーション対象の作成
アニメーション対象の作成方法は、特に特別な処理は必要ありません。以下のCanvas
や、Grid
についても、アニメーションの動作確認のために定義したまでで、深い理由があるわけではありません。
ただし今回のようなコントロールのサイズを変更する場合は、以下のGrid
のように固定サイズのエリアを作成してからアニメーション対象を定義しないと、アニメーションによるサイズ変更に伴いコントロールの位置まで変化してしまう可能性があります。
<Canvas> <!-- アニメーション対象 --> <Grid Canvas.Left="50" Canvas.Top="30" Height="50"> <Grid.ColumnDefinitions> <ColumnDefinition Width="50" /> <ColumnDefinition Width="50" /> <ColumnDefinition Width="50" /> </Grid.ColumnDefinitions> <Ellipse x:Name="LoadEllipse1" Grid.Column="0" Width="{StaticResource LoadEllipseMinSize}" Height="{Binding ElementName=LoadEllipse1, Path=Width}" Fill="Gray" /> ・・・省略・・・ </Grid>
縦横比の固定
今回のアニメーションではEllipseのサイズを変更していますが、縦横比を固定しつつサイズを変更するにはWight、Heightの両方についてStoryboard
を作成する必要があります。しかしまったく同じDoubleAnimationUsingKeyFrames
をWight、Heightの2つ分作成するのは面倒なので、以下のようにHeightに自分のWightをBindingすることで、縦横比を固定させています。
<Ellipse x:Name="LoadEllipse1" Grid.Column="0" Width="{StaticResource LoadEllipseMinSize}" Height="{Binding ElementName=LoadEllipse1, Path=Width}" Fill="Gray" />
ストーリーボードの実行、停止
ここでは、C#上から、ボタンクリックイベントに同期してストーリーボードの有効、無効を切り替えています。以下のようにリソースからストーリーボードのオブジェクトを取得した後に、storyboard.Begin();
することで開始、storyboard.Stop();
することで停止します。
private void Start_Click(object sender, RoutedEventArgs e) { var storyboard = this.Resources["LoadingAnimation"] as Storyboard; storyboard.Begin(); } private void Stop_Click(object sender, RoutedEventArgs e) { var storyboard = this.Resources["LoadingAnimation"] as Storyboard; storyboard.Stop(); }