以编程方式(平滑地)动画化ScrollViewer

36

有没有一种方法能够在Windows Phone 8.1 Runtime中平滑地动画化ScrollViewer的垂直偏移量?

我尝试过使用ScrollViewer.ChangeView()方法,但无论我将disableAnimation参数设置为true还是false,垂直偏移的变化都没有被动画化。

例如:myScrollViewer.ChangeView(null, myScrollViewer.VerticalOffset + p, null, false); 偏移量会改变,但没有动画效果。

我还尝试使用了一个垂直偏移调解器:

/// <summary>
/// Mediator that forwards Offset property changes on to a ScrollViewer
/// instance to enable the animation of Horizontal/VerticalOffset.
/// </summary>
public sealed class ScrollViewerOffsetMediator : FrameworkElement
{
    /// <summary>
    /// ScrollViewer instance to forward Offset changes on to.
    /// </summary>
    public ScrollViewer ScrollViewer
    {
        get { return (ScrollViewer)GetValue(ScrollViewerProperty); }
        set { SetValue(ScrollViewerProperty, value); }
    }
    public static readonly DependencyProperty ScrollViewerProperty =
            DependencyProperty.Register("ScrollViewer",
            typeof(ScrollViewer),
            typeof(ScrollViewerOffsetMediator),
            new PropertyMetadata(null, OnScrollViewerChanged));
    private static void OnScrollViewerChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
    {
        var mediator = (ScrollViewerOffsetMediator)o;
        var scrollViewer = (ScrollViewer)(e.NewValue);
        if (null != scrollViewer)
        {
            scrollViewer.ScrollToVerticalOffset(mediator.VerticalOffset);
        }
    }

    /// <summary>
    /// VerticalOffset property to forward to the ScrollViewer.
    /// </summary>
    public double VerticalOffset
    {
        get { return (double)GetValue(VerticalOffsetProperty); }
        set { SetValue(VerticalOffsetProperty, value); }
    }
    public static readonly DependencyProperty VerticalOffsetProperty =
            DependencyProperty.Register("VerticalOffset",
            typeof(double),
            typeof(ScrollViewerOffsetMediator),
            new PropertyMetadata(0.0, OnVerticalOffsetChanged));
    public static void OnVerticalOffsetChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
    {
        var mediator = (ScrollViewerOffsetMediator)o;
        if (null != mediator.ScrollViewer)
        {
            mediator.ScrollViewer.ScrollToVerticalOffset((double)(e.NewValue));
        }
    }

    /// <summary>
    /// Multiplier for ScrollableHeight property to forward to the ScrollViewer.
    /// </summary>
    /// <remarks>
    /// 0.0 means "scrolled to top"; 1.0 means "scrolled to bottom".
    /// </remarks>
    public double ScrollableHeightMultiplier
    {
        get { return (double)GetValue(ScrollableHeightMultiplierProperty); }
        set { SetValue(ScrollableHeightMultiplierProperty, value); }
    }
    public static readonly DependencyProperty ScrollableHeightMultiplierProperty =
            DependencyProperty.Register("ScrollableHeightMultiplier",
            typeof(double),
            typeof(ScrollViewerOffsetMediator),
            new PropertyMetadata(0.0, OnScrollableHeightMultiplierChanged));
    public static void OnScrollableHeightMultiplierChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
    {
        var mediator = (ScrollViewerOffsetMediator)o;
        var scrollViewer = mediator.ScrollViewer;
        if (null != scrollViewer)
        {
            scrollViewer.ScrollToVerticalOffset((double)(e.NewValue) * scrollViewer.ScrollableHeight);
        }
    }
}

我可以使用DoubleAnimation动画来操控VerticalOffset属性:

Storyboard sb = new Storyboard();
DoubleAnimation da = new DoubleAnimation();
da.EnableDependentAnimation = true;
da.From = Mediator.ScrollViewer.VerticalOffset;
da.To = da.From + p;
da.Duration = new Duration(TimeSpan.FromMilliseconds(300));
da.EasingFunction = new ExponentialEase() { EasingMode = EasingMode.EaseOut };
Storyboard.SetTarget(da, Mediator);
Storyboard.SetTargetProperty(da, "(Mediator.VerticalOffset)");
sb.Children.Add(da);

sb.Begin();

中介者在 XAML 中声明。 但是在我的设备上(Lumia 930),这个动画不够流畅。


4
你可以尝试使用WinRTXamlToolkitScrollToVerticalOffsetWithAnimation扩展功能。你可以手动实现它,或通过Nuget添加该库。 - Nate Diamond
2
我认为没有办法使其完全流畅。这种动画可能没有硬件加速。 - Lukáš Neoproud
还在寻找答案吗? - user4843530
你能给我一个例子,ChangeView不执行动画吗?无论虚拟化是否开启,它都应该执行动画。 - Justin XL
在我的项目中,我正在使用ListViewScrollViewer,但它从未进行过动画。也许是因为我的数据模板太复杂了,但我怀疑这一点,因为滚动非常流畅,只是编程滚动不行。当我使用ChangeView时,它只是改变垂直偏移而没有动画效果。 - Kristian Vukusic
显示剩余6条评论
4个回答

15

无论是否启用数据虚拟化,您都应该坚持使用ChangeView来进行滚动动画。

如果没有看到ChangeView不能工作的代码,很难猜测实际情况,但有几件事情可以尝试。

第一种方法是在调用ChangeView之前添加Task.Delay(1),只是为了给操作系统一些时间来完成其他并发的UI任务。

await Task.Delay(1);
scrollViewer.ChangeView(null, scrollViewer.ScrollableHeight, null, false);

第二种方法稍微复杂一些。我注意到,当你在ListView中有许多复杂的项目时,从第一个项目到最后一个项目(从ChangeView方法中)的滚动动画并不顺畅。

这是因为ListView首先需要由于数据虚拟化而实现/渲染许多项目,然后才进行动画滚动。在我看来效率不高。

我想到的解决办法是:首先使用非动画的ListView.ScrollIntoView滚动到最后一个项目,以使其实现。然后,调用ChangeView将偏移量向上移动到ListViewActualHeight * 2大小,并禁用动画(根据您的应用程序滚动体验,可以将其更改为任何大小)。最后,再次调用ChangeView以启用动画将其滚回末尾。这样做将提供更好的滚动体验,因为滚动距离仅为ListViewActualHeight

请记住,如果您要滚动到的项目已经在UI上实现,您不需要执行上述任何操作。您只需计算此项目与ScrollViewer顶部之间的距离,并调用ChangeView滚动到它。

我已经将上述逻辑封装在这个答案Update 2部分中(感谢这个问题让我意识到我的初始答案当虚拟化开启时无法正常工作:p)。让我知道你的进展如何。


2
第一种方法通过使用Task.Delay使得ChangeView更加平滑,我从未想到这会有如此大的帮助。非常感谢。 - Kristian Vukusic

4

3
当您查看ScrollViewerExtensions.cs的源代码时,创建动画的方法名为ScrollToVerticalOffsetWithAnimation,您可以看到该行:da.EnableDependentAnimation = true表示该动画不在组合线程上运行,这意味着它可能不会运行得很平滑。这就是主要问题,如何使用独立动画来动画滚动视图控件。 - Kristian Vukusic
这两者都与WinRT/Windows10无关。 - Jerry Nixon

0

随着在较新版本的Windows 10中ScrollToVerticalOffset被弃用(导致ScrollViewOffSetMediator扩展控件不再工作),以及新的ChangeView方法实际上不能提供平滑或可控制的动画,需要一个新的解决方案。请参见我的答案,它允许您平稳地动画和缩放ScrollViewer及其内容到任何所需位置,而不管应用程序的最终用户最初将滚动条放置在哪里:

如何在UWP中滚动到元素


0

我相信这篇文章就是你要找的,而且他使用的方法似乎对你有用。

快速方法:

  1. 手动向scrollviewer添加偏移依赖参数。

  2. 复制你的scrollviewer。

  3. 使用动画器。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接