2013-12-22 130 views
23

我在我的WPF應用程序中有一個ScrollViewer,我希望它具有平滑/動畫滾動效果,就像Firefox(如果您知道我在說什麼)。ScrollViewer上的動畫(平滑)滾動

我試圖尋找在互聯網上,我發現的唯一的事情是這樣的:

How To Create An Animated ScrollViewer (or ListBox) in WPF

它的工作原理相當不錯,但我有一個問題 - 這動畫滾動效果但ScrollViewerThumb直接去按點 - 我希望它是藏漢動畫

我怎樣才能使ScrollViewerThumb被藏漢動畫,否則是沒有用相同的親工作控制perties /我想要的功能?

+0

唉......在鏈路的整個做法是非常hackish的:它複製的控制和DPS保持現有的控制行爲,同時加入動畫。我嘗試了一些方法來獲得所需的行爲(在「animateScroller」中爲動畫滾動條製作動畫,使「PART_AniVerticalScrollBar」成爲雙向綁定),但每種方式都會陷入奇怪的行爲。 – McGarnagle

+1

我最好的建議是重新編寫整個'ScrollViewer'控件。我意識到這是有點棘手的...但子類化現有的控制在我看來太麻煩了,因爲缺乏動畫被烘烤。 – McGarnagle

+0

我創建了我自己的控件,由兩個ScrollBar和一個ScrollViewer組成隱藏滾動條,將滾動條從滾動查看器中分離出來。然後,我可以輕鬆實現摩擦滾動,因爲我手動處理滾動條拖動。 –

回答

36

在您的例子有來自ScrollViewerListBox繼承了兩個控件,動畫是由SplineDoubleKeyFrame[MSDN]實現。在我的時候,我意識到動畫通過附加依賴屬性VerticalOffsetProperty,它允許您偏移滾動條直接轉移到雙動畫,這樣滾動:

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(); 

的例子可以在這裏找到:

How to: Animate the Horizontal/VerticalOffset properties of a ScrollViewer

WPF - Animate ListBox.ScrollViewer.HorizontalOffset?

在這種情況下,工作內容和Thumb井平滑滾動。基於這種方法,並使用您的示例 [How To Create An Animated ScrollViewer (or ListBox) in WPF],我創建了一個附加行爲ScrollAnimationBehavior,它可以應用於ScrollViewerListBox

使用的示例:

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

enter image description here

IsEnabled屬性負責爲ScrollViewerListBox滾動動畫。下面的執行:

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); 
    } 
} 

在這些Loaded處理程序爲PreviewMouseWheelPreviewKeyDown設置事件處理程序。

助手(輔助程序)取自示例,並提供值爲double的類型,該值將傳遞給過程AnimateScroll()。這裏並動畫的法寶:

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

  • 僅實現垂直的動畫,如果你接受這個項目的例子,你會發現自己沒有問題的水平的動畫。

  • ListBox當前項目的選擇沒有轉移到下一個元素,這是由於截取了事件PreviewKeyDown,所以你必須考慮這個時刻。

  • 此實現完全適用於MVVM模式。要在Blend中使用此行爲,您需要繼承接口Behavior。例子可以發現herehere

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


示例項目可在此link


以下是該實現的全碼:

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 
} 
1

滾動自定義的最佳示例可以在代碼項目的Sacha Barber文章中找到。看到這個code project article on Friction scrolling關於這個話題的文章。

許多Sacha Barbers WPF代碼已被集成到WPF的Github項目中。有關非常有用的開源WPF實現,請參見MahaApps Metro

+5

我不認爲這真的解決了這個問題? OP想要動畫滾動條,但「摩擦滾動」的東西根本沒有滾動條。 – McGarnagle