【Win10】实现 ListViewBase 平滑滚动

原文:【Win10】实现 ListViewBase 平滑滚动

首先解释下标题的 ListViewBase 是什么鬼。ListViewBase 我们可以查阅 MSDN 文档:https://msdn.microsoft.com/zh-cn/library/windows.ui.xaml.controls.listviewbase.aspx 得知,ListViewBase 是 ListView 和 GridView 的基类(ListView 和 GridView 则为常用的数据展示控件之一)。而本文的主要目的就是实现 ListView 和 GridView 的平滑滚动,因此我将标题写成“实现 ListViewBase 平滑滚动”而不是“实现 ListView 和 GridView 平滑滚动”(实际上本文适用于任何继承自 ListViewBase 的控件)。

首先我们先复习一下怎么滚动到 ListViewBase 的某一个 item。

在 ListViewBase 类中,有一个方法叫做 ScrollIntoView。这个方法有两个重载,我们看复杂一点,有两个参数的这个:

//
// 摘要:
//     滚动列表,以将指定数据项移入具有指定对齐方式的视图中。
//
// 参数:
//   item:
//     要在视图中显示的数据项。
//
//   alignment:
//     指定项是使用 Default 还是 Leading 对齐方式的枚举值。
[Overload("ScrollIntoViewWithAlignment")]
 public void ScrollIntoView(System.Object item, ScrollIntoViewAlignment alignment);

第一个参数就是我们需要滚动到当前可视区域的 item,而第二个参数,Default 是指让其滚动到当前可视区域即可,Leading 则是指让其滚动到当前可视区域的顶部。

但是比较遗憾的是,这个方法一执行(?)立马滚动到目标 item 了,完全不带一丁点动画效果(后文你会了解到内部执行仍需很少一段时间,尽管我们肉眼察觉不到)。在这个时代,没有一个好的 UI,怎么能吸引用户呢?因此我们就来研究并实现怎样能让 ListViewBase 平滑滚动到某个 item。

说起滚动的话,我们一定会想到 ScrollBar、ScrollViewer 这类的控件的。而幸运的是,ScrollViewer 有一个方法,叫 ChangeView 是带动画效果的(也可以选择不使用动画效果)。并且 ListView、GridView 内部都是有一个 ScrollViewer 的。那么我们自然而然就想到,是不是可以操作 ListViewBase 内部的这个 ScrollViewer 来实现平滑滚动。

先开始编写代码吧:

public static class ListViewBaseExtensions
{
    public static void ScrollIntoViewSmoothly(this ListViewBase listViewBase, object item, ScrollIntoViewAlignment alignment)
    {
        if (listViewBase == null)
        {
            throw new ArgumentNullException(nameof(listViewBase));
        }

        // GetFirstDescendantOfType 是 WinRTXamlToolkit 中的扩展方法,
        // 寻找该控件在可视树上第一个符合类型的子元素。
        ScrollViewer scrollViewer = listViewBase.GetFirstDescendantOfType<ScrollViewer>();

        // 由于 ScrollViewer 肯定有,因此不做 null 检查判断了。

        scrollViewer.ChangeView(targetHorizontalOffset, targetVerticalOffset, null);
    }
}

然而问题来了,targetHorizontalOffset 和 targetVerticalOffset 我们是不知道的,也就是说,我们不知道目标 item 所在的位置。

尽管我们不知道,但是,ListViewBase 自身的 ScrollIntoView 方法它是知道的,那我们干脆就让它当个跑腿,先执行一次,然后就可以获取目标位置了。

public static class ListViewBaseExtensions
{
    public static void ScrollIntoViewSmoothly(this ListViewBase listViewBase, object item, ScrollIntoViewAlignment alignment)
    {
        if (listViewBase == null)
        {
            throw new ArgumentNullException(nameof(listViewBase));
        }

        // GetFirstDescendantOfType 是 WinRTXamlToolkit 中的扩展方法,
        // 寻找该控件在可视树上第一个符合类型的子元素。
        ScrollViewer scrollViewer = listViewBase.GetFirstDescendantOfType<ScrollViewer>();

        // 由于 ScrollViewer 肯定有,因此不做 null 检查判断了。

        // 记录初始位置,用于 ScrollIntoView 检测目标位置后复原。
        double originHorizontalOffset = scrollViewer.HorizontalOffset;
        double originVerticalOffset = scrollViewer.VerticalOffset;

        // 跑腿。
        listViewBase.ScrollIntoView(item, alignment);

        // 获取目标位置。
        double targetHorizontalOffset = scrollViewer.HorizontalOffset;
        double targetVerticalOffset = scrollViewer.VerticalOffset;

        // scrollViewer.ChangeView(targetHorizontalOffset, targetVerticalOffset, null);
    }
}

