如何减慢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; }
核心问题分析
- QScroller参数配置错误:你在
createScroller中先设置MaximumVelocity=0.0005,随后又覆盖为0,直接禁用了滚动速度控制;DecelerationFactor=10远超出合理范围(正常应小于1),导致滚动逻辑混乱。 - 未连接滚动条信号:
handleScrollBar系列函数仅在构造时调用一次,未监听滚动条的valueChanged或sliderReleased信号,无法实时响应滚动状态。 - 缺少自动吸附逻辑:没有实现滚动停止后自动对齐到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
相关产品推荐
相关产品推荐

