QML用ListView实现带section的GridView

QML自带的GridView只能定义delegate,没有section,类似手机相册带时间分组标签的样式就没法做。最简单的方式就是组合ListView+GridView,或者ListView+Flow,但是嵌套View时,子级View一般是完全展开的,只显示该分组几行就得把该分组全部加载了,这样就没有了View在需要时才实例化Item的优势,所以最好还是在单层View实现最终效果。

QML的ListView支持section,可以自定义分组样式,所以可以通过ListView来实现带section的GridView。当然,你也可以直接修改GridView的C++源码给他加上section。

ListView实现GridView的效果无非就是把多行显示到一行。可以让ListView某一行撑高,其他行高度为0;也可以平均分配一行高度。因为delegate会被ListView控制位置,所以相对位置可以在内部嵌套然后设置偏移量,使之看起来在一行上。

本文完整代码:

https://github.com/gongjianbo/MyTestCode/tree/master/Qml/TestQml_20240205_SectionGrid

先实现一个不带section的GridView:

import QtQuick 2.15
import QtQuick.Controls 2.15// ListView 实现 GridView 效果
Rectangle {id: controlborder.color: "black"// 边距property int padding: 10// Item 间隔property int spacing: 10// Item 宽property int itemWidth: 300// Item 高property int itemHeight: 100// Delegate 宽property int delegateWidth: itemWidth + spacing// Delegate 高property int delegateHeight: itemHeight + spacing// 列数根据可视宽度和 Item 宽度计算property int columns: (list_view.width + spacing - padding) / delegateWidth < 1? 1: (list_view.width + spacing - padding) / delegateWidth// 套一层 Item clip 剪去 ListView 尾巴上多余的部分不显示出来Item {anchors.fill: parentanchors.margins: control.padding// 右侧留下滚动条位置,所以 columns 里 list_view.width 要减一个 paddinganchors.rightMargin: 0clip: trueListView {id: list_viewwidth: parent.width// 高度多一个 delegate 放置 footer,防止末尾的一行滑倒底部后隐藏// 多出来的一部分会被外部 Item clip 掉height: parent.height + control.delegateHeight + control.spacingflickableDirection: Flickable.HorizontalAndVerticalFlickboundsBehavior: Flickable.StopAtBoundsheaderPositioning: ListView.OverlayHeader// 底部多一个 footer 撑高可显示范围,防止末尾的一行滑倒底部后隐藏footerPositioning: ListView.OverlayFooterScrollBar.vertical: ScrollBar {// padding 加上 ListView 多出来的一部分bottomPadding: padding + (control.delegateHeight + control.spacing)// 常驻显示只是方便调试policy: ScrollBar.AlwaysOn}footer: Item {// 竖向的 ListView 宽度无所谓width: control.delegateWidth// 高度大于等于 delegate 高度才能保证显示height: control.delegateHeight}// 奇数方便测试model: 31delegate: Item {width: control.delegateWidth// 每行第一个 Item 有高度,后面的没高度,这样就能排列到一行// 因为 0 高度 Item 在末尾,超出范围 visible 就置为 false 了,所以才需要 footer 撑高多显示一行的内容// delegate 高度不一致会导致滚动条滚动时长度变化height: (model.index % control.columns === 0) ? control.delegateHeight : 0// 放置真正的内容Rectangle {// 根据列号计算 xx: (model.index % control.columns) * control.delegateWidth// 负高度就能和每行第一个的 y 一样y: (model.index % control.columns !== 0) ? -control.delegateHeight : 0width: control.itemWidthheight: control.itemHeightborder.color: "black"Text {anchors.centerIn: parent// 显示行号列号text: "(%1,%2)".arg(parseInt(model.index / control.columns)).arg(model.index % control.columns)}}}}}
}

如果要带section,就得每个分组有单独的index,这样才能计算分组内的行列号,需要我们自定义一个ListModel:

