Hi,
habe mir gestern überlegt, dass so eine Item-Animation schon was feines wäre. Folgende Idee: Immer, wenn ich den ItemsSource einer ListView/ListBox verändere, werden die Items des neuen ItemsSources schön eingeblendet.
Ich habe mir dafür ein Behavior geschrieben. Dieses funktioniert, wie schon gesagt, mit einer ListBox oder einem ListView. Sollte der Benutzer scrollen, wird die Animation gestoppt und alle Items werden sofort sichtbar, sodass es nicht komisch aussieht, weil eben nur die Items animiert werden, die der Benutzer zuerst sieht. Das ganze ist vollständig MVVM kompatibel und läuft auch super mit Data Visualisierung. So sieht dann das Resultat aus:
Die Animation könnt ihr ganz einfach ändern, ihr müsst nur die
Hier ist der Code des Animation-Behaviors:
Spoiler anzeigen
und hier noch die ListBox-Extensions: (Weil das
Spoiler anzeigen
Um das dann zum laufen zu bekommen, müsst ihr in XAML einfach nur den Namespace importieren, in der die
Ein Beispielprojekt findet ihr im Anhang. Viel Spaß
PS: Die Idee stammt von @GimpTutWorks
habe mir gestern überlegt, dass so eine Item-Animation schon was feines wäre. Folgende Idee: Immer, wenn ich den ItemsSource einer ListView/ListBox verändere, werden die Items des neuen ItemsSources schön eingeblendet.
Ich habe mir dafür ein Behavior geschrieben. Dieses funktioniert, wie schon gesagt, mit einer ListBox oder einem ListView. Sollte der Benutzer scrollen, wird die Animation gestoppt und alle Items werden sofort sichtbar, sodass es nicht komisch aussieht, weil eben nur die Items animiert werden, die der Benutzer zuerst sieht. Das ganze ist vollständig MVVM kompatibel und läuft auch super mit Data Visualisierung. So sieht dann das Resultat aus:
Die Animation könnt ihr ganz einfach ändern, ihr müsst nur die
TODO
-Kommentare suchen.Hier ist der Code des Animation-Behaviors:
C#-Quellcode
- class ItemsControlAnimationBehavior
- {
- public static readonly DependencyProperty IsAnimationEnabledProperty = DependencyProperty.RegisterAttached(
- "IsAnimationEnabled", typeof (bool), typeof (ItemsControlAnimationBehavior), new PropertyMetadata(false, PropertyChangedCallback));
- private static void PropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
- {
- var itemsControl = dependencyObject as ItemsControl;
- if (itemsControl == null)
- throw new ArgumentException("The dependencyObject isn't an ItemsControl");
- var dependencyPropertyDescriptor = DependencyPropertyDescriptor.FromProperty(ItemsControl.ItemsSourceProperty, typeof(ItemsControl));
- if ((bool)dependencyPropertyChangedEventArgs.NewValue)
- {
- dependencyPropertyDescriptor.AddValueChanged(dependencyObject, ItemsSourceChanged);
- }
- else
- {
- dependencyPropertyDescriptor.RemoveValueChanged(itemsControl, ItemsSourceChanged);
- }
- }
- private static readonly Dictionary<ItemsControl, DispatcherTimer> Timers = new Dictionary<ItemsControl, DispatcherTimer>();
- private async static void ItemsSourceChanged(object sender, EventArgs eventArgs)
- {
- var itemsControl = (ItemsControl) sender;
- //TODO: Hier die Animationen ändern
- var opacityAnimation = new DoubleAnimation(0, 1, TimeSpan.FromMilliseconds(260), FillBehavior.Stop);
- var marginAnimation = new ThicknessAnimation(new Thickness(-20, 0, 20, 0), new Thickness(0),
- TimeSpan.FromMilliseconds(240), FillBehavior.Stop)
- {
- EasingFunction = new CircleEase {EasingMode = EasingMode.EaseOut}
- };
- if (Timers.ContainsKey(itemsControl)) //Wenn der Timer bereits gestartet ist, stoppen wir diesen
- {
- Timers[itemsControl].Stop();
- Timers.Remove(itemsControl);
- }
- List<ListBoxItem> visibleItems;
- while (true)
- {
- visibleItems = ListBoxExtensions.GetVisibleItemsFromItemsControl(itemsControl,
- Window.GetWindow(itemsControl));
- if (visibleItems.Count > 0 || itemsControl.Items.Count == 0)
- break;
- await Task.Delay(1); //Das Problem ist, dass das Event ausgelöst wird, bevor die Items gerendert wurden. Also warten wir, bis welche da sind
- }
- foreach (var item in visibleItems)
- {
- item.Opacity = 0; //Wir setzten bei allen die Transparenz auf 0, damit die noch nicht animierten nicht sichtbar sind
- }
- DispatcherTimer dispatcherTimer;
- var enumerator = visibleItems.GetEnumerator();
- if (enumerator.MoveNext())
- {
- ScrollChangedEventHandler scrollChangedEventHandler = null;
- scrollChangedEventHandler = (o, args) =>
- {
- var handler = scrollChangedEventHandler; //Wir holen uns den Handler hier rein
- if (handler != null) //Wir löschen diesen, damit sich hier nichts überschneidet
- itemsControl.RemoveHandler(ScrollViewer.ScrollChangedEvent, handler);
- if (!Timers.ContainsKey(itemsControl) || !Timers[itemsControl].IsEnabled) //Wenn der Timer für das für diesen Handler bestimmten ItemsControl nicht existiert oder deaktiviert ist, wurde die Animation wohl schon beendet. Da wollen wir nicht dazwischen funken.
- return;
- var timer = Timers[itemsControl]; //Wir holen uns erstmal den Timer...
- timer.Stop(); //... und stoppen diesen
- while (true) //Anschließend gehen wir alle noch nicht animierte Elemente durch und machen sie sichtbar
- {
- var item = enumerator.Current;
- if (item == null)
- break;
- item.Opacity = 1;
- if (!enumerator.MoveNext()) break;
- }
- };
- dispatcherTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(30) }; //TODO: Hier könnt ihr die Zeitspanne zwischen den Animationen für die Element einstellen
- dispatcherTimer.Tick += (s, timerE) =>
- {
- var item = enumerator.Current; //Bei jedem Tick animieren wir das nächte Item
- if (item == null) return;
- item.BeginAnimation(FrameworkElement.MarginProperty, marginAnimation);
- item.BeginAnimation(UIElement.OpacityProperty, opacityAnimation);
- item.Opacity = 1;
- if (!enumerator.MoveNext()) //Wenn keines mehr da ist, stoppen wir den Timer und löschen den Handler für das Scroll-Event
- {
- dispatcherTimer.Stop();
- itemsControl.RemoveHandler(ScrollViewer.ScrollChangedEvent, scrollChangedEventHandler);
- }
- };
- Timers.Add(itemsControl, dispatcherTimer);
- dispatcherTimer.Start();
- itemsControl.AddHandler(ScrollViewer.ScrollChangedEvent, scrollChangedEventHandler);
- }
- }
- public static void SetIsAnimationEnabled(DependencyObject element, bool value)
- {
- element.SetValue(IsAnimationEnabledProperty, value);
- }
- public static bool GetIsAnimationEnabled(DependencyObject element)
- {
- return (bool) element.GetValue(IsAnimationEnabledProperty);
- }
- }
und hier noch die ListBox-Extensions: (Weil das
ListViewItem
von ListBoxItem
erbt, funktionieren die mit der ListView
auch)C#-Quellcode
- static class ListBoxExtensions
- {
- private static bool IsUserVisible(FrameworkElement element, FrameworkElement container)
- {
- if (!element.IsVisible)
- return false;
- Rect bounds =
- element.TransformToAncestor(container).TransformBounds(new Rect(0.0, 0.0, element.ActualWidth, element.ActualHeight));
- var rect = new Rect(0.0, 0.0, container.ActualWidth, container.ActualHeight);
- return rect.Contains(bounds.TopLeft) || rect.Contains(bounds.BottomRight);
- }
- public static List<ListBoxItem> GetVisibleItemsFromItemsControl(ItemsControl itemsControl, FrameworkElement parentToTestVisibility)
- {
- var items = new List<ListBoxItem>();
- foreach (var item in itemsControl.ItemsSource)
- {
- var lvItem = (ListBoxItem)itemsControl.ItemContainerGenerator.ContainerFromItem(item);
- if (lvItem == null)
- continue;
- if (IsUserVisible(lvItem, parentToTestVisibility))
- {
- items.Add(lvItem);
- }
- else if (items.Any())
- {
- break;
- }
- }
- return items;
- }
- }
Um das dann zum laufen zu bekommen, müsst ihr in XAML einfach nur den Namespace importieren, in der die
ItemsControlAnimationExtension
-Klasse liegt und dann die Eigenschaft IsAnimationEnabled
der Klasse auf true
setzen:Ein Beispielprojekt findet ihr im Anhang. Viel Spaß
PS: Die Idee stammt von @GimpTutWorks
Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „VincentTB“ ()