Qt列表等控件实现平滑滚动

Qt自带的的列表控件是不能平滑滚动的,但如果滚动速度快的话很容易引起视线丢失,体验效果很差。本篇主要讲述如何在Qt中对列表控件加入平滑滚动。文中以QScrollArea控件为例,其他控件方法一样。

原理

Qt的列表控件中,有以下两个接口:

void QAbstractScrollArea::setHorizontalScrollBar(QScrollBar *scrollBar);
void QAbstractScrollArea::setVerticalScrollBar(QScrollBar *scrollBar);

显然上述两个接口的作用是设置控件的横纵两个滚动条,以指针形式传入,我们将以此来实现控制页面的滚动。传入滚动条对象后,我们就可以使用setValue()来间接控制页面的滚动了。然后再使用QPropertyAnimation类来实现滚动的效果。

实现

一、自定义滚动条控件

需要实现两个功能:

  • 当使用setValue()对滚动条的滑块进行移动时,滑块会在一个时间段内以某种规律连续的移动到目标位置,而不是瞬间移动。
  • 新增一个槽函数void scroll(int value),实现传入一个数字后相对滚动指定距离。如:scroll(100)就是向下滚动100个单位。

头文件:

#ifndef SMOOTHSCROLLBAR_H
#define SMOOTHSCROLLBAR_H #include <QScrollBar>
#include <QPropertyAnimation>
class SmoothScrollBar : public QScrollBar
{
Q_OBJECT
public:
SmoothScrollBar(QWidget *parent=nullptr);
private:
//这里重写鼠标事件的目的是在手动点击或拖动滚动条时更新m_targetValue_v变量,并且在拖动时立即结束滚动的动画。
//这里如果不明白作用,可以先注释掉看看手动拖动滚动条时对动画有什么影响。
void mousePressEvent(QMouseEvent *) override;
void mouseReleaseEvent(QMouseEvent *) override;
void mouseMoveEvent(QMouseEvent *) override; QPropertyAnimation *m_scrollAni; //用来实现动画
int m_targetValue_v; //用来记录目标位置的变量
public slots:
void setValue(int value); //重写的setValue槽函数,实现动画效果
void scroll(int value); //新增相对滚动的槽函数,value为滚动距离的矢量表示
signals:
}; #endif // SMOOTHSCROLLBAR_H

源文件:

#include "smoothscrollbar.h"
#include <QWheelEvent>
SmoothScrollBar::SmoothScrollBar(QWidget* parent):QScrollBar(parent)
{
m_scrollAni=new QPropertyAnimation;
m_scrollAni->setTargetObject(this);
m_scrollAni->setPropertyName("value");
m_scrollAni->setEasingCurve(QEasingCurve::OutQuint); //设置动画曲线,在Qt文档中有详细的介绍
m_scrollAni->setDuration(800); //设置动画时间,数值越小播放越快
m_targetValue_v=value(); //将m_targetValue_v初始化
} void SmoothScrollBar::setValue(int value)
{
m_scrollAni->stop();//停止现在的动画,防止出现冲突
m_scrollAni->setStartValue(this->value()); //设置动画滚动的初始值为当前位置
m_scrollAni->setEndValue(value); //设置动画的结束位置为目标值
m_scrollAni->start(); //开始动画
} void SmoothScrollBar::scroll(int value)
{
m_targetValue_v-=value; //将目标值和相对位置进行运算
setValue(m_targetValue_v); //开始动画
} void SmoothScrollBar::mousePressEvent(QMouseEvent *e)
{
//当使用鼠标操作滚动条时,不会刷新m_targetValue_v的值,因而需要重写事件,对其进行刷新。
m_scrollAni->stop();
QScrollBar::mousePressEvent(e);
m_targetValue_v=value();
} void SmoothScrollBar::mouseReleaseEvent(QMouseEvent *e)
{
m_scrollAni->stop();
QScrollBar::mouseReleaseEvent(e);
m_targetValue_v=value();
} void SmoothScrollBar::mouseMoveEvent(QMouseEvent *e)
{
m_scrollAni->stop();
QScrollBar::mouseMoveEvent(e);
m_targetValue_v=value();
}

二、自定义列表控件

将列表的滚动条替换为我们刚刚自定义的滚动条

头文件:

#ifndef SMOOTHSCROLLAREA_H
#define SMOOTHSCROLLAREA_H #include <QWidget>
#include <QScrollArea>
#include "smoothscrollbar.h"
class SmoothScrollArea : public QScrollArea
{
Q_OBJECT
public:
explicit SmoothScrollArea(QWidget *parent = nullptr);
private:
SmoothScrollBar* vScrollBar; //纵向滚动条
void wheelEvent(QWheelEvent* e); //捕获鼠标滚轮事件
}; #endif // SMOOTHSCROLLAREA_H

源文件:

