cpp
#include <QApplication>
#include <QListWidget>
#include <QScroller>
#include <QScrollerProperties>
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
// 创建列表控件并添加示例项
QListWidget listWidget;
for (int i = 0; i < 100; ++i) {
listWidget.addItem(QString("文件 %1").arg(i));
}
listWidget.resize(400, 300);
listWidget.show();
// 启用惯性滚动:触摸手势
// QScroller::grabGesture(listWidget.viewport(), QScroller::TouchGesture);
// 或者启用鼠标左键拖动惯性
QScroller::grabGesture(listWidget.viewport(), QScroller::LeftMouseButtonGesture);
// 获取Scroller对象并设置参数
QScroller *scroller = QScroller::scroller(listWidget.viewport());
QScrollerProperties properties = scroller->scrollerProperties();
// 调整减速度因子(0~1,值越小惯性越长)
properties.setScrollMetric(QScrollerProperties::DecelerationFactor, 0.05);
// 设置最小拖动触发距离(防止误触)
properties.setScrollMetric(QScrollerProperties::DragStartDistance, 0.001);
scroller->setScrollerProperties(properties);
return app.exec();
}
示例二:
cpp
// inertialscroller.h
#ifndef INERTIALSCROLLER_H
#define INERTIALSCROLLER_H
#include <QAbstractItemView>
#include <QEvent>
#include <QMouseEvent>
#include <QObject>
#include <QPointF>
#include <QScrollBar>
#include <QTableView>
#include <QTime>
#include <QTimer>
#include <QWheelEvent>
/**
* @brief 惯性滚动管理器类
*
* 为QTableView或其他QAbstractItemView子类提供惯性滚动功能。
* 追踪鼠标拖拽事件并在释放后应用速度衰减算法模拟惯性滚动效果。
*/
class InertialScroller : public QObject
{
Q_OBJECT
public:
/**
* @brief 构造函数
* @param view 需要添加惯性滚动功能的视图
* @param parent 父对象
*/
explicit InertialScroller(QAbstractItemView *view, QObject *parent = nullptr);
/**
* @brief 析构函数
*/
~InertialScroller();
/**
* @brief 设置衰减系数,控制惯性滚动的衰减速度
* @param factor 衰减系数(0.0-1.0),越小衰减越快
*/
void setDecelerationFactor(qreal factor);
/**
* @brief 设置惯性滚动的最大初始速度
* @param speed 最大速度(像素/秒)
*/
void setMaxSpeed(qreal speed);
/**
* @brief 设置停止惯性滚动的最小速度阈值
* @param threshold 速度阈值(像素/秒)
*/
void setStopThreshold(qreal threshold);
/**
* @brief 设置滚轮惯性滚动的强度系数
* @param factor 强度系数,数值越大惯性效果越强
*/
void setWheelSpeedFactor(qreal factor);
/**
* @brief 启用或禁用滚轮惯性滚动
* @param enable 是否启用
*/
void setWheelInertiaEnabled(bool enable);
protected:
/**
* @brief 事件过滤器
* 拦截视图的鼠标事件处理惯性滚动
*/
bool eventFilter(QObject *watched, QEvent *event) override;
private:
QAbstractItemView *m_view; // 关联的视图
QTimer m_timer; // 惯性滚动计时器
QTime m_lastTime; // 上次事件时间
QPointF m_lastPos; // 上次鼠标位置
QPointF m_velocity; // 当前速度(x和y方向)
bool m_isPressed = false; // 鼠标是否按下
bool m_isScrolling = false; // 是否正在滚动
qreal m_deceleration = 0.95; // 衰减系数(0.0-1.0)
qreal m_maxSpeed = 2000.0; // 最大速度(像素/秒)
qreal m_stopThreshold = 10.0; // 停止阈值(像素/秒)
QVector<QPointF> m_positions; // 最近的鼠标位置记录
QVector<qint64> m_timestamps; // 最近的鼠标位置时间戳
qreal m_wheelSpeedFactor = 15.0; // 滚轮速度系数
bool m_wheelInertiaEnabled = true; // 是否启用滚轮惯性
private slots:
/**
* @brief 执行惯性滚动步骤
*/
void scrollStep();
/**
* @brief 开始惯性滚动
*/
void startScrolling(const QPointF &velocity);
/**
* @brief 停止惯性滚动
*/
void stopScrolling();
};
#endif // INERTIALSCROLLER_H
cpp
// inertialscroller.cpp
#include <QDateTime>
#include <QDebug>
#include <QtMath>
#include "inertialscroller.h"
InertialScroller::InertialScroller(QAbstractItemView *view, QObject *parent) : QObject(parent), m_view(view)
{
// 安装事件过滤器
m_view->viewport()->installEventFilter(this);
// 初始化计时器
m_timer.setInterval(16); // 约60FPS
connect(&m_timer, &QTimer::timeout, this, &InertialScroller::scrollStep);
// 初始化历史记录容器
m_positions.reserve(10);
m_timestamps.reserve(10);
}
InertialScroller::~InertialScroller()
{
if(m_view && m_view->viewport())
{
m_view->viewport()->removeEventFilter(this);
}
m_timer.stop();
}
void InertialScroller::setDecelerationFactor(qreal factor)
{
// 确保值在有效范围内
m_deceleration = qBound(0.1, factor, 0.99);
}
void InertialScroller::setMaxSpeed(qreal speed)
{
m_maxSpeed = qMax(1.0, speed);
}
void InertialScroller::setStopThreshold(qreal threshold)
{
m_stopThreshold = qMax(1.0, threshold);
}
void InertialScroller::setWheelSpeedFactor(qreal factor)
{
m_wheelSpeedFactor = qMax(1.0, factor);
}
void InertialScroller::setWheelInertiaEnabled(bool enable)
{
m_wheelInertiaEnabled = enable;
}
bool InertialScroller::eventFilter(QObject *watched, QEvent *event)
{
if(watched != m_view->viewport())
return false;
switch(event->type())
{
case QEvent::MouseButtonPress: {
QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event);
if(mouseEvent->button() == Qt::LeftButton)
{
stopScrolling();
m_isPressed = true;
m_lastPos = mouseEvent->pos();
m_lastTime.start();
// 清空历史记录
m_positions.clear();
m_timestamps.clear();
// 记录初始位置和时间
m_positions.append(mouseEvent->pos());
m_timestamps.append(QDateTime::currentMSecsSinceEpoch());
}
break;
}
case QEvent::MouseMove: {
if(m_isPressed)
{
QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event);
QPointF currentPos = mouseEvent->pos();
// 计算移动距离
QPointF delta = m_lastPos - currentPos;
// 添加到历史记录
m_positions.append(currentPos);
m_timestamps.append(QDateTime::currentMSecsSinceEpoch());
// 仅保留最近的5个记录点
while(m_positions.size() > 5)
{
m_positions.removeFirst();
m_timestamps.removeFirst();
}
// 滚动视图
QScrollBar *vScrollBar = m_view->verticalScrollBar();
QScrollBar *hScrollBar = m_view->horizontalScrollBar();
if(vScrollBar && vScrollBar->isVisible())
{
vScrollBar->setValue(vScrollBar->value() + delta.y());
}
if(hScrollBar && hScrollBar->isVisible())
{
hScrollBar->setValue(hScrollBar->value() + delta.x());
}
m_lastPos = currentPos;
}
break;
}
case QEvent::MouseButtonRelease: {
if(m_isPressed)
{
QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event);
if(mouseEvent->button() == Qt::LeftButton)
{
m_isPressed = false;
// 如果有足够的历史数据来计算速度
if(m_positions.size() >= 2)
{
// 使用最后几个点的平均速度
QPointF totalVelocity(0, 0);
int count = 0;
for(int i = 1; i < m_positions.size(); ++i)
{
qint64 timeDelta = m_timestamps[i] - m_timestamps[i - 1];
if(timeDelta > 0)
{
QPointF posDelta = m_positions[i - 1] - m_positions[i];
// 速度 = 距离/时间,单位为像素/秒
QPointF velocity = posDelta * (1000.0 / timeDelta);
totalVelocity += velocity;
count++;
}
}
if(count > 0)
{
QPointF avgVelocity = totalVelocity / count;
// 限制最大速度
qreal speedX = qBound(-m_maxSpeed, avgVelocity.x(), m_maxSpeed);
qreal speedY = qBound(-m_maxSpeed, avgVelocity.y(), m_maxSpeed);
// 如果速度足够大,启动惯性滚动
QPointF finalVelocity(speedX, speedY);
qreal speed = qSqrt(speedX * speedX + speedY * speedY);
if(speed > m_stopThreshold)
{
startScrolling(finalVelocity);
}
}
}
}
}
break;
}
case QEvent::Wheel: {
// 如果滚轮惯性被禁用,不拦截事件
if(!m_wheelInertiaEnabled)
{
stopScrolling();
return false;
}
// 处理鼠标滚轮事件产生惯性滚动
QWheelEvent *wheelEvent = static_cast<QWheelEvent *>(event);
// 停止当前的惯性滚动
stopScrolling();
// 计算滚轮滚动的速度和方向
QPoint pixelDelta = wheelEvent->pixelDelta();
QPoint angleDelta = wheelEvent->angleDelta();
// 优先使用像素增量,如果没有则使用角度增量
QPointF scrollAmount;
if(!pixelDelta.isNull())
{
scrollAmount = QPointF(pixelDelta);
} else if(!angleDelta.isNull())
{
// 标准鼠标滚轮:角度转为像素(大约8度为一个标准滚动单位)
scrollAmount = QPointF(angleDelta) / 8.0;
}
// 应用滚动方向(正数向下/右,负数向上/左)
scrollAmount = -scrollAmount;
// 缩放滚动量来创建合适的惯性效果
QPointF velocity = scrollAmount * m_wheelSpeedFactor;
// 如果速度足够大,开始惯性滚动
qreal speed = qSqrt(velocity.x() * velocity.x() + velocity.y() * velocity.y());
if(speed > m_stopThreshold)
{
startScrolling(velocity);
// 先手动执行一次滚动,让响应更快
QScrollBar *vScrollBar = m_view->verticalScrollBar();
QScrollBar *hScrollBar = m_view->horizontalScrollBar();
if(vScrollBar && vScrollBar->isVisible() && !angleDelta.isNull())
{
vScrollBar->setValue(vScrollBar->value() + angleDelta.y() / 120 * vScrollBar->singleStep());
}
if(hScrollBar && hScrollBar->isVisible() && !angleDelta.isNull())
{
hScrollBar->setValue(hScrollBar->value() + angleDelta.x() / 120 * hScrollBar->singleStep());
}
return true; // 拦截滚轮事件,自己处理
}
break;
}
default:
break;
}
// 继续传递事件,不拦截
return false;
}
void InertialScroller::scrollStep()
{
if(!m_isScrolling || m_isPressed)
return;
// 减速
m_velocity *= m_deceleration;
// 计算滚动距离
qreal dx = m_velocity.x() * (m_timer.interval() / 1000.0);
qreal dy = m_velocity.y() * (m_timer.interval() / 1000.0);
// 应用滚动
QScrollBar *vScrollBar = m_view->verticalScrollBar();
QScrollBar *hScrollBar = m_view->horizontalScrollBar();
if(vScrollBar && vScrollBar->isVisible())
{
vScrollBar->setValue(vScrollBar->value() + qRound(dy));
}
if(hScrollBar && hScrollBar->isVisible())
{
hScrollBar->setValue(hScrollBar->value() + qRound(dx));
}
// 如果速度足够小,停止滚动
qreal speed = qSqrt(m_velocity.x() * m_velocity.x() + m_velocity.y() * m_velocity.y());
if(speed < m_stopThreshold)
{
stopScrolling();
}
}
void InertialScroller::startScrolling(const QPointF &velocity)
{
if(m_isScrolling)
return;
m_velocity = velocity;
m_isScrolling = true;
m_timer.start();
}
void InertialScroller::stopScrolling()
{
if(!m_isScrolling)
return;
m_timer.stop();
m_isScrolling = false;
m_velocity = QPointF(0, 0);
}