qt浏览文件支持惯性

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);
}
相关推荐
Scott9999HH7 小时前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
码智社8 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海8 小时前
python 列表、元组、集合和字典
开发语言·python
萧瑟余晖9 小时前
JDK 26 新特性详解
java·开发语言
马优晨10 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
人邮异步社区12 小时前
怎么把C语言学到精通?
c语言·开发语言
心平气和量大福大12 小时前
C#-WPF-控件-TextBox 数据绑定
开发语言·c#·wpf
ttwuai13 小时前
Cursor 生成 CRUD 后,Go 后台接口别只测 200:JWT、RBAC 和 tenant_id 怎么验
开发语言·后端·golang
এ慕ོ冬℘゜13 小时前
前端基础:什么是时间戳?JS获取时间戳三种方法与实战用途
开发语言·前端·javascript
执明wa13 小时前
LayoutInflater详解: XML是如何变成View的?
android·xml·开发语言·android studio