アイテムがさささっとアニメーションするListBoxを作る
Windows Phone Advent Calendar 2011絶賛開催中。
これは19日目のエントリーです。
サンプルコードをダウンロード: AnimationListSample.zip
Windows Phoneといえば、Silverlightといえばアニメーションですよね。
で、アプリ作るなら、どうせやるならひらりひらりと華麗なアニメーションをキメたくなります。
今回のAdvent Calendarでもid:tmyt先生が紹介してくれたように、ページ単位のTransitionであればSilverlight Toolkitに出来合いのモノがありまして、それだけでも十分いい感じになるのですが、せっかくだからHomeタイルや標準のメールのように個々のアイテムまでささささっとアニメーションさせてみたい! ...というわけで、今回はアイテム単位でアニメーションするListBox派生クラスのサンプルを用意してみました。
このサンプルはコードが読みやすいようにできるだけシンプルな形に落とし込んであるので、あれやこれやと変更していろんなアニメーションにトライしてみてください。
基本的なコンセプトは単純で、ListBox継承クラスを一つ作成し、OnItemsChanged()をoverrideし、追加されてきたアイテム達に対して時間差をつけたStoryboardを片っ端から適用してやる、ただそれだけです。ね、簡単でしょ?
ただし、一口にListBoxのアイテム追加といってもその方法・状況は様々。アイテムたちは、XAMLで記述される場合もあれば、Add()されたり、Bindingされたり、OnItemsChanged()もまとめて呼ばれて来たりバラバラに叩かれたり...。
なので、Bindingの場合はまずその実体のコントロールを取得し、
一旦バッファとなるList
FrameworkElement elm = item as FrameworkElement;
if (elm == null)
elm = base.ItemContainerGenerator.ContainerFromItem(item) as FrameworkElement;
StoryboardなんてふつうBlendでしょ? C#のコードでStoryboardなんてやったことないし超面倒そう! と思います? その通り! 面倒だけど、そんなに複雑でなければ単にプロパティを付けてくだけですよ。
private void AppendToList()
{
for (int i = 0; i < appendingItems.Count; i++)
{
var item = appendingItems[i];
AnimateItem(item, i);
}
appendingItems.Clear();
}
まぁ、これはサンプルなので、実際にはリソースなどで定義済みのアニメーションを引っ張ってくるという手もあるでしょう。
Storyboard storyboard = new Storyboard();
...
element.RenderTransform = new CompositeTransform();
DoubleAnimation slideAni = new DoubleAnimation();
slideAni.From = -100.0;
slideAni.To = 0;
slideAni.Duration = duration;
slideAni.EasingFunction = new ExponentialEase() { EasingMode = EasingMode.EaseIn, Exponent = 5.0 };
Storyboard.SetTarget(slideAni, element);
Storyboard.SetTargetProperty(slideAni, new PropertyPath("UIElement.RenderTransform.TranslateX"));
storyboard.Children.Add(slideAni);double delay = itemInterval * delayCount;
storyboard.BeginTime = new TimeSpan?(TimeSpan.FromSeconds(delay));
storyboard.Begin();
このサンプルは単純にRenderTransform.TranslateXを使っていますが、このあたりを変えるだけで色々とバリエーションを作るのは簡単です。
というわけで、クラス全体の実装は以下のような感じになっています。
UserControlなどでなくListBox派生クラスなので使い方も簡単! ふつうのListBoxと同様にツールボックスから挿入するか、
public class AnimationListBox : ListBox
{
private List<FrameworkElement> appendingItems = new List<FrameworkElement>();
private Duration duration = new Duration(TimeSpan.FromSeconds(1.0));
private double itemInterval = 0.2;public AnimationListBox()
{
this.CacheMode = new BitmapCache();
}protected override void OnItemsChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
base.OnItemsChanged(e);if (e.Action == NotifyCollectionChangedAction.Reset)
{
appendingItems.Clear();
}
else if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add)
{
foreach (var item in e.NewItems)
{
this.UpdateLayout();
FrameworkElement elm = item as FrameworkElement;
if (elm == null)
elm = base.ItemContainerGenerator.ContainerFromItem(item) as FrameworkElement;
if (elm != null)
{
elm.Opacity = 0.0;
appendingItems.Add(elm);
Dispatcher.BeginInvoke(() => { AppendToList(); });
}
}
}
}private void AppendToList()
{
for (int i = 0; i < appendingItems.Count; i++)
{
var item = appendingItems[i];
AnimateItem(item, i);
}
appendingItems.Clear();
}private void AnimateItem(FrameworkElement element, int delayCount)
{
Storyboard storyboard = new Storyboard();DoubleAnimation opaAni = new DoubleAnimation();
opaAni.From = 0.0;
opaAni.To = 1.0;
opaAni.Duration = duration;
opaAni.EasingFunction = new ExponentialEase() { EasingMode = EasingMode.EaseIn, Exponent = 10.0 };
Storyboard.SetTarget(opaAni, element);
Storyboard.SetTargetProperty(opaAni, new PropertyPath(UIElement.OpacityProperty));
element.Opacity = 0;
storyboard.Children.Add(opaAni);element.RenderTransform = new CompositeTransform();
DoubleAnimation slideAni = new DoubleAnimation();
slideAni.From = -100.0;
slideAni.To = 0;
slideAni.Duration = duration;
slideAni.EasingFunction = new ExponentialEase() { EasingMode = EasingMode.EaseIn, Exponent = 5.0 };
Storyboard.SetTarget(slideAni, element);
Storyboard.SetTargetProperty(slideAni, new PropertyPath("UIElement.RenderTransform.TranslateX"));
storyboard.Children.Add(slideAni);double delay = itemInterval * delayCount;
storyboard.BeginTime = new TimeSpan?(TimeSpan.FromSeconds(delay));
storyboard.Begin();
}
}
DataBindingでも基本動きますが、コレクションをまとめてどんとBindすると、Windows PhoneではOnItemsChanged()を呼んでくれないので、このサンプルではアニメーションされません。このままで使うなら、コレクションを追加してからコレクションにAdd()してやってください。
基本的に、これは一つのやり方なので他にも様々なアプローチがあると思います。
<my:AnimationListBox>
<TextBlock Text="List Item 1"/>
...
</my:AnimationListBox>
また、Storyboard全部作りっぱな、流しっぱなかよ、というツッコミも有りで、このあたりは気持ち悪ければstoryboard.CompletedをハンドルしてStop()などを後処理したりしてみてください。まぁ延々と追加と削除を繰り返したりしなければいいかな...、などと思い今回は削ってあります。
というわけで、みなさま、よいクリスマスを!
プログラミング WINDOWS PHONE (MSDNプログラミングシリーズ)
- 作者: 高橋忍
- 出版社/メーカー: 日経BP社
- 発売日: 2011/10/06
- メディア: 単行本
- 購入: 2人 クリック: 264回
- この商品を含むブログ (30件) を見る