然而通过断点检查后,发现 targetHorizontalOffset 和 targetVerticalOffset 并没有发生变化。但是执行过后,ListViewBase 确实发生了滚动,因此我们质疑,是不是 ScrollIntoView 方法在控件内部是以一个异步的形式执行。

这个时候,我们还是想起近乎万能的 LayoutUpdated 事件吧。改写下代码。

public static class ListViewBaseExtensions
{
    public static void ScrollIntoViewSmoothly(this ListViewBase listViewBase, object item, ScrollIntoViewAlignment alignment)
    {
        if (listViewBase == null)
        {
            throw new ArgumentNullException(nameof(listViewBase));
        }

        // GetFirstDescendantOfType 是 WinRTXamlToolkit 中的扩展方法,
        // 寻找该控件在可视树上第一个符合类型的子元素。
        ScrollViewer scrollViewer = listViewBase.GetFirstDescendantOfType<ScrollViewer>();

        // 由于 ScrollViewer 肯定有,因此不做 null 检查判断了。

        // 记录初始位置,用于 ScrollIntoView 检测目标位置后复原。
        double originHorizontalOffset = scrollViewer.HorizontalOffset;
        double originVerticalOffset = scrollViewer.VerticalOffset;

        EventHandler<object> layoutUpdatedHandler = null;
        layoutUpdatedHandler = delegate
        {
            listViewBase.LayoutUpdated -= layoutUpdatedHandler;

            // 获取目标位置。
            double targetHorizontalOffset = scrollViewer.HorizontalOffset;
            double targetVerticalOffset = scrollViewer.VerticalOffset;

            // scrollViewer.ChangeView(targetHorizontalOffset, targetVerticalOffset, null);
        };
        listViewBase.LayoutUpdated += layoutUpdatedHandler;

        // 跑腿。
        listViewBase.ScrollIntoView(item, alignment);
    }
}

这次我们再断点后,发现能够获取目标位置了!!(所以我上面说“内部执行仍需很少一段时间,尽管我们肉眼察觉不到”)

接下来,由于跑腿是已经滚动目标位置了,因此我们需要复原到原来的位置,再滚动到目标位置以实现平滑滚动的动画效果。

public static void ScrollIntoViewSmoothly(this ListViewBase listViewBase, object item, ScrollIntoViewAlignment alignment)
    {
        if (listViewBase == null)
        {
            throw new ArgumentNullException(nameof(listViewBase));
        }

        // GetFirstDescendantOfType 是 WinRTXamlToolkit 中的扩展方法,
        // 寻找该控件在可视树上第一个符合类型的子元素。
        ScrollViewer scrollViewer = listViewBase.GetFirstDescendantOfType<ScrollViewer>();

        // 由于 ScrollViewer 肯定有,因此不做 null 检查判断了。

        // 记录初始位置,用于 ScrollIntoView 检测目标位置后复原。
        double originHorizontalOffset = scrollViewer.HorizontalOffset;
        double originVerticalOffset = scrollViewer.VerticalOffset;

        EventHandler<object> layoutUpdatedHandler = null;
        layoutUpdatedHandler = delegate
        {
            listViewBase.LayoutUpdated -= layoutUpdatedHandler;

            // 获取目标位置。
            double targetHorizontalOffset = scrollViewer.HorizontalOffset;
            double targetVerticalOffset = scrollViewer.VerticalOffset;

            // 复原位置,且不需要使用动画效果。
            scrollViewer.ChangeView(originHorizontalOffset, originVerticalOffset, null, true);

            // 最终目的,带平滑滚动效果滚动到 item。
            scrollViewer.ChangeView(targetHorizontalOffset, targetVerticalOffset, null);
        };
        listViewBase.LayoutUpdated += layoutUpdatedHandler;

        // 跑腿。
        listViewBase.ScrollIntoView(item, alignment);
    }
}

执行之后,然而我们发现还是直接滚动到目标,不带一丁点动画效果。但是,有了上面 ScrollIntoView 的经验后,我们自然而然也可以质疑 ChangeView 方法是不是像 ScrollIntoView 一样,内部也是异步执行的。再改写下:

