gpt4 book ai didi

wpf - ScrollViewer 上的动画(平滑)滚动

转载 作者:行者123 更新时间:2023-12-02 13:38:59 27 4
gpt4 key购买 nike

我的 WPF 应用程序中有一个 ScrollViewer,我希望它具有平滑/动画滚动效果,就像 Firefox 那样(如果你知道我在说什么) )。

我尝试在互联网上搜索,唯一找到的是:

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

它工作得很好,但我有一个问题 - 它动画滚动效果,但 ScrollViewerThumb 直接转到按下的点 - 我想要它也将被动画化

如何使 ScrollViewerThumb 也具有动画效果,或者是否有一个具有我想要的相同属性/功能的工作控件?

最佳答案

在您的示例中,有两个继承自 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>

输出

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

一些注释

  • 该示例仅实现了垂直动画,如果您接受此项目,您将毫无问题地实现水平动画。

  • ListBox中当前项的选择没有转移到下一个元素这是由于PreviewKeyDown事件的拦截,所以你要考虑一下这一刻。

  • 此实现完全适合 MVVM 模式。要在Blend中使用此行为,您需要继承接口(interface)Behavior。示例可见herehere .

已在 Windows XP、Windows 7、.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
}

关于wpf - ScrollViewer 上的动画(平滑)滚动,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20731402/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com