アニメーションWPFに「ラムダ」を使用します

たとえば、グラフィックプリミティブを作成してアニメーション化するには、一定の速度でポイントAからポイントBに移動するのは簡単です。 しかし、複数のオブジェクトを特定のシーケンスに配置し、それらを非線形にアニメーション化する必要がある場合はどうでしょうか? このため、WPFもSilverlightも組み込み関数を備えていません。 このエッセイでは、ラムダデリゲートと高階関数を使用してオブジェクトとアニメーションを動的に作成する方法を示します。





世代



次のようなものを作成(およびアニメーション化)する必要があるとします。







原則として、あなたはサイクルでうまくいくことができますが、すべてをよりわかりやすく、より理解しやすい「1行」にすることができたら、活用してみませんか? 簡単なものから始めましょう-円のセットは明らかにコレクションなので、すべてのオブジェクトへのリンクを保存するクラスを作成しましょう:



パブリッククラスLambdaCollection <T>:コレクション<T>ここで、T:DependencyObject、new()
 {
   public LambdaCollection(int count){while(count-> 0)Add(new T());  }
   ⋮
 }


これまでのところ、すべてが静かです-コンテンツの種類によって制限されるコレクションを定義し( DependencyObject



から継承し、デフォルトのコンストラクターを持つ必要があります)、必要なオブジェクトを一定量作成するコンストラクターを追加しました。 そして今、最も興味深い部分は、ラムダデリゲートを使用してT



オブジェクトのプロパティを初期化できるメソッドを追加していることです。



パブリッククラスLambdaCollection <T>:コレクション<T>ここで、T:DependencyObject、new()
 {
   ⋮
   public LambdaCollection <T> WithProperty <U>(DependencyPropertyプロパティ、Func <int、U>ジェネレーター)
   {
     for(int i = 0; i <Count; ++ i)
       this [i] .SetValue(プロパティ、ジェネレーター(i));
    これを返す;
   }
 }


ここで停止して、何が起こるかを確認する必要があります。 第一に、最後にreturn this



return this



と書かれているため、流itなインターフェイスです。 彼自身が2つのパラメーターを取ります。 最初のパラメーターは、コレクションのすべての要素で変更するプロパティです。 どこにでもサイクルを書く必要がないので、これは人生を大幅に簡素化します。 2番目のパラメーターは、値ジェネレーターへの参照です。つまり、コレクション内の要素のインデックスを取得し、 U



型の値を返す関数への参照ですU



さらに、ティム自体は何でもかまいません。主なことは、それがプロパティに合っているということです。



重要:ここでは自動型変換は行われないため、プロパティの型がdouble



場合、 int



型の値を生成できません-例外を取得します。



使い方は? はい、非常に簡単です。 たとえば、サイズが大きくなる10個の円を作成するには、次のように記述します。



 var circles =新しいLambdaCollection <Ellipse>(10)
   .WithProperty(WidthProperty、i => 1.5 *(i + 1))
   .WithProperty(HeightProperty、i => 1.5 *(i + 1));


このような式により、直径を要素の位置に依存させることができます。 この場合、最小の要素では1.5ピクセル、最大の要素では15ピクセルになります。 また、コードからわかるように、幅と高さを個別に変えることができます。



多くの場合、X座標とY座標を変更する必要があるため、タスクを簡素化する便利なメソッドを作成できます。



パブリッククラスLambdaCollection <T>:コレクション<T>ここで、T:DependencyObject、new()
 {
   ⋮
   public LambdaCollection <T> WithXY <U>(Func <int、U> xGenerator、Func <int、U> yGenerator)
   {
     for(int i = 0; i <Count; ++ i)
     {
       this [i] .SetValue(Canvas.LeftProperty、xGenerator(i));
       this [i] .SetValue(Canvas.TopProperty、yGenerator(i));
     }
    これを返す;
   }
 }


それでは、すべてをまとめて、最初に示した画像を作成しましょう。



 int count = 20;
 var circles =新しいLambdaCollection <Ellipse>(count)
   .WithXY(i => 100.0 +(4.0 * i * Math.Sin(i / 4.0 *(Math.PI)))、
           i => 100.0 +(4.0 * i * Math.Cos(i / 4.0 *(Math.PI))))
   .WithProperty(WidthProperty、i => 1.5 * i)
   .WithProperty(HeightProperty、i => 1.5 * i)
   .WithProperty(Shape.FillProperty、i =>新しいSolidColorBrush(
     Color.FromArgb(255、0、0、(バイト)(255-(バイト)(12.5 * i)))));
 foreach(サークル内の変数円)
   MyCanvas.Children.Add(円);