#pragma once
#include <QAbstractListModel>// 实际数据
struct DataInfo
{int value;// 本例用日期来分组QString date;
};// 分组信息,如 index
struct SectionInfo
{int index;
};class DataModel : public QAbstractListModel
{Q_OBJECT
private:enum ModelRole {ValueRole = Qt::UserRole, GroupNameRole, GroupIndexRole};
public:explicit DataModel(QObject *parent = nullptr);// Model 需要实现的必要接口int rowCount(const QModelIndex &parent = QModelIndex()) const override;QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;QHash<int, QByteArray> roleNames() const override;// 在头部添加一个数据Q_INVOKABLE void appendData(int value, const QString &date);// 根据 model.index 删除一个数据Q_INVOKABLE void removeData(int index);// 加点测试数据void test();private:QVector<DataInfo> datas;QVector<SectionInfo> inners;
};DataModel::DataModel(QObject *parent): QAbstractListModel(parent)
{test();
}int DataModel::rowCount(const QModelIndex &parent) const
{if (parent.isValid())return 0;return datas.size();
}QVariant DataModel::data(const QModelIndex &index, int role) const
{if (!index.isValid())return QVariant();auto &&item = datas.at(index.row());auto &&inner = inners.at(index.row());switch (role){case ValueRole: return item.value;case GroupNameRole: return item.date;case GroupIndexRole: return inner.index;}return QVariant();
}QHash<int, QByteArray> DataModel::roleNames() const
{static QHash<int, QByteArray> names{{ValueRole, "value"}, {GroupNameRole, "groupName"}, {GroupIndexRole, "groupIndex"}};return names;
}void DataModel::appendData(int value, const QString &date)
{// 先判断分组是否相同if (datas.isEmpty() || datas.first().date != date) {// 没有该组,新建一个分组DataInfo item;item.value = value;item.date = date;SectionInfo inner;inner.index = 0;beginInsertRows(QModelIndex(), 0, 0);datas.push_front(item);inners.push_front(inner);endInsertRows();} else {// 已有该组,插入并移动该组后面的 ItemDataInfo item;item.value = value;item.date = date;SectionInfo inner;inner.index = 0;beginInsertRows(QModelIndex(), 0, 0);datas.push_front(item);inners.push_front(inner);endInsertRows();// 刷新该组int update_count = 0;// 0 是新插入,1 是旧 0for (int i = 1; i < inners.size(); i++) {auto &&inner_i = inners[i];if (i > 1 && inner_i.index == 0)break;inner_i.index = i;update_count ++;}emit dataChanged(QAbstractListModel::index(1, 0), QAbstractListModel::index(1 + update_count, 0));}
}void DataModel::removeData(int index)
{if (index < 0 || index >= datas.size())return;beginRemoveRows(QModelIndex(), index, index);datas.removeAt(index);inners.removeAt(index);endRemoveRows();int update_count = 0;for (int i = index; i < inners.size(); i++) {auto &&inner_i = inners[i];if (inner_i.index == 0)break;inner_i.index -= 1;update_count ++;}if (update_count > 0) {emit dataChanged(QAbstractListModel::index(index, 0), QAbstractListModel::index(index + update_count, 0));}
}void DataModel::test()
{DataInfo item;SectionInfo inner;item.date = "2022.2.22";for (int i = 0; i < 11; i++){item.value = i + 1;datas.push_back(item);inner.index = i;inners.push_back(inner);}item.date = "2010.10.10";for (int i = 0; i < 21; i++){item.value = i + 1;datas.push_back(item);inner.index = i;inners.push_back(inner);}item.date = "1999.9.9";for (int i = 0; i < 31; i++){item.value = i + 1;datas.push_back(item);inner.index = i;inners.push_back(inner);}
}
import QtQuick 2.15
import QtQuick.Controls 2.15
import Test 1.0// ListView 实现带 section 分组的 GridView
Rectangle {id: controlborder.color: "black"// 边距property int padding: 10// Item 间隔property int spacing: 10// Item 宽property int itemWidth: 300// Item 高property int itemHeight: 100// Delegate 宽property int delegateWidth: itemWidth + spacing// Delegate 高property int delegateHeight: itemHeight + spacing// 列数根据可视宽度和 Item 宽度计算property int columns: (list_view.width + spacing - padding) / delegateWidth < 1? 1: (list_view.width + spacing - padding) / delegateWidth// 套一层 Item clip 剪去 ListView 尾巴上多余的部分不显示出来Item {anchors.fill: parentanchors.margins: control.padding// 右侧留下滚动条位置,所以 columns 里 list_view.width 要减一个 paddinganchors.rightMargin: 0clip: trueListView {id: list_viewwidth: parent.width// 高度多一个 delegate 放置 footer,防止末尾的一行滑倒底部后隐藏// 多出来的一部分会被外部 Item clip 掉height: parent.height + control.delegateHeight + control.spacingflickableDirection: Flickable.HorizontalAndVerticalFlickboundsBehavior: Flickable.StopAtBoundsheaderPositioning: ListView.OverlayHeader// 底部多一个 footer 撑高可显示范围,防止末尾的一行滑倒底部后隐藏footerPositioning: ListView.OverlayFooterScrollBar.vertical: ScrollBar {// padding 加上 ListView 多出来的一部分bottomPadding: padding + (control.delegateHeight + control.spacing)// 常驻显示只是方便调试policy: ScrollBar.AlwaysOn}footer: Item {// 竖向的 ListView 宽度无所谓width: control.delegateWidth// 高度大于等于 delegate 高度才能保证显示height: control.delegateHeight}model: DataModel {id: list_model}section {property: "groupName"criteria: ViewSection.FullStringdelegate: Item {width: list_view.width - control.paddingheight: 40Rectangle {width: parent.widthheight: parent.height - control.spacingcolor: "gray"Text {anchors.centerIn: parenttext: sectioncolor: "white"}}}labelPositioning: ViewSection.InlineLabels}delegate: Item {width: control.delegateWidth// 每行第一个 Item 有高度,后面的没高度,这样就能排列到一行// 因为 0 高度 Item 在末尾,超出范围 visible 就置为 false 了,所以才需要 footer 撑高多显示一行的内容// delegate 高度不一致会导致滚动条滚动时长度变化height: (model.groupIndex % control.columns === 0) ? control.delegateHeight : 0// 放置真正的内容Rectangle {// 根据列号计算 xx: (model.groupIndex % control.columns) * control.delegateWidth// 负高度就能和每行第一个的 y 一样y: (model.groupIndex % control.columns !== 0) ? -control.delegateHeight : 0width: control.itemWidthheight: control.itemHeightborder.color: "black"Text {anchors.centerIn: parent// 显示行号列号text: "(%1,%2) - %3".arg(parseInt(model.groupIndex / control.columns)).arg(model.groupIndex % control.columns).arg(model.value)}Column {x: 12anchors.verticalCenter: parent.verticalCenterspacing: 12Button {width: 100height: 30text: "append"onClicked: {list_model.appendData(model.value, "2222.2.22")}}Button {width: 100height: 30text: "remove"onClicked: {list_model.removeData(model.index)}}}}} // end delegate Item} // end ListView}
}

