wpf animation scrollviewer
link

wpf - Desplazamiento animado(Suave) en ScrollViewer



animation (2)

El mejor ejemplo de personalización de desplazamiento se puede encontrar en un artículo de Sacha Barber en Code Project. Vea este artículo de proyecto de código en el artículo de desplazamiento de Fricción sobre el tema.

Varios códigos WPF de Sacha Barbers se han integrado en un proyecto de Github para WPF. Vea MahaApps Metro para algunas implementaciones de WPF de código abierto muy útiles.

Tengo un ScrollViewer en mi aplicación WPF, y quiero que tenga un efecto de desplazamiento suave / animado como Firefox (si sabes de lo que estoy hablando).

Traté de buscar en internet, y lo único que encontré es esto:

Cómo crear un ScrollViewer animado (o ListBox) en WPF

Funciona bastante bien, pero tengo un problema: anima el efecto de desplazamiento, pero el Thumb ScrollViewer va directamente al punto presionado. También quiero que esté animado.

¿Cómo puedo hacer que el Thumb ScrollViewer también sea animado, o de lo contrario hay un control que funcione con las mismas propiedades / características que quiero?


En su ejemplo, hay dos controles heredados de ScrollViewer y ListBox , la animación está implementada por SplineDoubleKeyFrame [MSDN] . En mi época, realicé el desplazamiento de animación a través de la propiedad de dependencia asociada VerticalOffsetProperty , que le permite transferir directamente la barra de desplazamiento desplazada a una animación doble, como esta:

DoubleAnimation verticalAnimation = new DoubleAnimation(); verticalAnimation.From = scrollViewer.VerticalOffset; verticalAnimation.To = some value; verticalAnimation.Duration = new Duration( some duration ); Storyboard storyboard = new Storyboard(); storyboard.Children.Add(verticalAnimation); Storyboard.SetTarget(verticalAnimation, scrollViewer); Storyboard.SetTargetProperty(verticalAnimation, new PropertyPath(ScrollAnimationBehavior.VerticalOffsetProperty)); // Attached dependency property storyboard.Begin();

Los ejemplos se pueden encontrar aquí:

Cómo: animar las propiedades Horizontal / VerticalOffset de un ScrollViewer

WPF: Animate ListBox.ScrollViewer.HorizontalOffset?

En este caso, funciona bien el desplazamiento suave del contenido y el Thumb . Basado en este enfoque, y usando su ejemplo [Cómo crear un ScrollViewer animado (o ListBox) en WPF] , creé un comportamiento adjunto ScrollAnimationBehavior , que se puede aplicar a ScrollViewer y ListBox .

Ejemplo de uso:

XAML

<Window x:Class="ScrollAnimateBehavior.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:sys="clr-namespace:System;assembly=mscorlib" xmlns:AttachedBehavior="clr-namespace:ScrollAnimateBehavior.AttachedBehaviors" Title="MainWindow" WindowStartupLocation="CenterScreen" Height="350" Width="525"> <Window.Resources> <x:Array x:Key="TestArray" Type="{x:Type sys:String}"> <sys:String>TEST 1</sys:String> <sys:String>TEST 2</sys:String> <sys:String>TEST 3</sys:String> <sys:String>TEST 4</sys:String> <sys:String>TEST 5</sys:String> <sys:String>TEST 6</sys:String> <sys:String>TEST 7</sys:String> <sys:String>TEST 8</sys:String> <sys:String>TEST 9</sys:String> <sys:String>TEST 10</sys:String> </x:Array> </Window.Resources> <Grid> <TextBlock Text="ScrollViewer" FontFamily="Verdana" FontSize="14" VerticalAlignment="Top" HorizontalAlignment="Left" Margin="80,80,0,0" /> <ScrollViewer AttachedBehavior:ScrollAnimationBehavior.IsEnabled="True" AttachedBehavior:ScrollAnimationBehavior.TimeDuration="00:00:00.20" AttachedBehavior:ScrollAnimationBehavior.PointsToScroll="16" HorizontalAlignment="Left" Width="250" Height="100"> <StackPanel> <ItemsControl ItemsSource="{StaticResource TestArray}" FontSize="16" /> </StackPanel> </ScrollViewer> <TextBlock Text="ListBox" FontFamily="Verdana" FontSize="14" VerticalAlignment="Top" HorizontalAlignment="Right" Margin="0,80,100,0" /> <ListBox AttachedBehavior:ScrollAnimationBehavior.IsEnabled="True" ItemsSource="{StaticResource TestArray}" ScrollViewer.CanContentScroll="False" HorizontalAlignment="Right" FontSize="16" Width="250" Height="100" /> </Grid> </Window>