それだけです-メソッドのペアを使用すると、非常に簡単に要素の異なる「コンステレーション」を作成できます。 それでは、アニメーションを見てみましょう。



アニメーション



DoubleAnimation



ような線形アニメーションは退屈です。 私たち自身が要素の値を制御するとき、それははるかに興味深いです。 このタイプだけを例にとると、アニメーション化された値がジェネレーターによって制御されるように、非常に簡単に再定義できます。



パブリッククラスLambdaDoubleAnimation:DoubleAnimation
 {
   public Func <double、double> ValueGenerator {get; セット;  }
  保護されたオーバーライドdouble GetCurrentValueCore(double origin、double dst、AnimationClock clock)
   {
     return ValueGenerator(base.GetCurrentValueCore(origin、dst、clock));
   }
 }


これで、線形補間を行うクラスができました。次に、変換された値を取得して、それを使って何かをすることができます。



コレクションを操作するため、このようなアニメーションオブジェクトのセットを作成する機会が再び必要です。 以下にそのようなクラスを示します。



パブリッククラスLambdaDoubleAnimationCollection:コレクション<LambdaDoubleAnimation>
 {
   ⋮
   public LambdaDoubleAnimationCollection(int count、Func <int、double> from、Func <int、double> to、
     Func <int、Duration> duration、Func <int、Func <double、double >> valueGenerator)
   {
     for(int i = 0; i <count; ++ i)
     {
       var lda =新しいLambdaDoubleAnimation
       {
         From = from(i)、 
         To = to(i)、 
        期間=期間(i)、
         ValueGenerator = valueGenerator(i)
       };
      追加(lda);
     }
   }
   public void BeginApplyAnimation(UIElement []ターゲット、DependencyPropertyプロパティ)
   {
     for(int i = 0; i <Count; ++ i)
      ターゲット[i] .BeginAnimation(プロパティ、アイテム[i]);
   }
 }


デザイナーは複数いますが、そのうちの1人だけが上に示されています。 パラメータは値ジェネレータです。つまり、すべてのアニメーションパラメータは、コレクション内の要素の位置から派生​​させることもできます。 valueGenerator



パラメーターは、2次関数、または「関数ジェネレーター関数」、つまり、コレクション内のインデックスに依存し、アニメーション中に補間されたdouble



値に依存する値を持つジェネレーターを想定しています。 C#では、これは、ここで "double lambda"を渡す必要があることを意味します。たとえば、 i => j => f(j)







次に簡単な例を示します。スパイラルを回転させて正弦波にします。



 var c = new LambdaDoubleAnimationCollection(
   circles.Count、 
   i => 10.0 * i、 
   i =>新しいDuration(TimeSpan.FromSeconds(2))、
   i => j => 100.0 / j);
 c.BeginApplyAnimation(circles.Cast <UIElement>().ToArray()、Canvas.LeftProperty);


ここでの唯一の迷惑は、共分散の欠如です。 このため、パラメーターとして円を渡すことはできませんUIElement[]



に変換する必要があります。 そして、私はすぐに長さを知りたいので、正確に配列が選択されましたIEnumerable



を使用することも可能です。



アニメーション自体を表示することはできませんが、その最終段階は「側面から」のらせんビューです。







拡張機能



小さなフレームワークの拡張は簡単です。 要素は並行してアニメーション化されますが、すべてが一貫している必要がありますか? 問題ありませんLambdaDoubleAnimationCollection



少し変更LambdaDoubleAnimationCollection



だけLambdaDoubleAnimationCollection







パブリッククラスLambdaDoubleAnimationCollection:コレクション<LambdaDoubleAnimation>
 {
   ⋮
   public void BeginApplyAnimation(UIElement []ターゲット、DependencyPropertyプロパティ)
   {
     for(int i = 0; i <Count; ++ i)
     {
      アイテム[i] .BeginTime = new TimeSpan(0);
      ターゲット[i] .BeginAnimation(プロパティ、アイテム[i]);
     }
   }
   public void BeginSequentialAnimation(UIElement []ターゲット、DependencyPropertyプロパティ)
   {
     TimeSpan acc =新しいTimeSpan(0);
     for(int i = 0; i <Items.Count; ++ i)
     {
      アイテム[i] .BeginTime = acc;
       acc + =アイテム[i] .Duration.TimeSpan;
     }
     for(int i = 0; i <Count; ++ i)
     {
      ターゲット[i] .BeginAnimation(プロパティ、アイテム[i]);
     }
   }
 }


他の要素と一緒です。 頑張って



St. Petersburg ALT.NET Group




All Articles