アニメーションの場合は、
- 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 を量産しないでよくなるので(特に開始終了点の名前付けとかが面倒なので)、花札ゲームのアニメーションに活用できそう。なので、再び花札ゲーム製作に戻るということで。