Output

IsEnabled propiedad IsEnabled es responsable de la animación de desplazamiento para ScrollViewer y para ListBox . Debajo de su implementación:

public static DependencyProperty IsEnabledProperty = DependencyProperty.RegisterAttached("IsEnabled", typeof(bool), typeof(ScrollAnimationBehavior), new UIPropertyMetadata(false, OnIsEnabledChanged)); public static void SetIsEnabled(FrameworkElement target, bool value) { target.SetValue(IsEnabledProperty, value); } public static bool GetIsEnabled(FrameworkElement target) { return (bool)target.GetValue(IsEnabledProperty); } private static void OnIsEnabledChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) { var target = sender; if (target != null && target is ScrollViewer) { ScrollViewer scroller = target as ScrollViewer; scroller.Loaded += new RoutedEventHandler(scrollerLoaded); } if (target != null && target is ListBox) { ListBox listbox = target as ListBox; listbox.Loaded += new RoutedEventHandler(listboxLoaded); } }

En estos controladores Loaded se configuran los controladores de eventos para PreviewMouseWheel y PreviewKeyDown .

Los auxiliares (procedimientos auxiliares) se toman del ejemplo y proporcionan un valor de tipo double , que se pasa al procedimiento AnimateScroll() . Aquí están la clave mágica de la animación:

private static void AnimateScroll(ScrollViewer scrollViewer, double ToValue) { DoubleAnimation verticalAnimation = new DoubleAnimation(); verticalAnimation.From = scrollViewer.VerticalOffset; verticalAnimation.To = ToValue; verticalAnimation.Duration = new Duration(GetTimeDuration(scrollViewer)); Storyboard storyboard = new Storyboard(); storyboard.Children.Add(verticalAnimation); Storyboard.SetTarget(verticalAnimation, scrollViewer); Storyboard.SetTargetProperty(verticalAnimation, new PropertyPath(ScrollAnimationBehavior.VerticalOffsetProperty)); storyboard.Begin(); }

Some notes

  • El ejemplo solo implementó animación vertical, si acepta este proyecto, se realizará sin problemas la animación horizontal.

  • La selección del elemento actual en ListBox no transferido al siguiente elemento de esto se debe a la interceptación de eventos PreviewKeyDown , por lo que debe pensar en este momento.

  • Esta implementación es totalmente adecuada para el patrón MVVM. Para usar este comportamiento en la Blend , necesita heredar el Behavior interfaz. El ejemplo se puede encontrar here y here .

Tested on Windows XP, Windows Seven, .NET 4.0.

El proyecto de muestra está disponible en este link .

A continuación se muestra un código completo de esta implementación:

