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 { 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 属性
public bool IsNoTouch { get => (bool)GetValue(IsNoTouchProperty); set => SetValue(IsNoTouchProperty, value); }
public double ScrollSensitivity { get => (double)GetValue(ScrollSensitivityProperty); set => SetValue(ScrollSensitivityProperty, Math.Max(0.5, Math.Min(2.0, value))); }
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>
|