WPF 控件库——带有惯性的ScrollViewer
WPF 控件库系列博文地址:
一、先看看效果
二、原理
虽然效果很简单,但是网上的一些资料涉及的代码量非常可观,而且效果也不是很理想,滚动的时候没有一个顺滑感。我这里提供的源码一共120多行,就能实现上图的效果。
本质上我们只要接管ScrollViewer的滚动逻辑,并且把这个逻辑替换成带有惯性的即可,那么如何去接管呢?这里的关键是先屏蔽ScrollViewer的鼠标滚轮事件:
protected override void OnMouseWheel(MouseWheelEventArgs e)
{
e.Handled = true;
}
这样一来,ScrollViewer就不会响应滚轮事件了,我们就在这里做文章。首先我们给这个ScrollViewer添加一个属性 IsEnableInertia ,用来控制是否使用惯性,因为萝卜青菜各有所爱,不要想着强制所有人使用惯性,所以滚轮响应方法变为:
protected override void OnMouseWheel(MouseWheelEventArgs e)
{
if (!IsEnableInertia)
{
base.OnMouseWheel(e);
return;
}
e.Handled = true;
}
控制ScrollViewer的垂直滚动可以使用 ScrollViewer.ScrollToVerticalOffset ,横向也一样。为什么不能用 VerticalOffset ?因为 VerticalOffset 在注册的时候就说明了是只读的:
private static readonly DependencyPropertyKey VerticalOffsetPropertyKey = DependencyProperty.RegisterReadOnly(nameof (VerticalOffset), typeof (double), typeof (ScrollViewer), (PropertyMetadata) new FrameworkPropertyMetadata((object) 0.0)); public static readonly DependencyProperty VerticalOffsetProperty = ScrollViewer.VerticalOffsetPropertyKey.DependencyProperty;
好了,接下来就是怎么在滚轮响应方法中实现惯性运动了,也就是一种减速运动。想到这儿,熟悉动画的博友很快就知道要用WPF的动画来实现了,默认的动画都是一次线性的,要有惯性效果就得用缓动函数,WPF的缓动函数有很多,而 CubicEase 非常适合用来做惯性,它的描述图如下:
图中,横轴表示时间,纵轴表示运动距离。很明显,中间的 EaseOut 模式就是我们想要的。到了这里思路就清晰了,我们可以定义一个属性 CurrentVerticalOffset ,我们会在它上面实现动画,在它的值回调函数中调用 ScrollViewer.ScrollToVerticalOffset 来更新ScrollViewer的滚动位置。当然我们还需要一个私有字段 _totalVerticalOffset ,这个是用来存放ScrollViewer滚动目标位置的,滚轮向下滚动一个单位我们就给它减去一次 e.Delta ,这里的e是滚轮响应方法传进来的参数,每次给它赋值之后,就可以在 CurrentVerticalOffset 上执行动画了: BeginAnimation(CurrentVerticalOffsetProperty, animation) ,需要特别注意的是,当一个依赖属性用了动画改变后,再对其赋值则不会生效,原因是在一个动画到达活动期的终点后,时间线默认会保持其进度,直到其父级的活动期和保持期结束为止。如果想在动画结束后还可以手动更改依赖属性的值,则需要把 FillBehavior 设置为Stop。不过这样又会出现一个问题,一旦动画结束,这个依赖属性又会恢复初始值,所以还要给这个动画订阅一个 Completed 事件,在事件响应方法中为 CurrentVerticalOffset 给定目标值,也就是 _totalVerticalOffset 。
最后还有一个冲突问题,当手动拖动滑块或者当用上下文菜单改变滚动条位置时是不能用动画的,因为这时候没有触发 OnMouseWheel ,没关系,这正是我们想要的,但是如果再次触发 OnMouseWheel 就有问题了,因为手动触发滚动的时候我们没有给 CurrentVerticalOffset 和 _totalVerticalOffset 赋值( CurrentVerticalOffset 和 _totalVerticalOffset 只在 OnMouseWheel 中赋值),所以在用动画执行滚动操作前要先判断一下是否需要先更新一下它们俩,如何判断?我们可以用一个私有字段 _isRunning 来维护状态,每当动画开始就给它赋值true,结束则赋值false。这样一来,当 _isRunning = false 时,说明在调用 OnMouseWheel 前,动画已经结束,用户可能已经手动改变了滚动条位置(也可能没有,但这并不影响),所以就要给之前俩兄弟更新一下值了。
因为常见的惯性滚动以垂直方向居多,所以我没有写水平方向的逻辑,但也很容易扩展,有兴趣的博友可以下载源代码自己研究。
三、源码
本文所讨论的控件源码已经在github开源:https://github.com/NaBian/HandyControl
WPF 控件库——带有惯性的ScrollViewer的更多相关文章
- WPF 控件库——带有惯性的ScrollViewer*(转)
转:https://blog.csdn.net/ahilll/article/details/82418892 一.先看看效果 二.原理 虽然效果很简单,但是网上的一些资料涉及的代码量非常可观,而且效 ...
- WPF 控件库——仿制Windows10的进度条
WPF 控件库系列博文地址: WPF 控件库——仿制Chrome的ColorPicker WPF 控件库——仿制Windows10的进度条 WPF 控件库——轮播控件 WPF 控件库——带有惯性的Sc ...
- WPF 控件库——仿制Chrome的ColorPicker
WPF 控件库系列博文地址: WPF 控件库——仿制Chrome的ColorPicker WPF 控件库——仿制Windows10的进度条 WPF 控件库——轮播控件 WPF 控件库——带有惯性的Sc ...
- WPF 控件库——轮播控件
WPF 控件库系列博文地址: WPF 控件库——仿制Chrome的ColorPicker WPF 控件库——仿制Windows10的进度条 WPF 控件库——轮播控件 WPF 控件库——带有惯性的Sc ...
- WPF 控件库——可拖动选项卡的TabControl
WPF 控件库系列博文地址: WPF 控件库——仿制Chrome的ColorPicker WPF 控件库——仿制Windows10的进度条 WPF 控件库——轮播控件 WPF 控件库——带有惯性的Sc ...
- 《Dotnet9》系列-开源C# WPF控件库3《HandyControl》强力推荐
大家好,我是Dotnet9小编,一个从事dotnet开发8年+的程序员.我最近开始写dotnet分享文章,希望能让更多人看到dotnet的发展,了解更多dotnet技术,帮助dotnet程序员应用do ...
- 我的WPF控件库——KAN.WPF.XCtrl(141105)
自己开发的WPF控件库,只是初版,有扩展的Button,TextBox,Window.详细参见前几篇博文. WPF自定义控件(一)——Button:http://www.cnblogs.com/Qin ...
- 反爬虫:利用ASP.NET MVC的Filter和缓存(入坑出坑) C#中缓存的使用 C#操作redis WPF 控件库——可拖动选项卡的TabControl 【Bootstrap系列】详解Bootstrap-table AutoFac event 和delegate的分别 常见的异步方式async 和 await C# Task用法 c#源码的执行过程
反爬虫:利用ASP.NET MVC的Filter和缓存(入坑出坑) 背景介绍: 为了平衡社区成员的贡献和索取,一起帮引入了帮帮币.当用户积分(帮帮点)达到一定数额之后,就会“掉落”一定数量的“帮帮 ...
- 国内开源C# WPF控件库Panuon.UI.Silver推荐
国内优秀的WPF开源控件库,Panuon.UI的优化版本.一个漂亮的.使用样式与附加属性的WPF UI控件库,值得向大家推荐使用与学习. 今天站长(Dotnet9,站长网址:https://dotne ...
随机推荐
- TELNET协议规范
ARPA Internet上的主机被要求采用并实现此标准. 介绍 TELNET Protocol的目的是提供一个相对通用的,双向的,面向八位字节的通信方法.它主要的目标是允许接口终端设备的标准方法和面 ...
- 第九章 Servlet工作原理解析
9.1 从Servlet容器说起 Servlet容器:Jetty, Tomcat等. 这里以Tomcat为例, 真正管理Servlet的容器是Context容器,一个Context对应一个WE ...
- Struts2接受页面传值过程中出现input的问题
其实我在使用Struts2的时候,遇到要求返回input的时候不算少.一般我们在使用Struts2的时候,都会返回SUCCESS/ERROR,或者是NONE以到Strtuts的配置文件中再进行相应的处 ...
- http协议Keep-Alive
Keep-Alive 是什么? 概观 默认情况下,HTTP链接通常在请求完成之后关闭.这意味着服务端在完成响应的交付之后便关闭了TCP链接.为了让链接保持打开,来满足多请求,可以使用keep-aliv ...
- 解决maven构建工程错误:Failure to transfer org.apache.maven.plugins:maven-jar-plugin:pom:2.4 from错误
问题描述: mvn archetype:generate -DgroupId=com.mycompany.app -DartifactId=myapp -DarchetypeArtifactId=ma ...
- Pandas统计计算和描述
Pandas统计计算和描述 示例代码: import numpy as np import pandas as pd df_obj = pd.DataFrame(np.random.randn(5,4 ...
- jquery添加和删除多个同名的input输入框
<script type="text/javascript"> function del(obj){ $(obj).parents("li").re ...
- Halcon学习(八)文本操作
标签: 学习 杂谈 分类: halcon学习 1.open_file( : : FileName, FileType : FileHandle) FileType: 'output':创建文本 ‘ap ...
- vue中使用markdown富文本,并在html页面中展示
想给自己的后台增加一个markdown编辑器,下面记录下引用的步骤 引入组件mavon-editor 官网地址:https://github.com/hinesboy/mavonEditor // 插 ...
- Path expected for join!的解决办法
Path expected for join! [SELECT count(*) FROM cn.com.jsoft.entities.TDSysnphoto p left join TDSysnot ...