Created
May 5, 2012 11:08
-
-
Save mfakane/2601600 to your computer and use it in GitHub Desktop.
Heavymoon ListBox Item Animator
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
<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