#include "smoothScrollArea.h"
#include <QVBoxLayout>
#include <QLabel>
#include <QWheelEvent>
#include <QDebug>
SmoothScrollArea::SmoothScrollArea(QWidget *parent) : QScrollArea(parent)
{
auto layout = new QVBoxLayout;
vScrollBar=new SmoothScrollBar();
vScrollBar->setOrientation(Qt::Orientation::Vertical); //将滚动条设置为纵向
QWidget* w=new QWidget; //主体Widget
for (int i=0;i<200 ;i++ ) { //在w中加入200个label,用来测试滚动
QFont font;
font.setPointSize(i+1);
auto a=new QLabel(QString::number(i));
a->setFont(font);
layout->addWidget(a);
}
setVerticalScrollBar(vScrollBar); //设置纵向滚动条
w->setLayout(layout); //设置布局
setWidget(w); //设置widget } void SmoothScrollArea::wheelEvent(QWheelEvent *e)
{
//当捕获到事件后,调用相对滚动的槽函数
vScrollBar->scroll(e->angleDelta().y());
}

到此为止,SmoothScrollArea类便可以支持纵向的平滑滚动。其他的列表控件方法一致。

三、测试效果

SmoothScrollArea列表控件加入到主窗口后,运行即可。

补充

在此之前参考过deepin-launcher的小窗口模式列表代码,deepin的平滑滚动策略存在缺陷,导致体验较差。这里我详细说明一下这一简单的问题,非deepin用户或开发者可以到此为止了。

/**
* @brief AppListView::wheelEvent 鼠标滑轮事件触发滑动区域控件动画
* @param e 鼠标滑轮事件指针对象
*/
void AppListView::wheelEvent(QWheelEvent *e)
{
if (e->orientation() == Qt::Horizontal)
return; int offset = -e->delta(); m_scrollAni->stop();
m_scrollAni->setStartValue(verticalScrollBar()->value());
m_scrollAni->setEndValue(verticalScrollBar()->value() + offset * m_speedTime);
m_scrollAni->start();
}

由代码中可以看到,首先他屏蔽了横向滑动的事件,这个主要应对触控板的一些问题。

offset为滚动的距离,每次滚动之前先停止上次动画(如果还没有结束的话),然后在以当前位置为起始位置,以相对offset的位置为结束位置,这就会导致一个问题。

假设有两个连续的滚动事件被触发,上边这个函数就会执行两次。这里会出现两种情况:

  • 两次连续的事件时间间隔比较大,鼠标滚轮速度比较慢。
  • 两次连续事件时间间隔很小,小于动画时长,鼠标滚动速度很快。

第一种情况下,deepin的这个方案并没有什么问题,两次动画之间互不干扰。但是第二种情况就会发现问题,假设动画时长为800ms,当第一次滚动事件触发后,随后800ms的时间中,滚动条将相应的向目标位置滚动,但是当第二次事件在第一次的动画还未结束时到来的话,第一次的动画将被打断,也就是只滚动了一部分就结束了,因此,当连续的快速滚动事件结束后,实际滚动的距离要远小于期望的距离。

这个问题在本文中的解决方案时建立一个目标位置的变量。此变量用于记录滚动所期望的位置,不会导致滚动失真。

大致如下:

设记录目标位置的变量为a,a的值将被初始化为滚动条当前的value,此后,当鼠标滚动事件被触发时,首先将a的值通过和滚动距离的计算变为新的位置,此时当前位置与a的值将不再相等,然后在通过动画将结束位置定为a。这样处理的好处,通过上边的方法分析如下:

首先第一种情况是没有区别的,来看第二种。同样假设动画时方案长为800ms,当第一次滚动事件发生后,a将被计算为要滚动的目标位置,随后的800ms将是动画的执行过程,当这个过程还未结束时,第二次滚动又将a的值通过和滚动距离的计算,变为一个新的位置,再由动画去执行。这里注意,a在两次变化中,第一次的距离并未丢失,两次距离相加,当连续的快速滚动事件结束后,实际滚动的距离等于所期望的距离。

deepin那种方案所导致的现象

  • 快速滚动滚轮并不能让列表的滚动速度便快,甚至还可能不如滚轮滚的慢一点。
  • 滚动的动画将会在一些情况下看起来不流畅

希望deepin能采纳文中的方案。

文中代码下载地址:https://maicss.lanzoui.com/iQHCHswqm6j

Qt列表等控件实现平滑滚动&deepin启动器存在的问题的更多相关文章

  1. Qt实现表格控件-支持多级列表头、多级行表头、单元格合并、字体设置等

    目录 一.概述 二.效果展示 三.定制表头 1.重写数据源 2.重写QHeaderView 四.设置属性 五.相关文章 原文链接:Qt实现表格控件-支持多级列表头.多级行表头.单元格合并.字体设置等 ...

  2. duilib List控件,横向滚动时列表项不移动或者显示错位的bug的修复

    转载请说明出处,谢谢~~:http://blog.csdn.net/zhuhongshu/article/details/42264673 关于这个bug的修复我之前写过一篇博客,连接为:http:/ ...

  3. MFC编程入门之二十四(常用控件:列表框控件ListBox)

    前面两节讲了比较常用的按钮控件,并通过按钮控件实例说明了具体用法.本文要讲的是列表框控件(ListBox)及其使用实例. 列表框控件简介 列表框给出了一个选项清单,允许用户从中进行单项或多项选择,被选 ...

  4. Windows Phone 8.1 新特性 - 控件之列表选择控件

    本篇我们来介绍Windows Phone 8.1 新特性中的列表选择控件. 在Windows Phone 8 时代,大家都会使用 LongListSelector 来实现列表选择控件,对数据进行分组显 ...

  5. Qt学习——QListWidget控件的使用

    转载:GDUTLYP Qt提供QListWidget类列表框控件用来加载并显示多个列表项.QListWidgetItem类就是列表项类. 一般列表框控件中的列表项有两种加载方式: 一种是由用户手动添加 ...

  6. VS2010/MFC编程入门之二十四(常用控件:列表框控件ListBox)

    前面两节讲了比较常用的按钮控件,并通过按钮控件实例说明了具体用法.本文要讲的是列表框控件(ListBox)及其使用实例. 列表框控件简介 列表框给出了一个选项清单,允许用户从中进行单项或多项选择,被选 ...

  7. (七)对话框,单选框(radiobox),复选框(checkbox),列表框(ListBox),组合框(CComboBox),水平滚动条(Horizontal scroll bar),微调(旋转)spincontrol,列表视图控件CListCtrl,静态控件static

    1,模态对话框和非模态对话框 // 模态对话框 void CMainFrame::OnDialogExec() { // TODO: 在此添加命令处理程序代码 // 创建对话框对象 CDialog d ...

  8. VS2010-MFC(常用控件:列表框控件ListBox)

    转自:http://www.jizhuomi.com/software/186.html 列表框控件简介 列表框给出了一个选项清单,允许用户从中进行单项或多项选择,被选中的项会高亮显示.列表框可分为单 ...

  9. LabVIEW(十六):多列列表框控件

    1.多列列表框控件:前面板右键>列表.表格和树>多列列表框2.默认情况下只显示列首,可设置显示行首:前面板选中多列列表框右键>显示>行首3.LabVIEW中提供42种自带的图标 ...

随机推荐

  1. 暑假自学java第六天

    1,方法的覆盖:当子类继承父类,而子类中的方法与父类中方法的名称,返回类型及参数都完全一致时,就称子类中的方法覆盖了父类中的方法,有时也称方法的"重写" [不需要关键字] 2,th ...

  2. MVC 与 三层架构

    https://www.bilibili.com/video/av29086718/?p=24 MVC: MVC与三层架构进行比较:

  3. linux学习之路第九天(任务调度)

    crond 任务调度 概念 任务调度:是指的系统在某个时间执行的特定命令或程序. 比喻理解: 可以这样理解,比如生活中有闹钟,闹钟的作用是不是叫人起床的,那古时候没有闹钟,叫人起床的工作是不是要人去完 ...

  4. Ha1cyon_CTF-公开赛(wp)

    一.babyasm 00007FF7A8AC5A50 push rbp 00007FF7A8AC5A52 push rdi 00007FF7A8AC5A53 sub rsp,238h 00007FF7 ...

  5. 『心善渊』Selenium3.0基础 — 23、Selenium元素等待

    目录 1.什么是元素等待 2.为什么要设置元素等待 3.Selenium中常用的等待方式 4.强制等待 5.隐式等待 (1)隐式等待介绍 (2)示例 6.显式等待 (1)显式等待介绍 (2)语法 (3 ...

  6. 传统.NET 4.x应用容器化体验(3)

    上一篇我们自己通过编写Dockerfile来编译部署一个ASP.NET MVC应用程序到Windows Container,这一篇我们来试着将.NET 4.x的镜像推送到harbor私有镜像仓库. 1 ...

  7. MySQL全面瓦解26:代码评审中的MySQL(团队使用)

    数据库对象命名规范 数据库对象 数据库对象是数据库的组成部分,常见的有以下几种: 表(Table ).索引(Index).视图(View).图表(Diagram).缺省值(Default).规则(Ru ...

  8. OpenFaaS实战之二:函数入门

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  9. 开源百宝箱《HelloGitHub》第 64 期

    兴趣是最好的老师,HelloGitHub 让你对编程感兴趣! 简介 分享 GitHub 上有趣.入门级的开源项目. HelloGitHub 有实战.教程.黑科技.开源书籍.企业级开源项目,涵盖多种编程 ...

  10. VUP虚拟直播与光学动作捕捉技术

    虚拟直播将虚拟场景.虚拟形象实时显示在观众面前,虚拟场景与人物替代了原有的耗费较大搭建成本的实景场景与真人出镜,为观众带来全新的视觉体验,同时新技术降低了原有场景搭建成本,是近些年继AI.VR.动作捕 ...