看了看原生UWP的ScrollViewer,滑动很流畅(例如 开始菜单),但是WPF自带的ScrollViewer滚动十分生硬..

突发奇想,今天来实现一个流畅滚动的ScrollViewer.

一、目标

查阅网上的实现方法,要么直接重写控件,要么一堆Storyboard..很是无奈,还有些许bug.

主要目标如下:

1.能够流畅地滚动ScrollViewer   要求:有惯性的物理滚动 而不是 生硬的 内容滚动.

2.在使用动画的过程中,避免多个begin导致卡顿

3.好用(? )

二、准备工作

实现以上目标需要到的几件东西:

1.对于Listbox之类的控件需要先讲滚动方式变为 像素滚动  (若没有的话就可以不设置)

<ListBox
VirtualizingPanel.ScrollUnit = "Pixel"
VirtualizingPanel.VirtualizationMode="Recycling"/>

2.选一种动画使用的 缓动函数 这里推荐使用 CubicEase的EaseOut函数 (优点:接近于惯性滚动  对于CPU比较友好)

3.要对ScrollViewer启用滚动动画 需要自己写依赖属性:

public static class ScrollViewerBehavior
{
public static readonly DependencyProperty VerticalOffsetProperty = DependencyProperty.RegisterAttached("VerticalOffset", typeof(double), typeof(ScrollViewerBehavior), new UIPropertyMetadata(0.0, OnVerticalOffsetChanged));
public static void SetVerticalOffset(FrameworkElement target, double value) => target.SetValue(VerticalOffsetProperty, value);
public static double GetVerticalOffset(FrameworkElement target) => (double)target.GetValue(VerticalOffsetProperty);
private static void OnVerticalOffsetChanged(DependencyObject target, DependencyPropertyChangedEventArgs e) => (target as ScrollViewer)?.ScrollToVerticalOffset((double)e.NewValue);
}

三、编码

    //继承ScrollViewer 通过截获MouseWheel事件控制滚动
public class MyScrollViewer : ScrollViewer
{
//记录上一次的滚动位置
private double LastLocation = ;
//重写鼠标滚动事件
protected override void OnMouseWheel(MouseWheelEventArgs e)
{
double WheelChange = e.Delta;
//可以更改一次滚动的距离倍数 (WheelChange可能为正负数!)
double newOffset = LastLocation - (WheelChange * );
//Animation并不会改变真正的VerticalOffset(只是它的依赖属性) 所以将VOffset设置到上一次的滚动位置 (相当于衔接上一个动画)
ScrollToVerticalOffset(LastLocation);
//碰到底部和顶部时的处理
if (newOffset < )
newOffset = ;
if (newOffset > ScrollableHeight)
newOffset = ScrollableHeight; AnimateScroll(newOffset);
LastLocation = newOffset;
//告诉ScrollViewer我们已经完成了滚动
e.Handled = true;
}
private void AnimateScroll(double ToValue)
{
//为了避免重复,先结束掉上一个动画
BeginAnimation(ScrollViewerBehavior.VerticalOffsetProperty, null);
DoubleAnimation Animation = new DoubleAnimation();
Animation.EasingFunction = new CubicEase() { EasingMode = EasingMode.EaseOut };
Animation.From = VerticalOffset;
Animation.To = ToValue;
//动画速度
Animation.Duration = TimeSpan.FromMilliseconds();
//考虑到性能,可以降低动画帧数
//Timeline.SetDesiredFrameRate(Animation, 40);
BeginAnimation(ScrollViewerBehavior.VerticalOffsetProperty, Animation);
}
}

使用方法:直接创建 <MyScrollViewer/>

如果是在ListBox中,可以通过修改模板的方式,把<ScrollViewer/>换成MyScrollViewer即可.

四、关于性能

按照上述方法实现的功能,CPU占用率基本稳定在10% ,但是要疯狂地上下滑动滚轮的话,25%+   但是动画还是很流畅的

以下有几点可以改进的措施:

滚动的CPU占用不只是对偏移量的一个计算,与你滚动的UI画面内容具有很大的关系

例如大量的图片Image   3D对象    复杂的自定义控件 等等 都需要在滚动时计算并绘制。

你可以尝试以下方法:

1).启用ListBox的虚拟化技术

2).对于图像的绘制,可以通过降低其渲染度(可能会损坏图片质量 特别是低清小图 ),对你的图片对象使用:

RenderOptions.SetBitmapScalingMode(控件对象,BitmapScalingMode.LowQuality);

3).就像上文中所说,可以通过降低渲染帧数来控制CPU占用率(可能会又是流畅度)

4).如果能找到更好的缓动函数

5).参阅: 优化控件性能 -WPF |Microsoft Docs

