You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何减慢QListWidget滚动条的拖拽滚动速度?

QListWidget实现闹钟滚轮:解决拖拽滚动过快及吸附对齐问题

我用QListWidget的垂直滚动条实现类似闹钟的分钟/秒数选择功能,但遇到两个问题:

  • 拖拽(比如触控板轻扫)时滚动速度过快,一次能从0滑到30
  • 尝试捕获滑块释放信号来减速,但信号没触发
    想要实现类似手机闹钟的效果:拖拽时滚动逐渐减速,最终自动固定到某个数值,而不是直接滑到两端。

现有代码

mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QListWidget>
#include <QScrollBar>
#include <QScroller>

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private:
    Ui::MainWindow *ui;

    QListWidget *listWidget1;
    QListWidget *listWidget2;

    void createScroller();
    void handleScrollBar();
    void handleScrollBar2();
};
#endif // MAINWINDOW_H

mainwindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QScroller>
#include <QScrollBar>
#include <QHBoxLayout>
#include <QDebug>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    // Create and set up ListWidget1
    listWidget1 = new QListWidget(this);
    listWidget2 = new QListWidget(this);

    QFont userScroller("DINCond-Bold", 17, QFont::Medium);

    for (int i = 0; i <= 30; i++) {
        QListWidgetItem *item;
        if (i < 10)
            item = new QListWidgetItem("0" + QString::number(i));
        else
            item = new QListWidgetItem(QString::number(i));
        item->setFont(userScroller);
        listWidget1->addItem(item);
        item->setTextAlignment(Qt::AlignRight);
    }

    // Append "00" for circular scrolling behavior
    QListWidgetItem *circularItem = new QListWidgetItem("00");
    circularItem->setFont(userScroller);
    circularItem->setTextAlignment(Qt::AlignRight);
    listWidget1->addItem(circularItem);

    listWidget1->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    listWidget1->setStyleSheet("background:rgba(0,0,0,0.025);border-radius : 0px;border:0px;color: rgba(255, 255, 255, 1);");
    listWidget1->setSpacing(30);
    listWidget1->setFixedHeight(350);
    listWidget1->setAttribute(Qt::WA_AcceptTouchEvents, true);
    listWidget1->setAutoScroll(false);
    listWidget1->setDragEnabled(false);
    listWidget1->setAcceptDrops(false);
    listWidget1->setFocusPolicy(Qt::NoFocus);
    listWidget1->setSelectionMode(QAbstractItemView::NoSelection);

    for (int i = 0; i < 60; i++) {
        QListWidgetItem *item;
        if (i < 10)
            item = new QListWidgetItem("0" + QString::number(i));
        else
            item = new QListWidgetItem(QString::number(i));
        item->setFont(userScroller);
        item->setTextAlignment(Qt::AlignLeft);
        listWidget2->addItem(item);
    }

    listWidget2->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    listWidget2->setStyleSheet("background:rgba(0,0,0,0.025);border-radius : 0px;border:0px;color: rgba(255, 255, 255, 1);");
    listWidget2->setSpacing(30);
    listWidget2->setFixedHeight(350);
    listWidget2->setAttribute(Qt::WA_AcceptTouchEvents, true);
    listWidget2->setAutoScroll(false);
    listWidget2->setFocusPolicy(Qt::NoFocus);
    listWidget2->setSelectionMode(QAbstractItemView::NoSelection);

    QScroller::grabGesture(listWidget1, QScroller::LeftMouseButtonGesture);
    QScroller::grabGesture(listWidget2, QScroller::LeftMouseButtonGesture);
    QScroller::grabGesture(listWidget1, QScroller::ScrollerGestureType::TouchGesture);
    QScroller::grabGesture(listWidget2, QScroller::ScrollerGestureType::TouchGesture);

    QHBoxLayout *listWidgetLayout1 = new QHBoxLayout();
    listWidgetLayout1->addWidget(listWidget1);

    QHBoxLayout *listWidgetLayout2 = new QHBoxLayout();
    listWidgetLayout2->addWidget(listWidget2);

    QHBoxLayout *mainLayout = new QHBoxLayout();
    mainLayout->addLayout(listWidgetLayout1);
    mainLayout->addLayout(listWidgetLayout2);

    QWidget *centralWidget = new QWidget(this);
    centralWidget->setLayout(mainLayout);
    setCentralWidget(centralWidget);

    // Initialize scrollers
    createScroller();
    handleScrollBar();
    handleScrollBar2();
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::createScroller()
{
    QScroller *scroller1 = QScroller::scroller(listWidget1->viewport());
    QScroller *scroller2 = QScroller::scroller(listWidget2->viewport());

    if (scroller1) {
        QScrollerProperties scrollerProps = scroller1->scrollerProperties();
        scrollerProps.setScrollMetric(QScrollerProperties::VerticalOvershootPolicy, QScrollerProperties::OvershootAlwaysOff);
        scrollerProps.setScrollMetric(QScrollerProperties::DecelerationFactor, 10);
        scrollerProps.setScrollMetric(QScrollerProperties::MaximumVelocity, 0.0005);
        //scrollerProps.setScrollMetric(QScrollerProperties::ScrollingCurve, QEasingCurve(QEasingCurve::Linear));
        scrollerProps.setScrollMetric(QScrollerProperties::DragVelocitySmoothingFactor, 1);
        scrollerProps.setScrollMetric(QScrollerProperties::OvershootDragResistanceFactor, 0);
        scrollerProps.setScrollMetric(QScrollerProperties::OvershootScrollTime, 0);
        scrollerProps.setScrollMetric(QScrollerProperties::OvershootScrollDistanceFactor, 0.1);
        scrollerProps.setScrollMetric(QScrollerProperties::SnapTime, 0.0006);
        scrollerProps.setScrollMetric(QScrollerProperties::SnapPositionRatio, 0.5);
        scrollerProps.setScrollMetric(QScrollerProperties::MaximumClickThroughVelocity, 0.01);
        scrollerProps.setScrollMetric(QScrollerProperties::MinimumVelocity, 0);
        scrollerProps.setScrollMetric(QScrollerProperties::MaximumVelocity, 0);
        scroller1->setScrollerProperties(scrollerProps);
    }

    if (scroller2) {
        QScrollerProperties scrollerProps = scroller2->scrollerProperties();
        scrollerProps.setScrollMetric(QScrollerProperties::VerticalOvershootPolicy, QScrollerProperties::OvershootAlwaysOff);
        scrollerProps.setScrollMetric(QScrollerProperties::DecelerationFactor, 10);
        scrollerProps.setScrollMetric(QScrollerProperties::MaximumVelocity, 0.0005);
        //scrollerProps.setScrollMetric(QScrollerProperties::ScrollingCurve, QEasingCurve(QEasingCurve::Linear));
        scrollerProps.setScrollMetric(QScrollerProperties::DragVelocitySmoothingFactor, 1);
        scrollerProps.setScrollMetric(QScrollerProperties::OvershootDragResistanceFactor, 0);
        scrollerProps.setScrollMetric(QScrollerProperties::OvershootScrollTime, 0);
        scrollerProps.setScrollMetric(QScrollerProperties::OvershootScrollDistanceFactor, 0.1);
        scrollerProps.setScrollMetric(QScrollerProperties::SnapTime, 0.0006);
        scrollerProps.setScrollMetric(QScrollerProperties::SnapPositionRatio, 0.5);
        scrollerProps.setScrollMetric(QScrollerProperties::MaximumClickThroughVelocity, 0.01);
        scrollerProps.setScrollMetric(QScrollerProperties::MinimumVelocity, 0);
        scrollerProps.setScrollMetric(QScrollerProperties::MaximumVelocity, 0);
        scroller2->setScrollerProperties(scrollerProps);
    }
}