这里只是实现了一个简单的效果,很多细节还需要调整。

通过添加更多的属性和计算,也可以实现带section的FlowView,即Item的宽高不是固定大小,整体为流式布局。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/455372.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【VUE】UniAPP之uview组件库,自定义tag封装,支持添加u-icon图标

组件代码 <template><view class"tag" :class"[props.mode, props.shape]"><slot name"left"><!-- icon图标 没有传入图标时不显示 --><u-icon v-if"props.icon ! " :name"props.icon" :color&…

【数据分享】1929-2023年全球站点的逐月平均能见度(Shp\Excel\免费获取)

气象数据是在各项研究中都经常使用的数据&#xff0c;气象指标包括气温、风速、降水、能见度等指标&#xff0c;说到气象数据&#xff0c;最详细的气象数据是具体到气象监测站点的数据&#xff01; 之前我们分享过1929-2023年全球气象站点的逐月平均气温数据、逐月最高气温数据…

说说vue的diff算法

Vue的diff算法 是什么比较方式 – 深度优先&#xff0c;同层比较 比较只会在同层级进行&#xff0c;不会跨层级比较比较的过程中&#xff0c;循环从两边向中间收拢 diff 算法更新的例子原理分析 patchpatchVnodeupdateChildren 小结 Vue的diff算法 此文章&#xff0c;来源于印…