WPF 如何流畅地滚动ScrollViewer 简单实现下的更多相关文章

  1. VS编程,WPF中两个滚动条 ScrollViewer 同步滚动的一种方法

    原文:VS编程,WPF中两个滚动条 ScrollViewer 同步滚动的一种方法 版权声明:我不生产代码,我只是代码的搬运工. https://blog.csdn.net/qq_43307934/ar ...

  2. WPF自定义控件与样式(6)-ScrollViewer与ListBox自定义样式

    一.前言 申明:WPF自定义控件与样式是一个系列文章,前后是有些关联的,但大多是按照由简到繁的顺序逐步发布的等,若有不明白的地方可以参考本系列前面的文章,文末附有部分文章链接. 本文主要内容: Scr ...

  3. 【转】WPF自定义控件与样式(6)-ScrollViewer与ListBox自定义样式

    一.前言 申明:WPF自定义控件与样式是一个系列文章,前后是有些关联的,但大多是按照由简到繁的顺序逐步发布的等. 本文主要内容: ScrollViewer的样式拆解及基本样式定义: ListBox集合 ...

  4. Git使用总结 Asp.net生命周期与Http协议 托管代码与非托管代码的区别 通过IEnumerable接口遍历数据 依赖注入与控制反转 C#多线程——优先级 AutoFac容器初步 C#特性详解 C#特性详解 WPF 可触摸移动的ScrollViewer控件 .NET(C#)能开发出什么样的APP?盘点那些通过Smobiler开发的移动应用

    一,原理 首先,我们要明白Git是什么,它是一个管理工具或软件,用来管理什么的呢?当然是在软件开发过程中管理软件或者文件的不同版本的工具,一些作家也可以用这个管理自己创作的文本文件,由Linus开发的 ...

  5. WPF 超长文本的来回滚动

    原文:WPF 超长文本的来回滚动 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/Vblegend_2013/article/details/8362 ...

  6. 不用splitter控件 简单实现对mfc对话框的分割的方法

    不用splitter控件  简单实现对mfc对话框的分割的方法 直接贴上源代码主要部分吧 这个是基于对话框的工程 进行对话框的分割实现 只是相应了三个消息函数,看一下就会明白的 我空间资源里边有现成的 ...

  7. springmvc springJDBC 简单实训银行账户管理系统

    springmvc springJDBC 简单实训银行账户管理系统 1.简单介绍一下,在校时每周结束都会有一次学习总结,简称“实训”,这次实训内容是spring,因为是最近热门框架,我就先从基础方面开 ...

  8. WPF获取外部EXE图标最简单的方法

    原文:WPF获取外部EXE图标最简单的方法 首先在工程添加对System.Drawing的引用 创建以下方法: public static ImageSource GetIcon(string fil ...

  9. WPF MVVM+EF增删改查 简单示例(二) 1对1 映射

    WPF MVVM+EF增删改查 简单示例(一)实现了对学生信息的管理. 现在需求发生变更,在录入学生资料的时候同时需要录入学生的图片信息,并且一名学生只能有一张图片资料.并可对学生的图片资料进行更新. ...

随机推荐

  1. Django模板之模板变量

    深度查询句点符(.)在模板语言中有特殊的含义. 当模版系统遇到点("."),它将以这样的顺序查询: 字典查询(Dictionary lookup) 属性或方法查询(Attribut ...

  2. python函数总结,你值得拥有

    目录 函数总结 函数定义与结构 函数名的使用 函数的参数 名称空间与作用域 名称空间 作用域 函数嵌套 内置函数(globals( ),locals( )) global+nonlocal 可迭代对象 ...

  3. JQuery动态创建表单并提交

    // 捕捉链接的点击事件 $('#btn').click(function(){ // 取得要提交的参数 var my_val = $.trim($('#ipt').val()); // 取得要提交页 ...

  4. JavaScript中setInterval关闭问题

    这篇博客主要记录下学习中碰到的一些问题(以防忘记). JavaScript中,在setInterval中关闭该定时器,但是此次执行也会完成,用语言描述不太容易,直接看代码: var i=1; var ...

  5. 【Java_Eclipse】Eclipse插件如何卸载?

    本博客我们讲一下对于Eclipse的插件卸载 1.Eclipse中:Window——Install  New  SoftWare 2.找到相应插件卸载即可

  6. Kubernetes学习笔记(四):服务

    服务介绍 服务是一种为一组相同功能的pod提供单一不变接入点的资源.当服务存在时,他的IP和端口不会改变.客户端通过IP和端口建立连接,这些连接会被路由到任何一个pod上.如此,客户端不需要知道每个单 ...

  7. vue中 vue-awesome-swiper的使用

    在页面开发中,经常会碰到需要轮播,滑动等需求,特别是多元素滑动,此时,要么自己写,要么网上找轮子,不过自己写,其实还是有点难度的,一般就是网上找写好的库,本文就是针对vue-awesome-swipe ...

  8. 线程池 & 线程调度

    线程池1. 第四种获取线程的方法:线程池,一个 ExecutorService,它使用可能的几个池线程之 一执行每个提交的任务, 通常使用 Executors 工厂方法配置. 2. 线程池可以解决两个 ...

  9. git可视化打包更新文件

    每次当我们修改了项目代码的时候,总需要理出来一个更新包发给测试部门或者给客户更新.当我们一次的修改的代码多了之后,我们就很难按照文件夹一个一个的去提交出来哪些的更新的,哪些是未修改的.于是乎就在度娘能 ...

  10. Window10:不能建立到远程计算机的连接,你可能需要更改此连接的网络设置。

    一,右键我的电脑点击管理. 二,在系统工具中找到设备管理,在设备管理中找到网络适配器. 三,在网络适配器中找到WAN Miniport(IP) 四,找到WAN Miniport(IP)右键放心卸载,作 ...