目录
1.使用场景
项目中需要用到很多的小格子来显示数据,但是如果new很多个按钮出来的话内存就爆掉了,为了解决这个问题一开始选择使用QGraphicsView来实现,绘制的QGraphicsRectItem大大减小了内存占用,初步解决了问题。随着需求的变化,需要的格子越来越多,即使减少了内存占用数量,格子更多的时候依然会造成内存占用过多和界面卡顿,所以不得不继续优化,于是选择绘制固定的格子数量,拖动滚动条的时候通过移动格子的坐标来刷新显示。
2.功能实现
(1)初始化QGraphicsView
这里隐藏掉QGraphicsView自带的滚动条,因为会遮挡住一部分格子,使用自定义滚动条,setSceneRect决定了场景的大小,也就是实际需要的格子大小
mCellGraphicsView = new QGraphicsView(ui->cellWidget);
mCellGraphicsView->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
mCellGraphicsScene = new QGraphicsScene(this);
mCellGraphicsScene->setBackgroundBrush(Qt::NoBrush);
mCellGraphicsView->setScene(mCellGraphicsScene);
mCellGraphicsView->setAlignment(Qt::AlignLeft | Qt::AlignTop);
mCellGraphicsScene->setItemIndexMethod(QGraphicsScene::NoIndex);
mCellGraphicsView->setMouseTracking(true);
mCellGraphicsView->viewport()->installEventFilter(this);
mCellGraphicsScene->installEventFilter(this);
mCellGraphicsView->setOptimizationFlag(QtCharts::QChartView::DontAdjustForAntialiasing, true);
mCellGraphicsView->setViewportUpdateMode(QtCharts::QChartView::MinimalViewportUpdate);
mCellGraphicsView->setScene(mCellGraphicsScene);
mCellGraphicsView->setMouseTracking(true);
mCellGraphicsView->viewport()->installEventFilter(this);
mCellGraphicsView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
mCellGraphicsView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
mCellGraphicsView->setMaximumSize(ui->cellWidget->width(),ui->cellWidget->height());
mCellGraphicsView->setCursor(Qt::PointingHandCursor);
ui->verticalScrollBar->setCursor(Qt::PointingHandCursor);
ui->horizontalScrollBar->setCursor(Qt::PointingHandCursor);
int width=ITEM_SIZE*10000;//实际宽度
int height=ITEM_SIZE*10000;//实际高度
mCellGraphicsScene->setSceneRect(0, 0,width, height);
(2) 绘制固定数量格子并分组
绘制格子
这里我选择绘制6000个格子,一行100个,一列60个,一共60个组,每个组大小为10*10
//每个组10*10共100个格子,一行10个组,一列6个组,共60个组,6000个格子
const int gridSize = ITEM_SIZE; // 格子大小
const int rowCount = 10; // 一组中一行10个格子
const int colCount = 10; // 一组中一列10个格子
const int groupsRowCount = 6; // 6行
const int groupsColCount = 10; // 10列
这里用二维数组列表来存储所有的组
QList<QList<QGraphicsItemGroup*>> groupList;
//改动行列数需要修改颜色list大小,否则会越界崩溃
QList<Qt::GlobalColor> rowList;
rowList<<Qt::red<<Qt::green<<Qt::blue<<Qt::cyan<<Qt::yellow<<Qt::darkBlue;
QList<Qt::GlobalColor> colList;
colList<<Qt::darkYellow<<Qt::darkMagenta<<Qt::darkCyan<<Qt::darkBlue<<Qt::darkGreen<<Qt::darkRed<<Qt::lightGray<<Qt::color1<<Qt::gray<<Qt::blue;
//创建60个组
for (int groupRow = 0;groupRow < groupsRowCount;groupRow++)
{
QList<QGraphicsItemGroup*> list;
groupList.append(list);
for(int groupCol = 0;groupCol< groupsColCount ;groupCol++)
{
QGraphicsItemGroup* group = new QGraphicsItemGroup();
//一个组内循环
for (int row = 0; row < rowCount; ++row)//0~9
{
for (int col = 0; col < colCount; ++col)//0~9
{
int xPos = (groupCol * colCount + col) * (itemSize);
int yPos = (groupRow * rowCount + row) * (itemSize);
QGraphicsRectItem* rectItem = new QGraphicsRectItem(xPos, yPos, itemSize, itemSize);
QPen pen(QColor(0,0,0));
pen.setWidthF(0.5);
rectItem->setPen(pen);
//改动行列数需要修改颜色list大小,否则会越界崩溃
if(row==0) rectItem->setBrush(QBrush(rowList[groupRow]));
if(col==0) rectItem->setBrush(QBrush(colList[groupCol]));
group->addToGroup(rectItem);
}
}
groupList[groupRow].append(group);
mCellGraphicsScene->addItem(group);
}
}
分组的原因
如果整体移动的话会需要比较多的时间移动,快速拖动滚动条的时候界面就会很卡顿,所以需要分组,一次移动一行或者一列的组
(3)自定义滚动条
QScrollBar不支持直接点击跳转,重写一下这两个滚动条
水平滚动条
MyHorizontalScrollBar::MyHorizontalScrollBar(QWidget *parent) : QScrollBar(parent)
{
int height = 12;
setFixedHeight(height);
QString sty = QString(R"(QScrollArea{border:none;}
QScrollBar:horizontal {height: 8px;background: transparent;}
QScrollBar::handle:horizontal {height: 8px;background: rgb(175, 205, 240);border-radius:%1px;min-width: 100px;}
QScrollBar::handle:horizontal:hover {height: 8px;background: rgb(165, 195, 230);border-radius:%1px;min-width: 100px;}
QScrollBar::add-line:horizontal {width:0px;height: 8px;subcontrol-position: right;}
QScrollBar::sub-line:horizontal {width:0px;height: 8px;subcontrol-position: left;}
QScrollBar::add-page:horizontal,QScrollBar::sub-page:horizontal {background: #E2E8F7;border-radius:%1px;})").arg(height/2);
setStyleSheet(sty);
setCursor(Qt::PointingHandCursor);
}
void MyHorizontalScrollBar::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton)
{
//获取滑块范围
QStyleOptionSlider opt;
initStyleOption(&opt);
QRect sliderHandleRect = style()->subControlRect(QStyle::CC_ScrollBar, &opt, QStyle::SC_ScrollBarSlider, this);
if (!sliderHandleRect.contains(event->pos()))
{
// 获取当前点击位置,得到的这个鼠标坐标是相对于当前QSlider的坐标
int currentX = event->pos().x();
// 获取当前点击的位置占整个Slider的百分比
double per = currentX * 1.0 / this->width();
// 利用算得的百分比得到具体数字
int value = per * (this->maximum() - this->minimum()) + this->minimum();
// 设定滑动条位置
setValue(value);
}
}
QScrollBar::mousePressEvent(event);
}
垂直滚动条
MyVerticalScrollBar::MyVerticalScrollBar(QWidget *parent) : QScrollBar(parent)
{
int width = 12;
setFixedWidth(width);
QString sty = QString(R"( QScrollArea{border :none;}
QScrollBar:vertical{width:8px;background:transparent;}
QScrollBar::handle:vertical{width:8px;background: rgb(175, 205, 240);border-radius:%1px;min-height:100}
QScrollBar::handle:vertical:hover{width:8px;background: rgb(165, 195, 230);border-radius:%1px;min-height:100;}
QScrollBar::add-line:vertical{height:0px;width:8px;subcontrol-position:bottom;}
QScrollBar::sub-line:vertical{height:0px;width:8px;subcontrol-position:top;}
QScrollBar::add-page:vertical,QScrollBar::sub-page:vertical{background: #E2E8F7;border-radius:%1px;})").arg(width/2);
setStyleSheet(sty);
setCursor(Qt::PointingHandCursor);
}
void MyVerticalScrollBar::mousePressEvent(QMouseEvent * event)
{
if (event->button() == Qt::LeftButton)
{
//获取滑块范围
QStyleOptionSlider opt;
initStyleOption(&opt);
QRect sliderHandleRect = style()->subControlRect(QStyle::CC_ScrollBar, &opt, QStyle::SC_ScrollBarSlider, this);
if (!sliderHandleRect.contains(event->pos()))
{
//获取当前点击位置,得到的这个鼠标坐标是相对于当前QSlider的坐标
int currentY = event->pos().y();
//获取当前点击的位置占整个Slider的百分比
double per = currentY *1.0 /this->height();
//利用算得的百分比得到具体数字
int value = per*(this->maximum() - this->minimum()) + this->minimum();
//设定滑动条位置
setValue(value);
}
}
QScrollBar::mousePressEvent(event);
}
绑定滚动条
与QGraphicsView的滚动条绑定,并关联滚动条值改变时触发slip函数
connect(mCellGraphicsView->horizontalScrollBar(), &QScrollBar::valueChanged, this, &MainWindow::slip);
connect(mCellGraphicsView->verticalScrollBar(), &QScrollBar::valueChanged, this, &MainWindow::slip);
connect(ui->verticalScrollBar, &QScrollBar::valueChanged, mCellGraphicsView->verticalScrollBar(), &QScrollBar::setValue);
connect(ui->horizontalScrollBar, &QScrollBar::valueChanged, mCellGraphicsView->horizontalScrollBar(), &QScrollBar::setValue);
connect(mCellGraphicsView->verticalScrollBar(), &QScrollBar::rangeChanged, this, [=](int min, int max)
{
ui->verticalScrollBar->setRange(min, max);
if(max>0) ui->verticalScrollBar->show();
else ui->verticalScrollBar->hide();
});
connect(mCellGraphicsView->horizontalScrollBar(), &QScrollBar::rangeChanged, this, [=](int min, int max)
{
ui->horizontalScrollBar->setRange(min, max);
if(max>0) ui->horizontalScrollBar->show();
else ui->horizontalScrollBar->hide();
});
(4) 滚动时移动格子
下滑:窗口只能显示一部分的格子,所以在滚动条下滑的时候不需要立刻移动,目的是为了让窗口一直能看到格子,所以偏移量达到一定值的时候将最上方一行的组移动到最下方填补就可以实现
上滑:需要判断目前上方还有没有格子,如果没有就会出现空白,所以当上方不足一组高度的时候需要将最底下一行的组移动到最上面,并且需要有边界判断,不能移出界
右滑:同理下滑,不需要判断边界
左滑:同理上滑,需要保证有左边有一组格子,并且要判断边界
void MainWindow::slip()
{
int verticalCurrentY = mCellGraphicsView->verticalScrollBar()->value();
int horizontalCurrentX = mCellGraphicsView->horizontalScrollBar()->value();
int differenceY = verticalCurrentY-oldVerticalCurrentY;
int differenceX = horizontalCurrentX-oldHorizontalCurrentX;
int totalHeight = groupsRowCount*colCount*ITEM_SIZE; //6*10*ITEM_SIZE
int totalWidth = groupsColCount*rowCount*ITEM_SIZE; //10*10*ITEM_SIZE
//下滑
if(differenceY>0)
{
int needToSlip = totalHeight/2; // 根据窗口大小调节,如果出现空白就调小
while(verticalCurrentY-oldFrontY>=needToSlip && verticalCurrentY-oldFrontY<totalHeight)
{
slipDown();
}
if(verticalCurrentY-oldFrontY>=totalHeight)//大于整体格子的高度直接整体移动
{
graphicMoveY();//整体移动
}
}
//上滑
else if(differenceY<0)
{
//不够一组距离的时候补充一组
while(verticalCurrentY-oldFrontY<=ITEM_SIZE*colCount && oldFrontY-verticalCurrentY<totalHeight && oldFrontY>0)
{
slipUp();
}
if (oldFrontY-verticalCurrentY>=totalHeight)
{
graphicMoveY();//整体移动
}
}
//右滑
else if(differenceX>0)
{
int needToSlip = totalWidth/2; // 根据窗口大小调节,如果出现空白就调小
while(horizontalCurrentX-oldLeftX>=needToSlip && horizontalCurrentX-oldLeftX<totalWidth)
{
slipRight();
}
if (horizontalCurrentX-oldLeftX>=totalWidth)//大于整体格子的宽度直接整体移动
{
graphicMoveX();//整体移动
}
}
//左滑
else if(differenceX<0)
{
//不够一组距离的时候补充一组
while(horizontalCurrentX-oldLeftX<(ITEM_SIZE*rowCount) && oldLeftX-horizontalCurrentX<totalWidth && oldLeftX>0)
{
slipLeft();
}
if (oldLeftX-horizontalCurrentX>=totalWidth)
{
graphicMoveX();//整体移动
}
}
oldVerticalCurrentY = verticalCurrentY;
oldHorizontalCurrentX = horizontalCurrentX;
}
(5)整体移动
基本逻辑
点击的滚动条坐标很有可能不是整体格子的宽度或者高度,所以需要先移动到宽度或者高度的整数倍,不然的话就会出现有空白的地方。移动到整数倍的时候也会有可能出现窗口显示出空白的地方,所以整体移动完需要判断一下是否需要移动组来补齐空白。
边界判断
如果点击的位置小于整体大小,就将整体移动到起点,不要移出去
3.运行结果
代码比较多,这里只展示大概思路和部分代码,可以下载demo的源码
https://download.csdn.net/download/weixin_64319089/89461578
4.项目展示
demo实现了界面的刷新,在界面移动的时候加上数据的刷新即可实现高性能低内存的百万级数据刷新