public static class ScrollAnimationBehavior { #region Private ScrollViewer for ListBox private static ScrollViewer _listBoxScroller = new ScrollViewer(); #endregion #region VerticalOffset Property public static DependencyProperty VerticalOffsetProperty = DependencyProperty.RegisterAttached("VerticalOffset", typeof(double), typeof(ScrollAnimationBehavior), new UIPropertyMetadata(0.0, OnVerticalOffsetChanged)); public static void SetVerticalOffset(FrameworkElement target, double value) { target.SetValue(VerticalOffsetProperty, value); } public static double GetVerticalOffset(FrameworkElement target) { return (double)target.GetValue(VerticalOffsetProperty); } #endregion #region TimeDuration Property public static DependencyProperty TimeDurationProperty = DependencyProperty.RegisterAttached("TimeDuration", typeof(TimeSpan), typeof(ScrollAnimationBehavior), new PropertyMetadata(new TimeSpan(0, 0, 0, 0, 0))); public static void SetTimeDuration(FrameworkElement target, TimeSpan value) { target.SetValue(TimeDurationProperty, value); } public static TimeSpan GetTimeDuration(FrameworkElement target) { return (TimeSpan)target.GetValue(TimeDurationProperty); } #endregion #region PointsToScroll Property public static DependencyProperty PointsToScrollProperty = DependencyProperty.RegisterAttached("PointsToScroll", typeof(double), typeof(ScrollAnimationBehavior), new PropertyMetadata(0.0)); public static void SetPointsToScroll(FrameworkElement target, double value) { target.SetValue(PointsToScrollProperty, value); } public static double GetPointsToScroll(FrameworkElement target) { return (double)target.GetValue(PointsToScrollProperty); } #endregion #region OnVerticalOffset Changed private static void OnVerticalOffsetChanged(DependencyObject target, DependencyPropertyChangedEventArgs e) { ScrollViewer scrollViewer = target as ScrollViewer; if (scrollViewer != null) { scrollViewer.ScrollToVerticalOffset((double)e.NewValue); } } #endregion #region IsEnabled Property public static DependencyProperty IsEnabledProperty = DependencyProperty.RegisterAttached("IsEnabled", typeof(bool), typeof(ScrollAnimationBehavior), new UIPropertyMetadata(false, OnIsEnabledChanged)); public static void SetIsEnabled(FrameworkElement target, bool value) { target.SetValue(IsEnabledProperty, value); } public static bool GetIsEnabled(FrameworkElement target) { return (bool)target.GetValue(IsEnabledProperty); } #endregion #region OnIsEnabledChanged Changed private static void OnIsEnabledChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) { var target = sender; if (target != null && target is ScrollViewer) { ScrollViewer scroller = target as ScrollViewer; scroller.Loaded += new RoutedEventHandler(scrollerLoaded); } if (target != null && target is ListBox) { ListBox listbox = target as ListBox; listbox.Loaded += new RoutedEventHandler(listboxLoaded); } } #endregion #region AnimateScroll Helper private static void AnimateScroll(ScrollViewer scrollViewer, double ToValue) { DoubleAnimation verticalAnimation = new DoubleAnimation(); verticalAnimation.From = scrollViewer.VerticalOffset; verticalAnimation.To = ToValue; verticalAnimation.Duration = new Duration(GetTimeDuration(scrollViewer)); Storyboard storyboard = new Storyboard(); storyboard.Children.Add(verticalAnimation); Storyboard.SetTarget(verticalAnimation, scrollViewer); Storyboard.SetTargetProperty(verticalAnimation, new PropertyPath(ScrollAnimationBehavior.VerticalOffsetProperty)); storyboard.Begin(); } #endregion #region NormalizeScrollPos Helper private static double NormalizeScrollPos(ScrollViewer scroll, double scrollChange, Orientation o) { double returnValue = scrollChange; if (scrollChange < 0) { returnValue = 0; } if (o == Orientation.Vertical && scrollChange > scroll.ScrollableHeight) { returnValue = scroll.ScrollableHeight; } else if (o == Orientation.Horizontal && scrollChange > scroll.ScrollableWidth) { returnValue = scroll.ScrollableWidth; } return returnValue; } #endregion #region UpdateScrollPosition Helper private static void UpdateScrollPosition(object sender) { ListBox listbox = sender as ListBox; if (listbox != null) { double scrollTo = 0; for (int i = 0; i < (listbox.SelectedIndex); i++) { ListBoxItem tempItem = listbox.ItemContainerGenerator.ContainerFromItem(listbox.Items[i]) as ListBoxItem; if (tempItem != null) { scrollTo += tempItem.ActualHeight; } } AnimateScroll(_listBoxScroller, scrollTo); } } #endregion #region SetEventHandlersForScrollViewer Helper private static void SetEventHandlersForScrollViewer(ScrollViewer scroller) { scroller.PreviewMouseWheel += new MouseWheelEventHandler(ScrollViewerPreviewMouseWheel); scroller.PreviewKeyDown += new KeyEventHandler(ScrollViewerPreviewKeyDown); } #endregion #region scrollerLoaded Event Handler private static void scrollerLoaded(object sender, RoutedEventArgs e) { ScrollViewer scroller = sender as ScrollViewer; SetEventHandlersForScrollViewer(scroller); } #endregion #region listboxLoaded Event Handler private static void listboxLoaded(object sender, RoutedEventArgs e) { ListBox listbox = sender as ListBox; _listBoxScroller = FindVisualChildHelper.GetFirstChildOfType<ScrollViewer>(listbox); SetEventHandlersForScrollViewer(_listBoxScroller); SetTimeDuration(_listBoxScroller, new TimeSpan(0, 0, 0, 0, 200)); SetPointsToScroll(_listBoxScroller, 16.0); listbox.SelectionChanged += new SelectionChangedEventHandler(ListBoxSelectionChanged); listbox.Loaded += new RoutedEventHandler(ListBoxLoaded); listbox.LayoutUpdated += new EventHandler(ListBoxLayoutUpdated); } #endregion #region ScrollViewerPreviewMouseWheel Event Handler private static void ScrollViewerPreviewMouseWheel(object sender, MouseWheelEventArgs e) { double mouseWheelChange = (double)e.Delta; ScrollViewer scroller = (ScrollViewer)sender; double newVOffset = GetVerticalOffset(scroller) - (mouseWheelChange / 3); if (newVOffset < 0) { AnimateScroll(scroller, 0); } else if (newVOffset > scroller.ScrollableHeight) { AnimateScroll(scroller, scroller.ScrollableHeight); } else { AnimateScroll(scroller, newVOffset); } e.Handled = true; } #endregion #region ScrollViewerPreviewKeyDown Handler private static void ScrollViewerPreviewKeyDown(object sender, KeyEventArgs e) { ScrollViewer scroller = (ScrollViewer)sender; Key keyPressed = e.Key; double newVerticalPos = GetVerticalOffset(scroller); bool isKeyHandled = false; if (keyPressed == Key.Down) { newVerticalPos = NormalizeScrollPos(scroller, (newVerticalPos + GetPointsToScroll(scroller)), Orientation.Vertical); isKeyHandled = true; } else if (keyPressed == Key.PageDown) { newVerticalPos = NormalizeScrollPos(scroller, (newVerticalPos + scroller.ViewportHeight), Orientation.Vertical); isKeyHandled = true; } else if (keyPressed == Key.Up) { newVerticalPos = NormalizeScrollPos(scroller, (newVerticalPos - GetPointsToScroll(scroller)), Orientation.Vertical); isKeyHandled = true; } else if (keyPressed == Key.PageUp) { newVerticalPos = NormalizeScrollPos(scroller, (newVerticalPos - scroller.ViewportHeight), Orientation.Vertical); isKeyHandled = true; } if (newVerticalPos != GetVerticalOffset(scroller)) { AnimateScroll(scroller, newVerticalPos); } e.Handled = isKeyHandled; } #endregion #region ListBox Event Handlers private static void ListBoxLayoutUpdated(object sender, EventArgs e) { UpdateScrollPosition(sender); } private static void ListBoxLoaded(object sender, RoutedEventArgs e) { UpdateScrollPosition(sender); } private static void ListBoxSelectionChanged(object sender, SelectionChangedEventArgs e) { UpdateScrollPosition(sender); } #endregion }