public static class ListViewBaseExtensions
{
    public static void ScrollIntoViewSmoothly(this ListViewBase listViewBase, object item, ScrollIntoViewAlignment alignment)
    {
        if (listViewBase == null)
        {
            throw new ArgumentNullException(nameof(listViewBase));
        }

        // GetFirstDescendantOfType 是 WinRTXamlToolkit 中的扩展方法,
        // 寻找该控件在可视树上第一个符合类型的子元素。
        ScrollViewer scrollViewer = listViewBase.GetFirstDescendantOfType<ScrollViewer>();

        // 由于 ScrollViewer 肯定有,因此不做 null 检查判断了。

        // 记录初始位置,用于 ScrollIntoView 检测目标位置后复原。
        double originHorizontalOffset = scrollViewer.HorizontalOffset;
        double originVerticalOffset = scrollViewer.VerticalOffset;

        EventHandler<object> layoutUpdatedHandler = null;
        layoutUpdatedHandler = delegate
        {
            listViewBase.LayoutUpdated -= layoutUpdatedHandler;

        // 获取目标位置。
        double targetHorizontalOffset = scrollViewer.HorizontalOffset;
            double targetVerticalOffset = scrollViewer.VerticalOffset;

            EventHandler<ScrollViewerViewChangedEventArgs> scrollHandler = null;
            scrollHandler = delegate
            {
                scrollViewer.ViewChanged -= scrollHandler;

            // 最终目的,带平滑滚动效果滚动到 item。
            scrollViewer.ChangeView(targetHorizontalOffset, targetVerticalOffset, null);
            };
            scrollViewer.ViewChanged += scrollHandler;

        // 复原位置,且不需要使用动画效果。
        scrollViewer.ChangeView(originHorizontalOffset, originVerticalOffset, null, true);

        };
        listViewBase.LayoutUpdated += layoutUpdatedHandler;

        // 跑腿。
        listViewBase.ScrollIntoView(item, alignment);
    }
}

这次我们终于成功了!!!

效果:

最后我们像 ListViewBase 的 ScrollIntoView 方法,加多个只有一个参数的重载吧。

最终代码:

public static class ListViewBaseExtensions
{
    public static void ScrollIntoViewSmoothly(this ListViewBase listViewBase, object item)
    {
        ScrollIntoViewSmoothly(listViewBase, item, ScrollIntoViewAlignment.Default);
    }

    public static void ScrollIntoViewSmoothly(this ListViewBase listViewBase, object item, ScrollIntoViewAlignment alignment)
    {
        if (listViewBase == null)
        {
            throw new ArgumentNullException(nameof(listViewBase));
        }

        // GetFirstDescendantOfType 是 WinRTXamlToolkit 中的扩展方法,
        // 寻找该控件在可视树上第一个符合类型的子元素。
        ScrollViewer scrollViewer = listViewBase.GetFirstDescendantOfType<ScrollViewer>();

        // 由于 ScrollViewer 肯定有,因此不做 null 检查判断了。

        // 记录初始位置,用于 ScrollIntoView 检测目标位置后复原。
        double originHorizontalOffset = scrollViewer.HorizontalOffset;
        double originVerticalOffset = scrollViewer.VerticalOffset;

        EventHandler<object> layoutUpdatedHandler = null;
        layoutUpdatedHandler = delegate
        {
            listViewBase.LayoutUpdated -= layoutUpdatedHandler;

            // 获取目标位置。
            double targetHorizontalOffset = scrollViewer.HorizontalOffset;
            double targetVerticalOffset = scrollViewer.VerticalOffset;

            EventHandler<ScrollViewerViewChangedEventArgs> scrollHandler = null;
            scrollHandler = delegate
            {
                scrollViewer.ViewChanged -= scrollHandler;

                // 最终目的,带平滑滚动效果滚动到 item。
                scrollViewer.ChangeView(targetHorizontalOffset, targetVerticalOffset, null);
            };
            scrollViewer.ViewChanged += scrollHandler;

            // 复原位置,且不需要使用动画效果。
            scrollViewer.ChangeView(originHorizontalOffset, originVerticalOffset, null, true);

        };
        listViewBase.LayoutUpdated += layoutUpdatedHandler;

        // 跑腿。
        listViewBase.ScrollIntoView(item, alignment);
    }
}

最后再附送上 Demo:ListViewBaseScrollSmoothlyDemo.zip



最后的最后,冰天雪地裸体 360 度跪求一份 UWP/WP8.1 相关的工作。(长期有效)

原文地址:https://www.cnblogs.com/lonelyxmas/p/10218114.html

时间: 2024-10-19 16:22:12

