アニメーションの場合は、
- Storyboard の場合は、Blend でちまちま編集して動作確認
- プログラムコードに組み込んで、動作確認
- もう一度、Blend に戻って修正。
- またまた、プログラムコードに組み込んで、動作確認
っていう繰り返しになるので、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 を量産しないでよくなるので(特に開始終了点の名前付けとかが面倒なので)、花札ゲームのアニメーションに活用できそう。なので、再び花札ゲーム製作に戻るということで。
