一、触控时代的滚动工具:QScroller类设计介绍
1.1 从机械滚轮到数字惯性
在触控设备普及前,滚动操作如同老式打字机的滚轴,只能通过鼠标滚轮或滚动条进行离散式控制。QScroller的出现如同给数字界面装上了"惯性飞轮",通过模拟物理上的动量守恒定律,让滚动操作具备以下特性:
- 速度传递:滑动速度决定滚动距离;
- 动态衰减:摩擦系数影响停止时间;
- 边界弹性:类似橡皮筋的越界回弹效果;
1.2 类关系拓扑图
QScroller ────┬── QScrollerProperties
├── QScrollEvent
└── QScrollPrepareEvent
QScroller作为控制器,通过QScrollerProperties配置物理参数,生成QScrollEvent事件以驱动界面的刷新。
二、核心类深度解析
2.1 QScroller类:滚动引擎
关键方法说明:
// 启用控件手势支持(核心入口)
static QScroller *QScroller::grabGesture(
QObject *target,
QScroller::GestureType gestureType = TouchGesture
);
//检查指定的目标对象是否已经关联了 QScroller 实例
static bool hasScroller(QObject *target) ;
//使指定的目标对象停止捕获指定类型的滚动手势
static void ungrabGesture(QObject *target,
QScroller::ScrollerGestureType gestureType);
// 获取当前滚动状态
QScroller::State state() const;
// 滚动到指定区域(带动画)
void scrollTo(const QVector2D &pos,
qreal scrollTime = 0);
// 确保子控件可见(自动计算滚动路径)
void ensureVisible(const QRectF &rect,
qreal xmargin = 0.1,
qreal ymargin = 0.1);
//设置滚动器的属性,如滚动速度、摩擦力、弹性等
void setScrollerProperties(const QScrollerProperties &properties);
//启动滚动器,开始滚动操作
void start();
//停止滚动器,终止滚动操作
void stop();
//设置水平方向的捕捉位置。当滚动到这些位置时,滚动器会自动对齐到这些位置。
void setSnapPositionsX(const QList<qreal> &positions);
//设置垂直方向的捕捉位置。当滚动到这些位置时,滚动器会自动对齐到这些位置。
void setSnapPositionsY(const QList<qreal> &positions);
手势类型枚举:
enum GestureType {
TouchGesture, // 触摸手势
LeftMouseButtonGesture, // 左键拖拽
RightMouseButtonGesture // 右键拖拽(特殊场景)
};
2.2 QScrollerProperties:设置和调整配置参数
这个类就像汽车的"悬挂调校系统"调节汽车引擎一样,专门调整设置QScroller类的配置参数,包含几十个可配置参数,主要有:
QScrollerProperties properties;
//设置滚轮滚动过程中画面的帧率,帧率越高看着越舒服,帧率越低画面越跳动
properties.setScrollMetric(QScrollerProperties::FrameRate,QScrollerProperties::Fps30);
//设置平滑速度,当滑动完手离开屏幕后,进行平滑滑动的速度,此值应介于0和1之间。值越小,速度越慢。但我试过实际没太明显的区别
properties.setScrollMetric(QScrollerProperties::DragVelocitySmoothingFactor,0.3);
//设置鼠标释放后滚动到停止时的运动曲线,参数为QEasingCurve类型,不能设置为QEasingCurve::Type类型,不会隐式转换
properties.setScrollMetric(QScrollerProperties::ScrollingCurve,QEasingCurve::OutQuad);
//设置移动阀值(按下后需要移动最少距离后,触发滑动),用来避免误操作
properties.setScrollMetric(QScrollerProperties::DragStartDistance,0.003);
//设置减速因子,值越大,减速越快,进而会影响点击释放后滚动的距离。对于大多数类型,该值应在0.1到2.0的范围内
properties.setScrollMetric(QScrollerProperties::DecelerationFactor,0.4);
//设置当运动方向与某一个轴的角度小于该设定值(0~1)时,则限定只有该轴方向的滚动,一般就用0.5,两个轴都是,不然要么区域重叠,要么区域漏掉
properties.setScrollMetric(QScrollerProperties::AxisLockThreshold,0.5);
//自动滚动过程中,鼠标点击操作会停止当前滚动,当速度大于该设定(m/s)时,鼠标事件不会传递给目标即不会停止滚动
properties.setScrollMetric(QScrollerProperties::MaximumClickThroughVelocity,0.5);
//与AcceleratingFlickSpeedupFactor配合使用。设置时间及加速因子
properties.setScrollMetric(QScrollerProperties::AcceleratingFlickMaximumTime ,3);
//与AcceleratingFlickMaximumTime配合使用,应>=1
properties.setScrollMetric(QScrollerProperties::AcceleratingFlickSpeedupFactor,2);
//设置自动滑动的最小速度,m/s,如果手势的速度小于该速度,则不会触发自动滚动,所以可以设置得小一些,防止手指轻微移动引起屏幕滚动
properties.setScrollMetric(QScrollerProperties::MinimumVelocity,0.2);
//设置自动滚动能达到得最大速度,m/s
properties.setScrollMetric(QScrollerProperties::MaximumVelocity,0.2);
//设置当拖拽过量时,释放后位置恢复所用时间(s)
properties.setScrollMetric(QScrollerProperties::OvershootScrollTime,0.2);
//设置滚动曲线的时间因子。设置滚动的时长,值越小,滚动时间越长
properties.setScrollMetric(QScrollerProperties::SnapTime,0.2);
//设置过量拖拽的距离占页面的比例,0~1,比如设置0.2,过量拖拽垂直最多移动高度的1/5
properties.setScrollMetric(QScrollerProperties::OvershootDragDistanceFactor,0.2);
//设置垂直向允许过量拖拽的策略,可以设置滚动条出现时开启、始终关闭、始终开启三种策略
properties.setScrollMetric(QScrollerProperties::VerticalOvershootPolicy,QScrollerProperties::OvershootWhenScrollable);
//设置过量拖拽的移动距离与鼠标移动距离的比例,0~1,值越小表现出的阻塞感越强
properties.setScrollMetric(QScrollerProperties::OvershootDragResistanceFactor,0.2);
//设置自动滚动时允许的过量滚动距离比例
properties.setScrollMetric(QScrollerProperties::OvershootScrollDistanceFactor,0.2);
//设置鼠标事件延迟时间,单位s。
properties.setScrollMetric(QScrollerProperties::MousePressEventDelay,1);
QScroller::scroller(ui->scrollArea)->setScrollerProperties(properties);
三、实现原理揭秘
3.1 事件处理流水线
sequenceDiagram
participant User
participant QScroller
participant Widget
User->>QScroller: 触摸/鼠标按下
QScroller->>Widget: 发送ScrollPrepare事件
Widget->>QScroller: 返回内容尺寸等信息
loop 每帧更新
QScroller->>QScroller: 计算物理运动参数
QScroller->>Widget: 发送ScrollEvent
Widget->>Widget: 更新显示位置
end
User->>QScroller: 释放操作
QScroller->>Widget: 触发惯性滚动
3.2 物理模型公式
滚动位置计算采用改进型牛顿运动方程:
当前位置 = 初始位置 + 初速度 * t - 0.5 * 摩擦系数 * t²
其中摩擦系数通过QScrollerProperties::DecelerationFactor动态调整
四、实用代码示例
4.1 基础滑动实现
// 为QListWidget启用触控滑动
QListWidget *list = new QListWidget(this);
QScroller *scroller = QScroller::scroller(list->viewport());
QScroller::grabGesture(list->viewport(), QScroller::TouchGesture);
// 配置滑动参数(类似手机APP的流畅感)
QScrollerProperties prop = scroller->scrollerProperties();
prop.setScrollMetric(QScrollerProperties::DragVelocitySmoothingFactor, 0.6);
prop.setScrollMetric(QScrollerProperties::OvershootScrollDistanceFactor, 0.3);
scroller->setScrollerProperties(prop);
4.2 高级分页滚动
// 相册浏览分页效果
QScrollArea *gallery = new QScrollArea(this);
QScroller::grabGesture(gallery, QScroller::LeftMouseButtonGesture);
QScrollerProperties prop;
prop.setScrollMetric(QScrollerProperties::SnapPositionRatio, 1.0); // 整页吸附
prop.setScrollMetric(QScrollerProperties::SnapTime, 0.5); // 过渡动画时长
QScroller::scroller(gallery)->setScrollerProperties(prop);
五、性能优化指南
- 渲染优化:
// 启用像素级滚动(避免跳跃感)
listWidget->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
- 内存管理:
// 及时释放不再使用的滚动器
QScroller::ungrabGesture(scrollArea); // 类似断开刹车系统
- 动态调参技巧:
// 根据设备类型切换参数
#ifdef Q_OS_ANDROID
prop.setScrollMetric(...); // 移动端优化参数
#else
prop.setScrollMetric(...); // 桌面端参数
#endif
结语:滚动交互的未来
随着Qt6对QScroller的持续优化,已经未来产品更趋向于实际流畅感的需求,作为开发使用人员,这几个方面的优化值得我们关注使用:
- 多指操作与3D滚动集成
- 基于AI的动态参数调优
- 与Vulkan渲染引擎的深度结合