【Win10】实现 ListViewBase 平滑滚动的相关文章

Android游戏开发之主角的移动与地图的平滑滚动

人物移动地图的平滑滚动处理 玩过rpg游戏的朋友应该都知道RPG的游戏地图一般都比较大 今天我和大家分享一下在RPG游戏中如何来处理超出手机屏幕大小的游戏地图. 如图所示为程序效果动画图 地图滚动的原理 在本人之前博客的文章中介绍过人物在屏幕中的移动方式,因为之前拼的游戏地图是完全填充整个手机屏幕的,所以无需处理地图的平滑滚动.这篇文章我着重的向 大家介绍一下控制人物移动后地图滚动的处理方式.举个例子 如上图所示 比如人物向右移动,如果地图贴在屏幕左边边界 将先移动人物在地图的坐标,当人物在屏幕

jquery 平滑滚动页面到某个锚点

1 $(document).ready(function() { 2         $("a.topLink").click(function() { 3                 $("html, body").animate({ 4                         scrollTop: $($(this).attr("href")).offset().top + "px" 5            

平滑滚动

//平滑滚动 12.5.24未完 <html>    <style>    *{        padding:0px;        margin:0px;    }        html,body,div{            height:100%;        }    </style>    <body>        <div style='background-color:red'></div>        &l

在WPF中实现平滑滚动

WPF实现滚动条还是比较方便的,只要在控件外围加上ScrollViewer即可,但美中不足的是:滚动的时候没有动画效果.在滚动的时候添加过渡动画能给我们的软件增色不少,例如Office 2013的滚动的时候支持动画看起来就舒服多了. 之前倒是研究过如何实现这个平滑滚动,不过网上的方案大部分大多数如下: 通过VisualTree找到ScrollViewer 在ScrollChanged事件中添加动画 这种方案效果并不好,以为我们的滚动很多时候都是一口气滚动好几格滚轮的,这个时候上一个动画还没有结束

JQuery 实现锚点链接之间的平滑滚动

web开发前端一直用JQuery ,真正接触了才体会到,JQuery 原来比我想象的要强大的多,也可能比我体会到的还要强大的多,特别是兼容性那个好,于是把一些好玩的,酷炫的,能够取代 JS 的.统统给用上了. 从 JQuery 引入今天的正题.用 JQuery 实现锚点链接之间的平滑滚动.曾经介绍过一个用 JS 实现的页面锚点跳转缓冲特效,效果相当不错.能够在同一页面的锚点链接之间实现平滑的滚动,可是 JS 代码相对来说比較冗长.如今好了.仅仅要已经载入了 JQuery.我们就能够用较为简短的代

页面中的平滑滚动——smooth-scroll.js的使用

正常的本页面锚链接跳转的时候跟PPT似的,特别生硬,用户体验非常差. 这时候我们就可以借助smooth-scroll.js这个插件,来实现本页面的平滑的跳转. 1首先,导入必须的JS文件 <script src="js/jquery-1.10.2.js"></script> <script src="js/jquery.smooth-scroll.min.js"></script> <script src=&qu

jQuery写出可调控自定义的平滑滚动效果(锚点跳转动画)

今天朋友,没错,是上次的好基友,在用jQuery.scrollTo.js这个插件的时候总是没反应,而且在函数里console可以输出数据,这点现在仍让我很困惑,难道是scroll版本和我引用的jQUery版本不兼容?我在自己本地搭建了一个小demo也没反应,于是就借助动画写了一个可以自定义滑动的距离和速度调控的demo.供大家参考,欢迎交流更好的办法. 1 <!doctype html> 2 <html lang="en"> 3 <head> 4 &

android开发教程之使用线程实现视图平滑滚动示例

最近一直想做下拉刷新的效果,琢磨了好久,才走到通过onTouch方法把整个视图往下拉的步骤,接下来就是能拉下来,松开手要能滑回去啊.网上看了好久,没有找到详细的下拉刷新的例子,只有自己慢慢琢磨了.昨天和今天,研究了两天,下拉之后回滚回去的效果终于今天做出来了!开心.现在来分享下我的实现方法和一些心得体会吧.我看了网上一个大神的例子,发现是在onTouch里面使用View的scrollTo(int, int)方法,来使整个视图往下滚动的,我尝试了使用setTranslationY()来对视图进行回

javaScript滚动新闻之平滑滚动

<!DOCTYPE HTML> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>平滑滚动</title> </head> <style> <!-- #div1{ width:300px; height:100px; float:left;