void MainWindow::handleScrollBar()
{
    int mins = listWidget1->verticalScrollBar()->value();
    int secs = listWidget2->verticalScrollBar()->value();

    QString minsStr = (mins < 10) ? "0" + QString::number(mins) : QString::number(mins);
    QString secsStr = (secs < 10) ? "0" + QString::number(secs) : QString::number(secs);
}

void MainWindow::handleScrollBar2()
{
    int scrollbarValue = listWidget2->verticalScrollBar()->value();
    int mins = listWidget1->verticalScrollBar()->value() + 1;
    int secs = scrollbarValue + 1;

    QString minsStr = (mins < 10) ? "0" + QString::number(mins) : QString::number(mins);
    QString secsStr = (secs < 10) ? "0" + QString::number(secs) : QString::number(secs);
QString vtime = minsStr  + secsStr ;

    qDebug() << "Updated time: " << vtime;
}

核心问题分析

  1. QScroller参数配置错误:你在createScroller中先设置MaximumVelocity=0.0005,随后又覆盖为0,直接禁用了滚动速度控制;DecelerationFactor=10远超出合理范围(正常应小于1),导致滚动逻辑混乱。
  2. 未连接滚动条信号:handleScrollBar系列函数仅在构造时调用一次,未监听滚动条的valueChanged或sliderReleased信号,无法实时响应滚动状态。
  3. 缺少自动吸附逻辑:没有实现滚动停止后自动对齐到item的逻辑,无法让选中数值居中显示。

修复后的实现方案

1. 修正QScroller参数配置

