#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();
}
示例二:
// 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_OBJECTpublic:/*** @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
// 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); // 約60FPSconnect(&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);
}