1. 控件概述

WPF原生ScrollViewer在触控设备上的滚动体验不佳,本控件提供了以下增强功能:

  • 流畅的触控滚动:支持单指滑动操作
  • 惯性滚动效果:模拟自然物理滚动
  • 完全可定制:可调整滚动参数和视觉效果
  • 高性能:优化触控事件处理

技术特性对比

特性 原生ScrollViewer 本控件
触控支持 有限 完全支持
惯性滚动
性能优化 一般 高度优化
定制能力 有限 完全可定制

3. 完整控件代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media.Animation;

namespace WPF.Controls
{
/// <summary>
/// 支持触控手势的ScrollViewer控件
/// 功能:提供流畅的触控滚动体验,支持惯性滚动效果
/// </summary>
public class TouchableScrollViewer : ScrollViewer
{
#region 依赖属性

public static readonly DependencyProperty IsNoTouchProperty =
DependencyProperty.Register(
"IsNoTouch",
typeof(bool),
typeof(TouchableScrollViewer),
new PropertyMetadata(false));

public static readonly DependencyProperty ScrollSensitivityProperty =
DependencyProperty.Register(
"ScrollSensitivity",
typeof(double),
typeof(TouchableScrollViewer),
new PropertyMetadata(1.0));

public static readonly DependencyProperty EnableInertiaProperty =
DependencyProperty.Register(
"EnableInertia",
typeof(bool),
typeof(TouchableScrollViewer),
new PropertyMetadata(true));

#endregion

#region 属性

/// <summary>
/// 是否禁用触控功能
/// </summary>
public bool IsNoTouch
{
get => (bool)GetValue(IsNoTouchProperty);
set => SetValue(IsNoTouchProperty, value);
}

/// <summary>
/// 滚动灵敏度系数(0.5-2.0)
/// </summary>
public double ScrollSensitivity
{
get => (double)GetValue(ScrollSensitivityProperty);
set => SetValue(ScrollSensitivityProperty, Math.Max(0.5, Math.Min(2.0, value)));
}

/// <summary>
/// 是否启用惯性滚动
/// </summary>
public bool EnableInertia
{
get => (bool)GetValue(EnableInertiaProperty);
set => SetValue(EnableInertiaProperty, value);
}

#endregion

private Point _startPosition;
private double _startVerticalOffset;
private double _startHorizontalOffset;
private DateTime _lastTouchTime;
private double _lastSpeed;

public TouchableScrollViewer()
{
TouchDown += OnTouchDown;
TouchUp += OnTouchUp;
Loaded += OnLoaded;
}

private void OnLoaded(object sender, RoutedEventArgs e)
{
// 初始化触控设置
PanningMode = PanningMode.Both;
PanningDeceleration = 0.001;
}

private void OnTouchDown(object sender, TouchEventArgs e)
{
if (IsNoTouch) return;

TouchMove -= OnTouchMove;
TouchMove += OnTouchMove;

_startVerticalOffset = VerticalOffset;
_startHorizontalOffset = HorizontalOffset;
_startPosition = e.GetTouchPoint(this).Position;
_lastTouchTime = DateTime.Now;
}

private void OnTouchUp(object sender, TouchEventArgs e)
{
TouchMove -= OnTouchMove;

if (EnableInertia && _lastSpeed > 10)
{
// 惯性滚动实现
var animation = new DoubleAnimation
{
From = VerticalOffset,
To = VerticalOffset + _lastSpeed * 0.5,
Duration = TimeSpan.FromMilliseconds(500),
EasingFunction = new CubicEase { EasingMode = EasingMode.EaseOut }
};
BeginAnimation(VerticalOffsetProperty, animation);
}
}

private void OnTouchMove(object sender, TouchEventArgs e)
{
if (IsNoTouch) return;

var endPoint = e.GetTouchPoint(this);
var diffY = (endPoint.Position.Y - _startPosition.Y) * ScrollSensitivity;
var diffX = (endPoint.Position.X - _startPosition.X) * ScrollSensitivity;

ScrollToVerticalOffset(_startVerticalOffset - diffY);
ScrollToHorizontalOffset(_startHorizontalOffset - diffX);

// 计算滚动速度(像素/毫秒)
var now = DateTime.Now;
var elapsed = (now - _lastTouchTime).TotalMilliseconds;
_lastSpeed = Math.Abs(diffY) / elapsed;
_lastTouchTime = now;
}
}
}

4. 使用示例

基本使用

1
2
3
4
5
<local:TouchableScrollViewer Width="300" Height="400">
<StackPanel>
<!-- 内容元素 -->
</StackPanel>
</local:TouchableScrollViewer>

自定义参数

1
2
3
4
5
6
<local:TouchableScrollViewer 
IsNoTouch="False"
ScrollSensitivity="1.2"
EnableInertia="True">
<!-- 内容 -->
</local:TouchableScrollViewer>

数据绑定

1
2
3
4
5
<local:TouchableScrollViewer
IsNoTouch="{Binding IsScrollLocked}"
ScrollSensitivity="{Binding ScrollSensitivity}">
<!-- 内容 -->
</local:TouchableScrollViewer>