重新设置合理的滚动速度、减速因子和吸附参数:

void MainWindow::createScroller()
{
    QScroller *scroller1 = QScroller::scroller(listWidget1->viewport());
    QScroller *scroller2 = QScroller::scroller(listWidget2->viewport());

    auto setupScroller = [](QScroller *scroller) {
        if (!scroller) return;
        QScrollerProperties props = scroller->scrollerProperties();
        // 关闭过度滚动
        props.setScrollMetric(QScrollerProperties::VerticalOvershootPolicy, QScrollerProperties::OvershootAlwaysOff);
        // 减速因子:值越小,滚动减速越慢;值越大,减速越快(建议0.1-0.3)
        props.setScrollMetric(QScrollerProperties::DecelerationFactor, 0.2);
        // 最大滚动速度:限制拖拽时的速度
        props.setScrollMetric(QScrollerProperties::MaximumVelocity, 0.3);
        // 吸附到item中心位置
        props.setScrollMetric(QScrollerProperties::SnapPositionRatio, 0.5);
        // 吸附动画时长
        props.setScrollMetric(QScrollerProperties::SnapTime, 0.3);
        // 启用位置吸附
        props.setScrollMetric(QScrollerProperties::SnapEnabled, true);
        scroller->setScrollerProperties(props);
    };

    setupScroller(scroller1);
    setupScroller(scroller2);
}

2. 添加槽函数并连接信号

首先在mainwindow.h中声明槽函数:

private slots:
    void onScrollBarValueChanged(int value);
    void onScrollBarReleased();
    void alignToItem(QListWidget *listWidget);

然后在mainwindow.cpp的构造函数中添加信号连接:

// 连接滚动条信号
connect(listWidget1->verticalScrollBar(), &QScrollBar::valueChanged, this, &MainWindow::onScrollBarValueChanged);
connect(listWidget1->verticalScrollBar(), &QScrollBar::sliderReleased, this, &MainWindow::onScrollBarReleased);
connect(listWidget2->verticalScrollBar(), &QScrollBar::valueChanged, this, &MainWindow::onScrollBarValueChanged);
connect(listWidget2->verticalScrollBar(), &QScrollBar::sliderReleased, this, &MainWindow::onScrollBarReleased);

实现槽函数逻辑:

void MainWindow::onScrollBarValueChanged(int value)
{
    int mins = listWidget1->verticalScrollBar()->value();
    int secs = listWidget2->verticalScrollBar()->value();

    // 处理分钟列表的循环滚动:滑到最后一个"00"时自动回到0
    if (mins == listWidget1->count() - 1) {
        listWidget1->verticalScrollBar()->setValue(0);
        mins = 0;
    }

    QString minsStr = mins < 10 ? "0" + QString::number(mins) : QString::number(mins);
    QString secsStr = secs < 10 ? "0" + QString::number(secs) : QString::number(secs);
    qDebug() << "当前时间:" << minsStr << ":" << secsStr;
}

void MainWindow::onScrollBarReleased()
{
    // 释放滑块后自动对齐到最近的item
    alignToItem(listWidget1);
    alignToItem(listWidget2);
}

void MainWindow::alignToItem(QListWidget *listWidget)
{
    QScrollBar *scrollBar = listWidget->verticalScrollBar();
    int itemTotalHeight = listWidget->sizeHintForRow(0) + listWidget->spacing();
    // 计算当前滚动位置对应的item索引
    int currentIndex = qRound((double)scrollBar->value() / itemTotalHeight);
    // 确保索引在有效范围内
    currentIndex = qBound(0, currentIndex, listWidget->count() - 1);
    // 滚动到item中心位置
    listWidget->scrollToItem(listWidget->item(currentIndex), QAbstractItemView::PositionAtCenter);
}

3. 调整ListWidget滚动模式

在listWidget初始化时添加以下代码,让滚动按item为单位:

// 为listWidget1和listWidget2添加
listWidget1->setVerticalScrollMode(QAbstractItemView::ScrollPerItem);
listWidget2->setVerticalScrollMode(QAbstractItemView::ScrollPerItem);

关键优化说明

  • 速度控制:通过MaximumVelocity限制拖拽最大速度,DecelerationFactor调整减速快慢,贴合手机闹钟的滚动手感。
  • 自动吸附:启用SnapEnabled并结合scrollToItem,实现滚动停止后自动居中对齐到item。
  • 循环滚动:在valueChanged信号中处理分钟列表的循环逻辑,滑到末尾自动回到开头。
  • 信号响应:连接sliderReleased信号,确保释放滑块时触发对齐逻辑,解决之前信号未触发的问题。

内容的提问来源于stack exchange,提问作者Supratik Biswas

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 06:52:04