Could not connect to Redis at 127.0.0.1:6379:由于目标计算机积极拒绝,无法连接...问题解决方法之一

一、问题描述 将Redis压缩包解压后&#xff0c;安装Redis过程中出现问题Could not connect to Redis at 127.0.0.1:6379:由于目标计算机积极拒绝&#xff0c;无法连接... 官网windows下redis开机自启动的指令如下&#xff1a; 1、在redis目录下执行 redis-server --service-in…

Java面向对象 创建类 创建对象

目录 创建类类的属性类的方法实例分析 创建对象创建Test类测试分析 创建类 类的属性 属性用于定义该类或该类对象包含的数据或者说静态特征。属性作用范围是整个类体。 属性定义格式&#xff1a; [修饰符] 属性类型 属性名 [默认值] ;类的方法 方法用于定义该类或该类实例…

释放资源的方式

try - catch - finally finally代码区的特点&#xff1a;无论try中的程序是正常执行力&#xff0c;还是出现了异常&#xff0c;最后都一定会执行finally区&#xff0c;除非JVM终止。 作用&#xff1a;一般用于在程序执行完成后进行资源的释放操作&#xff08;专业级做法&#x…

用 Delphi 程序调用 Python 代码画曲线图

用 Python 的库画图 Python 代码如下&#xff1a; import matplotlib.pyplot as pltsquares [1, 4, 9, 16, 25]; plt.plot(squares); plt.grid(True) # 网格线 plt.show(); # 这句话会弹出个窗口出来&#xff0c;里面是上述数据的曲线。 把以上代码&#xff0c;放进 PyS…

对于模糊查询的SQL,怎么优先返回等值记录

说明&#xff1a;记录一次SQL改进的方法&#xff0c;希望能对大家有启发。 场景 前端项目有一个输入框&#xff0c;根据输入的银行名称&#xff0c;去模糊查询对应的数据库表&#xff0c;返回结果集&#xff0c;显示到下拉列表中。 因为银行名称字段包括了分行名&#xff0c…

如何进行游戏服务器的负载均衡和扩展性设计?

​在进行游戏服务器的负载均衡和扩展性设计时&#xff0c;需要考虑多个方面&#xff0c;以确保服务器的稳定性和可扩展性。以下是一些关键的步骤和考虑因素&#xff1a; 负载均衡的需求分析 在进行负载均衡设计之前&#xff0c;需要深入了解游戏服务器的负载特性和需求。这包括…

DevOps落地笔记-15|混沌工程:通过问题注入提高系统可靠性

上一课时介绍了通过搭建一套部署流水线&#xff0c;高效、可靠的将软件部署到测试环境以及生产环境。到目前为止&#xff0c;我们学习了从用户需求到软件部署到生产环境交付给用户的全过程。随着软件工程不断发展&#xff0c;近几年&#xff0c;出现了一种新的实践&#xff0c;…

idea(2023.3.3 ) spring boot热部署,修改热部署延迟时间

1、添加依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><optional>true</optional> </dependency>载入依赖 2、设置编辑器 设置两个选项 设置热部署更新延迟时…

无向图-树的重心-DFS求解

思路&#xff1a; 本题的本质是树的dfs&#xff0c; 每次dfs可以确定以u为重心的最大连通块的节点数&#xff0c;并且更新一下ans。 也就是说&#xff0c;dfs并不直接返回答案&#xff0c;而是在每次更新中迭代一次答案。 这样的套路会经常用到&#xff0c;在 树的dfs 题目中…