main.cpp
#include "virtual_list.h"
#include <QtWidgets/QApplication>
#include <qlistwidget.h>
int main(int argc, char* argv[])
{
QApplication a(argc, argv);
VirtualList w;
QList<QString> list;
// 插入50W条数据
for (int i = 0; i < 500000; i++)
{
list.push_back(QString("%1").arg(i, 5, 10, QChar('0')));
}
w.Append(list);
w.RefreshData();
w.show();
return a.exec();
}
virtual_list.h
#ifndef VIRTUAL_LIST_H
#define VIRTUAL_LIST_H
#include <QtWidgets/QMainWindow>
#include <qlistview.h>
#include <qlist.h>
#include <qstandarditemmodel.h>
#include <qboxlayout.h>
#include <qscrollbar.h>
class VirtualList : public QWidget
{
Q_OBJECT
public:
VirtualList(QWidget* parent = nullptr);
~VirtualList();
public:
/**
* @brief 追加单个数据
* @param data
*/
void Append(const QString& data);
/**
* @brief 批量追加数据
* @param dataList
*/
void Append(const QList<QString>& dataList);
/**
* @brief 清空表格
*/
void Clear();
/**
* @brief 获取表格
* @return
*/
QListView* GetListView() const;
/**
* @brief 重置页面数据
*/
void RefreshData();
/**
* @brief 行数
* @return
*/
int RowCount() const;
/**
* @brief 列数
* @return
*/
int ColumnCount() const;
/**
* @brief 获取当前索引
* @return
*/
int GetSelectedIndex() const;
/**
* @brief 获取指定行的数据
* @param index 行号,索引从0开始
* @return
*/
QString GetData(int index) const;
/**
* @brief 获取全部数据
* @return
*/
QList<QString> GetAllData() const;
protected:
void resizeEvent(QResizeEvent* event) override;
bool eventFilter(QObject* obj, QEvent* event) override;
protected:
/**
* @brief 竖向滚动条的指改变
* @param value
*/
void OnScrollBarValueChanged(int value);
/**
* @brief 处理滚动条(是否显示,以及长度区间)
*/
void HandleScrollBar();
private:
/**
* @brief 计算文本长度
* @param font
* @param text
* @return
*/
int CalcTextWidth(const QFont& font, const QString& text);
private:
QListView* m_pListView;
QScrollBar* m_pScrollBar;
QList<QString> m_dataList;
QStandardItemModel* m_pStdModel;
int m_nShowCount; // 显示的数量
int m_nCurrentPos; // 滚动条当前位置
int m_nCurrentIndex; // 当前数据索引
int m_nSelectedIndex; // 选中的索引
};
#endif
virtual_list.cpp
#include "virtual_list.h"
#include <qscrollbar.h>
#include <qevent.h>
#include <qfontmetrics.h>
#ifdef QT_DEBUG
#include <qdebug.h>
#endif // QT_DEBUG
#include <cmath>
using namespace std;
const static int nStdItemHeight = 40; // item固定40高度
VirtualList::VirtualList(QWidget* parent)
: QWidget(parent),
m_nShowCount(0),
m_nCurrentPos(0),
m_nCurrentIndex(-1),
m_nSelectedIndex(-1)
{
QHBoxLayout* pLayout = new QHBoxLayout(this);
m_pListView = new QListView(this);
m_pScrollBar = new QScrollBar(this);
m_pStdModel = new QStandardItemModel(m_pListView);
m_pListView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
m_pListView->setResizeMode(QListView::ResizeMode::Adjust);
m_pListView->setEditTriggers(QListView::NoEditTriggers);
m_pListView->setModel(m_pStdModel);
m_dataList.reserve(12000);
pLayout->addWidget(m_pListView);
pLayout->addWidget(m_pScrollBar);
m_pListView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
m_pScrollBar->setSingleStep(nStdItemHeight); // 一次移动一个Item
m_pScrollBar->setPageStep(nStdItemHeight);
m_pScrollBar->hide();
connect(m_pScrollBar, &QScrollBar::valueChanged, this, &VirtualList::OnScrollBarValueChanged);
connect(m_pListView, &QListView::clicked, this, [&] {
m_nSelectedIndex = m_nCurrentIndex + m_pListView->currentIndex().row();
});
installEventFilter(this);
m_pListView->installEventFilter(this);
m_pScrollBar->installEventFilter(this);
}
VirtualList::~VirtualList()
{}
void VirtualList::Append(const QString& data)
{
m_dataList.push_back(data);
// 数据追加后,更新索引
if (m_nCurrentIndex < 0)
{
m_nCurrentIndex = 0;
}
// 处理下滚动条
HandleScrollBar();
// 如果当前数据的数量小于显示数量,那么要刷新一次到界面
if (m_dataList.size() - 1 < m_nShowCount)
{
RefreshData();
}
}
void VirtualList::Append(const QList<QString>& dataList)
{
m_dataList.append(dataList);
// 数据追加后,更新索引
if (m_nCurrentIndex < 0)
{
m_nCurrentIndex = 0;
}
// 处理下滚动条
HandleScrollBar();
// 如果当前数据的数量小于显示数量,那么要刷新一次到界面
if (m_dataList.size() - dataList.size() < m_nShowCount)
{
RefreshData();
}
}
void VirtualList::Clear()
{
m_dataList.clear();
m_nCurrentPos = 0;
m_nCurrentIndex = -1;
m_nSelectedIndex = -1;
for (int i = m_pStdModel->rowCount() - 1; i >= 0; i--)
{
QStandardItem* item = m_pStdModel->item(i, 0); // 获取QStandardItem对象
delete item; // 释放QStandardItem对象
item = nullptr;
m_pStdModel->removeRow(i);
}
}
QListView* VirtualList::GetListView() const
{
return m_pListView;
}
int VirtualList::ColumnCount() const
{
return 1;
}
int VirtualList::GetSelectedIndex() const
{
return m_nSelectedIndex;
}
QString VirtualList::GetData(int index) const
{
if (index < 0 || index >= m_dataList.size())
{
return {};
}
return m_dataList[index];
}
QList<QString> VirtualList::GetAllData() const
{
return m_dataList;
}
void VirtualList::resizeEvent(QResizeEvent* event)
{
QWidget::resizeEvent(event);
// 获取重置的窗口大小
QSize listSize = m_pListView->size();
m_nShowCount = listSize.height() / nStdItemHeight;
if (m_nShowCount > 0 && m_dataList.size() > 0)
{
HandleScrollBar();
// 填充数据
RefreshData();
}
}
bool VirtualList::eventFilter(QObject* obj, QEvent* event)
{
if (event->type() == QEvent::Wheel)
{
if (obj == m_pScrollBar || obj == m_pListView)
{
QWheelEvent* we = static_cast<QWheelEvent*>(event);
if (we->angleDelta().y() > 0) // 向上滚
{
m_pScrollBar->setValue(m_pScrollBar->value() - nStdItemHeight);
}
else // 向下滚
{
m_pScrollBar->setValue(m_pScrollBar->value() + nStdItemHeight);
}
return true;
}
}
return QWidget::eventFilter(obj, event);
}
void VirtualList::OnScrollBarValueChanged(int value)
{
#ifdef QT_DEBUG
qDebug() << value;
#endif
// 查看最新的数据索引(最新索引是指即将要展示的首个数据索引)
int newIndex = std::ceil(value / (nStdItemHeight * 1.0f));
// 最新的索引超出数组大小
if (newIndex >= m_dataList.size())
{
// 最新的和当前的索引差值小于展示的数量时,直接退出
if (std::abs(newIndex - m_nCurrentIndex) <= m_nShowCount)
{
return;
}
else
{
--newIndex;
}
}
// 数据索引刷新
m_nCurrentIndex = newIndex;
// 取消选中状态
m_pListView->clearSelection();
// 看看刚才的选中索引是否在范围内
if (m_nSelectedIndex >= 0 && m_nSelectedIndex >= newIndex && m_nSelectedIndex <= newIndex + m_nShowCount)
{
int indexDiff = m_nSelectedIndex - m_nCurrentIndex;
m_pListView->setCurrentIndex(m_pStdModel->index(indexDiff, 0));
}
// 做下越界防护
if (m_nCurrentIndex >= m_dataList.size())
{
m_nCurrentIndex = m_dataList.size() - 1;
}
if (m_nCurrentIndex < 0)
{
m_nCurrentIndex = 0;
}
// 重置页面数据
RefreshData();
}
void VirtualList::HandleScrollBar()
{
if (m_dataList.size() > m_nShowCount)
{
// 设置大小,[0, item高度 * 数据长度]
m_pScrollBar->setRange(0, nStdItemHeight * m_dataList.size());
if (m_nShowCount < m_dataList.size()) // 显示的数量小于总数量
{
m_pScrollBar->setVisible(true); // 滚动条显示出来
}
}
}
int VirtualList::CalcTextWidth(const QFont& font, const QString& text)
{
QFontMetrics fm(font);
return fm.horizontalAdvance(text);
}
void VirtualList::RefreshData()
{
int showCount = m_nShowCount; // 界面最终展示多少个
// 最后数据小于应该显示的数量时,重新计算下数量
if (showCount + m_nCurrentIndex >= m_dataList.size())
{
showCount = m_dataList.size() - m_nCurrentIndex;
}
if (showCount > 0)
{
int diff = std::abs(showCount - m_pStdModel->rowCount());
if (showCount > m_pStdModel->rowCount()) // 展示的数量大于当前的,要添加
{
for (int i = 0; i < diff; i++)
{
QStandardItem* pItem = new QStandardItem;
pItem->setSizeHint(QSize(0, nStdItemHeight));
m_pStdModel->appendRow(pItem);
}
}
else if (showCount < m_pStdModel->rowCount()) // 展示的数量小于当前的,要删除
{
for (int i = m_pStdModel->rowCount() - 1; i >= showCount; i--)
{
QStandardItem* item = m_pStdModel->item(i, 0); // 获取QStandardItem对象
delete item; // 释放QStandardItem对象
item = nullptr;
m_pStdModel->removeRow(i);
}
}
else // 等于则不动
{
;
}
for (int i = 0; i < showCount; i++)
{
QStandardItem* pItem = m_pStdModel->item(i);
pItem->setSizeHint(QSize(CalcTextWidth(pItem->font(), m_dataList[m_nCurrentIndex + i]),
nStdItemHeight));
pItem->setText(m_dataList[m_nCurrentIndex + i]);
}
}
}
int VirtualList::RowCount() const
{
return m_dataList.size();
}
离线
离线
https://blog.csdn.net/qq_41359157/article/details/125163480
要实现手机类似手指滑动效果,或者其他触控屏滑动效果,需要在QTableWidget、QTableView、QListWidget、QListView控件基础上添加:
QScroller *pScroller = QScroller::scroller(listview);
pScroller->grabGesture(listview,QScroller::LeftMouseButtonGesture);
// 垂直方向按照像素的形式来滑动
listview->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
// 水平滑动条按照像素来滑动
// listview->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);
// listview->setVerticalScrollMode(QListWidget::ScrollPerPixel);
离线