Skip to content

Instantly share code, notes, and snippets.

@mfakane
Created May 5, 2012 11:08
Show Gist options
  • Save mfakane/2601600 to your computer and use it in GitHub Desktop.
Save mfakane/2601600 to your computer and use it in GitHub Desktop.
Heavymoon ListBox Item Animator
/*
<ListBox>
<i:Interaction.Behaviors>
<v:MetroAnimateItemsBehavior />
</i:Interaction.Behaviors>
</ListBox>
*/
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Interactivity;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Threading;
using Linearstar.Lavis;
namespace Linearstar.Heavymoon.Views
{
public class MetroAnimateItemsBehavior : Behavior<ItemsControl>
{
readonly CompositeDisposable observer = new CompositeDisposable();
readonly DispatcherTimer timer = new DispatcherTimer();
readonly Queue<FrameworkElement> pool = new Queue<FrameworkElement>();
readonly Storyboard defaultAppear;
IDisposable collectionChanged;
public static string VisibleStateName
{
get
{
return "MetroAnimateItemsVisible";
}
}
public static string HiddenStateName
{
get
{
return "MetroAnimateItemsHidden";
}
}
public TimeSpan Interval
{
get
{
return timer.Interval;
}
set
{
timer.Interval = value;
}
}
public Storyboard Disappear
{
get;
set;
}
public Storyboard Appear
{
get;
set;
}
public bool UseStoryboard
{
get;
set;
}
public bool AnimateOnLoaded
{
get;
set;
}
public MetroAnimateItemsBehavior()
{
this.Interval = TimeSpan.FromMilliseconds(50);
this.UseStoryboard = true;
this.AnimateOnLoaded = true;
this.Appear = defaultAppear = new Storyboard
{
Children =
{
new DoubleAnimation(-8, 0, new Duration(TimeSpan.FromMilliseconds(500)))
{
EasingFunction = new CubicEase
{
EasingMode = EasingMode.EaseOut,
},
}.Apply(_ => Storyboard.SetTargetProperty(_, new PropertyPath("RenderTransform.X"))),
new DoubleAnimation(0, 1, new Duration(TimeSpan.FromMilliseconds(500)))
{
EasingFunction = new CubicEase
{
EasingMode = EasingMode.EaseOut,
},
}.Apply(_ => Storyboard.SetTargetProperty(_, new PropertyPath("Opacity"))),
},
};
}
protected override void OnAttached()
{
observer.Add(Observable.FromEvent<EventHandler, EventArgs>(_ => (sender, e) => _(e), _ => timer.Tick += _, _ => timer.Tick -= _)
.Where(_ => pool.Any())
.Select(_ => pool.Dequeue())
.Subscribe(_ =>
{
// fixes conflicting against FluidMoveBehavior
if (this.Appear == defaultAppear && !(_.RenderTransform is TranslateTransform))
_.RenderTransform = new TranslateTransform();
if (this.Appear != null && this.UseStoryboard)
this.Appear.Begin(_, true);
else
VisualStateManager.GoToState(_, VisibleStateName, true);
}));
timer.Start();
observer.Add(Observable.FromEvent<RoutedEventHandler, RoutedEventArgs>(_ => (sender, e) => _(e), _ => this.AssociatedObject.Loaded += _, _ => this.AssociatedObject.Loaded -= _)
.Merge(Observable.FromEvent<DependencyPropertyChangedEventHandler, DependencyPropertyChangedEventArgs>(_ => (sender, e) => _(e), _ => this.AssociatedObject.IsVisibleChanged += _, _ => this.AssociatedObject.IsVisibleChanged -= _).Where(_ => (bool)_.NewValue).Select(_ => RoutedEventArgs.Empty))
.Subscribe(e =>
{
if (this.AnimateOnLoaded)
{
pool.Clear();
GetItems().ForEach(Animate);
}
}));
base.OnAttached();
}
public void Reanimate()
{
var source = this.AssociatedObject.ItemsSource as INotifyCollectionChanged;
(source == null
? this.AssociatedObject.Items.Cast<FrameworkElement>()
: this.AssociatedObject.ItemsSource
.Cast<object>()
.Select(this.AssociatedObject.ItemContainerGenerator.ContainerFromItem)
.Cast<FrameworkElement>()
.Where(_ => _ != null))
.ForEach(Animate);
}
void Animate(FrameworkElement item)
{
if (this.Appear != null && this.UseStoryboard)
{
// fixes conflicting against FluidMoveBehavior
if (this.Appear == defaultAppear && !(item.RenderTransform is TranslateTransform))
item.RenderTransform = new TranslateTransform();
this.Appear.Begin(item, true);
this.Appear.Pause(item);
}
else
VisualStateManager.GoToState(item, HiddenStateName, false);
if (!pool.Contains(item))
pool.Enqueue(item);
}
IList<FrameworkElement> GetItems()
{
var source = this.AssociatedObject.ItemsSource as INotifyCollectionChanged;
if (collectionChanged != null)
{
observer.Remove(collectionChanged);
collectionChanged.Dispose();
collectionChanged = null;
}
if (source != null)
{
var handler = Observable.FromEvent<NotifyCollectionChangedEventHandler, NotifyCollectionChangedEventArgs>(_ => (sender2, e2) => _(e2), _ => source.CollectionChanged += _, _ => source.CollectionChanged -= _);
var added = handler.Where(_ => _.Action == NotifyCollectionChangedAction.Add)
.SelectMany(_ => _.NewItems.Cast<object>())
.Select(_ => Tuple.Create(_, (FrameworkElement)this.AssociatedObject.ItemContainerGenerator.ContainerFromItem(_)))
.Multicast(new Subject<Tuple<object, FrameworkElement>>());
added.Where(_ => _.Item2 == null)
.Select(_ => _.Item1)
.Subscribe(i => Observable.FromEvent<EventHandler, EventArgs>(_ => (sender3, e3) => _(e3), _ => this.AssociatedObject.ItemContainerGenerator.StatusChanged += _, _ => this.AssociatedObject.ItemContainerGenerator.StatusChanged -= _)
.Where(_ => this.AssociatedObject.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated)
.Take(1)
.Select(_ => this.AssociatedObject.ItemContainerGenerator.ContainerFromItem(i))
.Cast<FrameworkElement>()
.Where(_ => _ != null)
.Subscribe(_ => Animate(_)));
added.Where(_ => _.Item2 != null)
.Select(_ => _.Item2)
.Subscribe(Animate);
observer.Add(collectionChanged = added.Connect());
return Enumerable.Range(0, this.AssociatedObject.Items.Count)
.Select(this.AssociatedObject.ItemContainerGenerator.ContainerFromIndex)
.Cast<FrameworkElement>()
.Where(_ => _ != null)
.Freeze();
}
else
return this.AssociatedObject.Items.Cast<FrameworkElement>().Freeze();
}
protected override void OnDetaching()
{
observer.Dispose();
timer.Stop();
base.OnDetaching();
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment