[WinRT] Storyboard.Clone を作る

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

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

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
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 にくっつけてしまいます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/// <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 で作っておきます。

1
2
3
4
5
6
7
8
9
10
11
12
<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 のほうにあわせておきます。

1
2
3
4
5
6
7
<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 時にキープしておきます。

1
2
3
4
5
6
7
8
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 パーマリンク