QML GridView 布局:网格排布与排列方向

0 阅读3分钟

GridView 和 ListView 的使用方式有点像,通过 model 提供数据、delegate 定义每一项的外观、样式。区别在于 GridView 把所有项构建成了一个二维网格,横向、竖向两个方向都可以随意排列。

这篇先用最基础的写法展示 GridView 如何构建二维网格,接着展示 flow、layoutDirection、verticalLayoutDirection 三个方向属性是什么效果。

基础网格

1.gif

演示代码

import QtQuick
import QtQuick.Controls
import QtQuick.Layouts

FadeInAnimation {
    ColumnLayout {
        anchors.fill: parent
        anchors.margins: 20
        spacing: 15

        // ... 省略标题组件 TitleSeparator ...

        // 内容卡片
        Rectangle {
            Layout.fillWidth: true
            Layout.fillHeight: true
            color: "#fafafa"
            radius: 8
            border.color: "#e0e0e0"
            border.width: 1

            GridView {
                id: grid
                anchors.fill: parent
                anchors.margins: 10
                clip: true

                cellWidth: 80
                cellHeight: 80

                model: ListModel {
                    ListElement { name: "音乐" }
                    ListElement { name: "电影" }
                    ListElement { name: "读书" }
                    ListElement { name: "旅行" }
                    ListElement { name: "摄影" }
                    ListElement { name: "编程" }
                    ListElement { name: "运动" }
                    ListElement { name: "美食" }
                    ListElement { name: "游戏" }
                    ListElement { name: "绘画" }
                    ListElement { name: "烘焙" }
                    ListElement { name: "露营" }
                }

                // delegate 占满整个单元格,内部卡片再留出间距
                delegate: Item {
                    required property int index
                    required property string name

                    width: grid.cellWidth
                    height: grid.cellHeight

                    Rectangle {
                        anchors.fill: parent
                        anchors.margins: 4
                        radius: 8
                        color: "#FFFFFF"
                        border.color: "#e0e0e0"
                        border.width: 1

                        Column {
                            anchors.centerIn: parent
                            spacing: 4

                            Text {
                                anchors.horizontalCenter: parent.horizontalCenter
                                text: name
                                color: "#333"
                                font.pointSize: 12
                                font.bold: true
                            }

                            Text {
                                anchors.horizontalCenter: parent.horizontalCenter
                                text: index + 1
                                color: "#999"
                                font.pointSize: 9
                            }
                        }
                    }
                }
            }
        }

        // 状态提示
        Text {
            Layout.fillWidth: true
            text: "共 " + grid.count + " 项"
            font.pointSize: 10
            color: "#666"
        }
    }
}

格子的大小由 cellWidth 和 cellHeight 决定,delegate 的根元素把宽高直接绑到这两个值上,就等于占满整个格子。

卡片本身另套一层 Rectangle,用 anchors.margins 留出 4px 间隙,相邻两项才不会挤在一起。

model 用 ListModel 描述,每个ListElement定义一个 name 字段,delegate 里用 required property string name 取出来。

index是网格自动给每项编的位置,从0开始,这里加1后显示在名字下面,方便对照顺序。

count是只读属性,直接读就是总项数,底部那行统计就是这么来的。

适用场景:应用宫格入口、兴趣标签墙、图片缩略图总览。

排列方向

同样的网格,用三个开关切换 flow、layoutDirection、verticalLayoutDirection,能拼出好几种排布。

2.gif

演示代码

import QtQuick
import QtQuick.Controls
import QtQuick.Layouts

FadeInAnimation {
    ColumnLayout {
        anchors.fill: parent
        anchors.margins: 20
        spacing: 15

        // ... 省略标题组件 TitleSeparator ...

        // 控制条
        RowLayout {
            Layout.fillWidth: true
            spacing: 12

            // flow 可选 FlowLeftToRight / FlowTopToBottom
            CheckBox {
                id: flowCheck
                text: "先竖排"
                font.pointSize: 10
            }

            // layoutDirection 可选 LeftToRight / RightToLeft
            CheckBox {
                id: rtlCheck
                text: "水平反向"
                font.pointSize: 10
            }

            // verticalLayoutDirection 可选 TopToBottom / BottomToTop
            CheckBox {
                id: bottomUpCheck
                text: "垂直反向"
                font.pointSize: 10
            }

            Item { Layout.fillWidth: true }

            Text {
                text: "共 " + grid.count + " 项"
                font.pointSize: 10
                color: "#666"
            }
        }

        // 内容卡片
        Rectangle {
            Layout.fillWidth: true
            Layout.fillHeight: true
            color: "#fafafa"
            radius: 8
            border.color: "#e0e0e0"
            border.width: 1

            GridView {
                id: grid
                anchors.fill: parent
                anchors.margins: 10
                clip: true

                cellWidth: 70
                cellHeight: 60

                flow: flowCheck.checked ? GridView.FlowTopToBottom : GridView.FlowLeftToRight
                layoutDirection: rtlCheck.checked ? Qt.RightToLeft : Qt.LeftToRight
                verticalLayoutDirection: bottomUpCheck.checked ? GridView.BottomToTop : GridView.TopToBottom

                model: 40

                delegate: Item {
                    required property int index

                    width: grid.cellWidth
                    height: grid.cellHeight

                    Rectangle {
                        anchors.fill: parent
                        anchors.margins: 4
                        radius: 8
                        color: "#FFFFFF"
                        border.color: "#e0e0e0"
                        border.width: 1

                        Text {
                            anchors.centerIn: parent
                            text: index + 1
                            color: "#333"
                            font.pointSize: 12
                            font.bold: true
                        }
                    }
                }
            }
        }
    }
}

flow管主方向,GridView.FlowLeftToRight 是默认值,先横着排满一行再换下一行;换成 GridView.FlowTopToBottom 后变成先竖着排满一列再排下一列,滚动方向也跟着从纵向换成横向。

layoutDirection 管水平方向,切到 Qt.RightToLeft 时格子从右边开始摆放;verticalLayoutDirection 管垂直方向,GridView.BottomToTop 让内容从底部往上填。

verticalLayoutDirection有个地方要留意:它只有在内容超过一屏时才看得出来。内容没占满视图的时候BottomToTop 和 TopToBottom 显示得一模一样,所以这个例子把项数放到了 40,专门撑出纵向滚动,勾上「垂直反向」才能看到内容从底部开始排。另外 model 这里直接写了数字 40,是 GridView 的简写方式,表示有 40 项,delegate 里只能拿到 index。

三个方向属性

属性控制什么取值
flow先横排还是先竖排GridView.FlowLeftToRight / GridView.FlowTopToBottom
layoutDirection水平排列方向Qt.LeftToRight / Qt.RightToLeft
verticalLayoutDirection垂直排列方向GridView.TopToBottom / GridView.BottomToTop

已验证环境: