[WinRT] Storyboard.Clone を作る

アニメーションの場合は、

  1. Storyboard の場合は、Blend でちまちま編集して動作確認
  2. プログラムコードに組み込んで、動作確認
  3. もう一度、Blend に戻って修正。
  4. またまた、プログラムコードに組み込んで、動作確認

っていう繰り返しになるので、Blend でデザイン、Visual Studio でビルドしてからシミュレーターで確認、ってのが定番…になると思うのですが、どうなんでしょう?そんなに複雑な Storyboard は作らないのかな?

■Storyboard.Clone を拡張メソッドで作る

リフレクションとか使って正確に書こうとも思ったのですが、さほど複雑な構造でもないし、入れ子になるクラスは決まっているのでだらだらと150行ほど書きます。

public static class StoryboardExtensions
{
	/// <summary>
	/// Storyboard をコピーする 
	/// </summary>
	/// <param name="src"></param>
	/// <returns></returns>
	public static Storyboard Clone(this Storyboard src)
	{
		var sb = new Storyboard();
		sb.AutoReverse = src.AutoReverse;
		sb.BeginTime = src.BeginTime;
		sb.Duration = src.Duration;
		sb.FillBehavior = src.FillBehavior;
		sb.RepeatBehavior = src.RepeatBehavior;
		sb.SpeedRatio = src.SpeedRatio;
		foreach (var tl in src.Children)
		{
			var tld = tl.Clone();
			sb.Children.Add(tld);
		}
		return sb;
	}

	public static Timeline Clone(this Timeline src)
	{
		Timeline dest = null;
		if (src is ColorAnimationUsingKeyFrames)
			dest = ((ColorAnimationUsingKeyFrames)src).Clone();
		if (src is DoubleAnimationUsingKeyFrames)
			dest = ((DoubleAnimationUsingKeyFrames)src).Clone();
		if (src is ObjectAnimationUsingKeyFrames)
			dest = ((ObjectAnimationUsingKeyFrames)src).Clone();
		if (src is PointAnimationUsingKeyFrames)
			dest = ((PointAnimationUsingKeyFrames)src).Clone();
		if (dest != null)
		{
			Storyboard.SetTargetProperty(dest, Storyboard.GetTargetProperty(src));
			Storyboard.SetTargetName(dest, Storyboard.GetTargetName(src));
		}
		return dest;
	}
	public static ColorAnimationUsingKeyFrames Clone(this ColorAnimationUsingKeyFrames src)
	{
		var dest = new ColorAnimationUsingKeyFrames();
		foreach (var kf in src.KeyFrames)
		{
			dest.KeyFrames.Add(kf.Clone());
		}
		return dest;
	}

	public static ColorKeyFrame Clone(this ColorKeyFrame src)
	{
		ColorKeyFrame dest = null;
		if (src is LinearColorKeyFrame)
			dest = new LinearColorKeyFrame();
		if (src is DiscreteColorKeyFrame)
			dest = new DiscreteColorKeyFrame();
		if (src is EasingColorKeyFrame)
			dest = new EasingColorKeyFrame();
		if (src is SplineColorKeyFrame)
			dest = new SplineColorKeyFrame();
		if (dest != null)
		{
			dest.KeyTime = src.KeyTime;
			dest.Value = src.Value;
		}
		return dest;
	}

	public static DoubleAnimationUsingKeyFrames Clone(this DoubleAnimationUsingKeyFrames src)
	{
		var dest = new DoubleAnimationUsingKeyFrames();
		foreach (var kf in src.KeyFrames)
		{
			dest.KeyFrames.Add(kf.Clone());
		}
		return dest;
	}
	public static DoubleKeyFrame Clone(this DoubleKeyFrame src)
	{
		DoubleKeyFrame dest = null;
		if (src is LinearDoubleKeyFrame)
			dest = new LinearDoubleKeyFrame();
		if (src is DiscreteDoubleKeyFrame)
			dest = new DiscreteDoubleKeyFrame();
		if (src is EasingDoubleKeyFrame)
			dest = new EasingDoubleKeyFrame();
		if (src is SplineDoubleKeyFrame)
			dest = new SplineDoubleKeyFrame();
		if (dest == null)
			return null;

		dest.KeyTime = src.KeyTime;
		dest.Value = src.Value;
		return dest;
	}
	public static ObjectAnimationUsingKeyFrames Clone(this ObjectAnimationUsingKeyFrames src)
	{
		var dest = new ObjectAnimationUsingKeyFrames();
		foreach (var kf in src.KeyFrames)
		{
			dest.KeyFrames.Add(kf.Clone());
		}
		return dest;
	}
	public static ObjectKeyFrame Clone(this ObjectKeyFrame src)
	{
		ObjectKeyFrame dest = null;
		if (src is DiscreteObjectKeyFrame)
			dest = new DiscreteObjectKeyFrame();
		if (dest == null)
			return null;

		dest.KeyTime = src.KeyTime;
		dest.Value = src.Value;
		return dest;
	}
	public static PointAnimationUsingKeyFrames Clone(this PointAnimationUsingKeyFrames src)
	{
		var dest = new PointAnimationUsingKeyFrames();
		foreach (var kf in src.KeyFrames)
		{
			dest.KeyFrames.Add(kf.Clone());
		}
		return dest;
	}
	public static PointKeyFrame Clone(this PointKeyFrame src)
	{
		PointKeyFrame dest = null;
		if (src is LinearPointKeyFrame)
			dest = new LinearPointKeyFrame();
		if (src is EasingPointKeyFrame)
			dest = new EasingPointKeyFrame();
		if (src is DiscretePointKeyFrame)
			dest = new DiscretePointKeyFrame();
		if (src is SplinePointKeyFrame)
			dest = new SplinePointKeyFrame();
		if (dest == null)
			return null;

		dest.KeyTime = src.KeyTime;
		dest.Value = src.Value;
		return dest;
	}
}

■アニメーションの開始点と終了点を変更する拡張メソッドを作る

アニメーションをする対象と、開始終了点を指定するのですが、色々ややこしいので拡張メソッドを用意しておきます。本当は別クラスのほうがいいんでしょうが、面倒なので Storyboard にくっつけてしまいます。

/// <summary>
/// 各種の値を変えるための拡張メソッド
/// </summary>
public static class StorybaordValueExtentions
{
	/// <summary>
	/// ターゲット名を指定する
	/// </summary>
	/// <param name="sb"></param>
	/// <param name="name"></param>
	public static void SetTarget(this Storyboard sb, UIElement el)
	{
		foreach (var tl in sb.Children)
		{
			Storyboard.SetTarget(sb, el);
		}
	}
	public static void SetTargetName(this Storyboard sb, string name)
	{
		foreach (var tl in sb.Children)
		{
			Storyboard.SetTargetName(sb, name);
		}
	}
	public static void SetMovePoint(this Storyboard sb, Point start, Point end)
	{
		((DoubleAnimationUsingKeyFrames)sb.Children[0]).KeyFrames[0].Value = start.X;
		((DoubleAnimationUsingKeyFrames)sb.Children[1]).KeyFrames[0].Value = start.Y;
		((DoubleAnimationUsingKeyFrames)sb.Children[0]).KeyFrames[1].Value = end.X;
		((DoubleAnimationUsingKeyFrames)sb.Children[1]).KeyFrames[1].Value = end.Y;
	}
}

storyboard は複数のターゲットを同時に動かすこともできるので、一律で変えてしまうのは困るのですが、まあ、そのときはそのときで考えるということで。

■実際に動かしてみる

あらかじめ、コピー元の storyboard を blend で作っておきます。

<Page.Resources>
    <Storyboard x:Name="sbMove">
        <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.TranslateX)" Storyboard.TargetName="pictAni">
            <EasingDoubleKeyFrame x:Name="sbStartX" KeyTime="0" Value="180.597"/>
            <EasingDoubleKeyFrame x:Name="sbEndX" KeyTime="0:0:1" Value="182.09"/>
        </DoubleAnimationUsingKeyFrames>
        <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.TranslateY)" Storyboard.TargetName="pictAni">
            <EasingDoubleKeyFrame x:Name="sbStartY" KeyTime="0" Value="-152.239"/>
            <EasingDoubleKeyFrame x:Name="sbEndY" KeyTime="0:0:1" Value="171.642"/>
        </DoubleAnimationUsingKeyFrames>
    </Storyboard>
</Page.Resources>

アニメーションする対象は、あらかじめ、Image.RenderTransform と CompositeTransform を書いておきます。
これは Blend が出力する Storyboard の癖なので、手作業で作れば Margin とか Canvas.Left あたりを直接変更することも可能でしょう。今は、Blend と Visual Studio の行き来を簡単にするということで、Blend のほうにあわせておきます。

<Image x:Name='pict1' HorizontalAlignment="Left" Height="100" 
            VerticalAlignment="Top" Width="64" Source="/Images/FC097-2.png"  Margin="288,151,0,0">
    <Image.RenderTransform>
        <CompositeTransform/>
    </Image.RenderTransform>

</Image>

自作した Clone と拡張メソッドを使って、pict1 を移動させます。ここでは、Clone した storyboard は使い捨てになっていますが、実際は Initialize 時にキープしておきます。

private void SbClone2Click(object sender, RoutedEventArgs e)
{
	Storyboard sb = this.sbMove.Clone();
	// ターゲットを変える
	sb.SetTarget( this.pict1 );
	sb.SetMovePoint(new Point(0, 0), new Point(100, 200));
	sb.Begin();
}

これで、storyboard の XAML を量産しないでよくなるので(特に開始終了点の名前付けとかが面倒なので)、花札ゲームのアニメーションに活用できそう。なので、再び花札ゲーム製作に戻るということで。

カテゴリー: C#, WinRT パーマリンク