QML Drawer 抽屉导航详解

Drawer 是 Qt Quick Controls 里的一个抽屉组件,就像手机 App 从屏幕边拉出来的那种侧栏:会从窗口某一条边滑出,同时盖上遮罩。

最常见的就有底部向上弹的菜单、左侧拉开的主菜单、顶部拉下来的通知。至于从哪边出来,由一个表示"边缘"的属性来决定,代码里叫 edge,取四个方向之一。

这篇文章先把这几个关键属性介绍一下,再用四个例子把四个方向的抽屉演示一遍。

基础概念

抽屉最常用的设置就这几个:

  • edge(边缘,属性) ------告诉抽屉从哪条边滑出来。取值有四个方向:Qt.TopEdge(从上往下)、Qt.RightEdge(从右往左)、Qt.BottomEdge(从下往上)、Qt.LeftEdge(从左往右)。
  • modal(模态,属性) ------设成 true 表示"模态",抽屉打开时会盖上一层遮罩,遮罩之外的界面点不动,只能先处理完抽屉再关掉。
  • open() / close()(打开 / 关闭,两个方法) ------open() 让抽屉弹出、close() 收起;另外点击遮罩、按 Esc 键也会自动关闭。
  • parent: Overlay.overlay(父级,挂在窗口覆盖层上) ------把抽屉挂到窗口最上层的"覆盖层"里。原因后面会讲:这样抽屉的边界才会贴着窗口边缘,而不是被别的容器框住。
qml 复制代码
import QtQuick
import QtQuick.Controls

Drawer {
    parent: Overlay.overlay
    width: Overlay.overlay.width
    height: 200
    edge: Qt.BottomEdge
    modal: true
    Text { text: "我是底部抽屉"; anchors.centerIn: parent }
}

这个最小例子跑起来后,一个底部抽屉就弹出了:从下往上滑出、带着遮罩、点击别处会自动关闭。剩下的工作,就是往抽屉里填充自己的内容。方向怎么选?经验是:想从顶部拉下一整条(比如消息、公告)用顶部;从底部顶上来一排快捷按钮用底部。

Demo 1 顶部通知抽屉

从顶边滑下一条横贯整个窗口的通知栏,主要展示了五条不同类型的消息------成功、失败、系统消息分别用绿、红、蓝的圆点标出,点任意一条就会收起。

演示代码

qml 复制代码
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts

// 顶部抽屉:消息通知列表(edge: Qt.TopEdge)
Drawer {
    id: root
    parent: Overlay.overlay          // 挂到窗口 overlay,滑动边界即窗口边缘
    width: Overlay.overlay.width
    height: Math.min(360, Overlay.overlay.height)  // 限高:不能比窗口还高
    edge: Qt.TopEdge
    modal: true

    background: Rectangle { color: "#e6e6e6" }

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

        Text { text: "消息通知"; font.pointSize: 13; font.bold: true; color: "#333" }
        Rectangle { Layout.fillWidth: true; Layout.preferredHeight: 1; color: "#ccc" }

        ListModel {
            id: msgModel
            ListElement { type: "opt_ok";    msg: "已保存,内容将在刷新后生效" }
            ListElement { type: "opt_ok";    msg: "密码已重置,请使用新密码进行登录" }
            ListElement { type: "opt_error"; msg: "网络开小差,请连接后重试" }
            ListElement { type: "sys";       msg: "新版本可用:V2.3.1,请立即更新体验最新功能" }
            ListElement { type: "sys";       msg: "维护通知:系统将于01-22 00:00-02:00升级,期间暂停服务" }
        }

        Repeater {
            model: msgModel
            Rectangle {
                required property string type
                required property string msg
                Layout.fillWidth: true
                Layout.preferredHeight: 40
                color: "#fff"
                radius: 6
                MouseArea { anchors.fill: parent; onClicked: root.close() }
                RowLayout {
                    anchors.fill: parent
                    anchors.margins: 10
                    spacing: 6
                    Rectangle {   // 类型驱动颜色:成功绿 / 失败红 / 系统蓝
                        Layout.preferredWidth: 16
                        Layout.preferredHeight: 16
                        radius: 8
                        color: {
                            if (type === "opt_ok") return "#2ecc71"
                            else if (type === "opt_error") return "#e74c3c"
                            return "#3498db"
                        }
                    }
                    Text { text: msg; font.pointSize: 11; color: "#333"; Layout.fillWidth: true }
                }
            }
        }
        Item { Layout.fillHeight: true }
    }
}

关键逻辑解析

这个抽屉挂在窗口最上层的覆盖层里,所以能以整个窗口为参照撑满宽度------就是前面基础概念里那行 parent 设置的作用。高度加了个上限,窗口再小也不会溢出。

每条消息按类型染色:成功的绿色、失败的红色、系统消息用蓝色,扫一眼就能区分。通知类抽屉的语义就是"看一眼就收",点任意一条都会自动收起。

适用场景:消息通知中心、系统公告、下拉式快捷提示。

Demo 2 底部功能抽屉

从底边滑上来一条抽屉,高度是固定的,里面放着一个"常用功能"宫格,每个功能由圆形彩色图标和文字标签组成,点任意一个图标就收起。

演示代码

qml 复制代码
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts

// 底部抽屉:常用功能宫格(edge: Qt.BottomEdge),点击图标即收起
Drawer {
    id: root
    parent: Overlay.overlay
    width: Overlay.overlay.width
    height: 200
    edge: Qt.BottomEdge
    modal: true

    background: Rectangle { color: "#e6e6e6"; border.color: "#ccc" }

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

        Text { text: "常用功能"; color: "#333"; font.pointSize: 12; font.bold: true }

        RowLayout {
            Layout.fillWidth: true
            Layout.fillHeight: true
            spacing: 20

            CustomModel { id: quickModel }
            Repeater {
                model: quickModel
                Rectangle {
                    required property string cmIcon
                    required property string cmText
                    required property string cmColor
                    Layout.preferredWidth: 64
                    Layout.preferredHeight: 64
                    color: "#e6e6e6"
                    MouseArea { anchors.fill: parent; onClicked: root.close() }
                    ColumnLayout {
                        anchors.fill: parent
                        Rectangle {   // 40×40 圆形色块 + 内嵌图标
                            Layout.preferredWidth: 40
                            Layout.preferredHeight: 40
                            radius: 20
                            color: cmColor
                            Layout.alignment: Qt.AlignHCenter
                            Image { source: cmIcon; sourceSize.width: 24; sourceSize.height: 24; anchors.centerIn: parent }
                        }
                        Text {
                            Layout.alignment: Qt.AlignHCenter
                            text: cmText
                            color: "#6a6a6a"
                            font.bold: true
                            font.pointSize: 11
                        }
                    }
                }
            }
        }
        Item { Layout.fillHeight: true }
    }
}

CustomModel 是共享的数据源组件,提供"图标 + 文本 + 颜色"三字段,左侧/右侧抽屉也复用它:

qml 复制代码
import QtQuick

ListModel {
    ListElement { cmIcon: "qrc:/images/home.svg";     cmText: "首页";     cmColor: "#e74c3c" }
    ListElement { cmIcon: "qrc:/images/stats.svg";    cmText: "数据统计"; cmColor: "#3498db" }
    ListElement { cmIcon: "qrc:/images/user.svg";     cmText: "用户管理"; cmColor: "#2ecc71" }
    ListElement { cmIcon: "qrc:/images/settings.svg"; cmText: "系统设置"; cmColor: "#f39c12" }
    ListElement { cmIcon: "qrc:/images/app.svg";      cmText: "应用中心"; cmColor: "#9b59b6" }
    ListElement { cmIcon: "qrc:/images/msg.svg";      cmText: "消息通知"; cmColor: "#e67e22" }
}

关键逻辑解析

底部抽屉高度固定,功能项横着排------对比顶部通知是竖着排,从下往上顶上来顺势横向展开,观感上更顺。

每个功能由圆形色块(内嵌小图标)加一行说明上下叠放。图标、文字、颜色这套数据单独存一份,四个方向的抽屉共用,改一处就能同步。快捷入口嘛,点到就关,所以点图标立即收起。

适用场景:移动端底部操作面板、快捷工具宫格、分享菜单。

Demo 3 左侧导航抽屉

从左侧滑出的是一整块深色主菜单。顶部显示用户信息,中间排着几个带图标的导航项,其中"消息通知"还带一个小红点提醒,抽屉外缘则放了一个收起按钮。点中某个导航项,它会高亮选中、把选中的文案告诉外部,再自动收起。

演示代码

qml 复制代码
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts

// 左侧抽屉:主导航菜单(edge: Qt.LeftEdge),选中项信号通知外部
Drawer {
    id: root
    parent: Overlay.overlay
    width: 260
    height: Overlay.overlay.height
    edge: Qt.LeftEdge
    modal: true

    property string selectedItem: "首页"   // 当前选中项(高亮依据)
    signal itemSelected(string text)       // 点击菜单项时向外通知

    background: Rectangle {
        color: "#2c3e50"
        // 抽屉外侧的收起把手
        Rectangle {
            anchors.top: parent.top
            anchors.left: parent.right
            width: 20
            height: 60
            color: "#34495e"
            Text { anchors.centerIn: parent; text: "收\n起"; wrapMode: Text.Wrap; font.pointSize: 9; color: "white" }
            MouseArea { anchors.fill: parent; onClicked: root.close() }
        }
    }

    ColumnLayout {
        anchors.fill: parent
        anchors.margins: 20
        spacing: 10

        // 用户信息区域
        Rectangle {
            Layout.fillWidth: true
            Layout.preferredHeight: 80
            color: "transparent"
            RowLayout {
                anchors.fill: parent
                spacing: 10
                Rectangle {
                    Layout.preferredWidth: 50
                    Layout.preferredHeight: 50
                    radius: 25
                    color: "#3498db"
                    Label { text: "U"; font.pointSize: 16; color: "white"; anchors.centerIn: parent }
                }
                ColumnLayout {
                    Layout.fillWidth: true
                    spacing: 5
                    Label { text: "用户名"; font.pointSize: 9; color: "white"; font.bold: true }
                    Label { text: "user@example.com"; font.pointSize: 9; color: "#bdc3c7" }
                }
            }
        }
        Rectangle { Layout.fillWidth: true; Layout.preferredHeight: 1; color: "#34495e" }

        CustomModel { id: navModel }
        Repeater {
            model: navModel
            Rectangle {
                required property string cmIcon
                required property string cmText
                required property string cmColor
                Layout.fillWidth: true
                Layout.preferredHeight: 50
                color: cmText === root.selectedItem ? "#34495e" : "transparent"
                radius: 8
                RowLayout {
                    anchors.fill: parent
                    anchors.margins: 10
                    spacing: 20
                    Rectangle {
                        Layout.preferredWidth: 30
                        Layout.preferredHeight: 30
                        radius: 15
                        color: cmColor
                        Image { source: cmIcon; sourceSize.width: 16; sourceSize.height: 16; anchors.centerIn: parent }
                    }
                    Label { text: cmText; font.pointSize: 11; color: "white"; Layout.fillWidth: true }
                    Rectangle {   // 红点提醒(仅"消息通知"项显示)
                        Layout.preferredWidth: 8
                        Layout.preferredHeight: 8
                        radius: 4
                        color: "#e74c3c"
                        visible: cmText === "消息通知"
                    }
                }
                MouseArea {
                    anchors.fill: parent
                    cursorShape: Qt.PointingHandCursor
                    onClicked: {
                        root.selectedItem = cmText   // 自己高亮
                        root.itemSelected(cmText)    // 通知外部
                        root.close()                 // 自动收起
                    }
                }
                Behavior on color { ColorAnimation { duration: 200 } }
            }
        }
        Item { Layout.fillHeight: true }
    }
}

关键逻辑解析

左侧这个抽屉沿整个窗口高度铺开、宽度固定,就是一条标准侧边栏的模样。

顶部是圆形头像、用户名和邮箱,下面隔一条细线------这是导航抽屉很常见的用户信息头部。往下是菜单项列表,点哪一项,那行就高亮(切换时带颜色过渡),同时把选中的文案通知给外部,然后自动收起,一套动作下来很顺手。"消息通知"那项旁边多了个小红点,表示有未读消息。

抽屉外缘还贴了个"收/起"小标签,不想点遮罩的时候,点它也能把抽屉收起来。

适用场景:App 主导航菜单、管理后台侧边菜单、需要"用户 + 菜单 + 提醒"一体的侧栏。

Demo 4 右侧设置抽屉

从右侧滑出一块浅色设置面板。顶部是标题和分隔线,下面每一行由彩色圆形图标、设置项名称和右侧的开关组成。它复用了左侧同一套数据,是把"设置页装进抽屉"的典型形态。

演示代码

qml 复制代码
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts

// 右侧抽屉:功能设置(edge: Qt.RightEdge),条目带开关
Drawer {
    id: root
    parent: Overlay.overlay
    width: 280
    height: Overlay.overlay.height
    edge: Qt.RightEdge
    modal: true

    background: Rectangle { color: "#e6e6e6"; border.color: "#ccc" }

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

        Text { text: "功能设置"; font.pointSize: 13; font.bold: true; color: "#333" }
        Rectangle { Layout.fillWidth: true; Layout.preferredHeight: 1; color: "#ccc" }

        CustomModel { id: settingsModel }
        Repeater {
            model: settingsModel
            Rectangle {
                required property string cmIcon
                required property string cmText
                required property string cmColor
                Layout.fillWidth: true
                Layout.preferredHeight: 50
                color: "#f5f5f5"
                radius: 6
                RowLayout {
                    anchors.fill: parent
                    anchors.margins: 10
                    spacing: 10
                    Rectangle {
                        Layout.preferredWidth: 30
                        Layout.preferredHeight: 30
                        radius: 15
                        color: cmColor
                        Image { source: cmIcon; sourceSize.width: 16; sourceSize.height: 16; anchors.centerIn: parent }
                    }
                    Label { text: cmText; font.pointSize: 11; color: "#333"; Layout.fillWidth: true }
                    Switch { checked: true }
                }
            }
        }
        Item { Layout.fillHeight: true }
    }
}

关键逻辑解析

右侧抽屉比左侧稍宽,因为每一行要同时放下彩色图标、设置项名称和右侧开关。它和左侧抽屉共用同一份数据,只是渲染成不同形态------左边画成可高亮的导航项,右边画成可拨动的设置行。开关的按下反馈由框架自带,不用额外写;如果想记住开关状态,在状态变化时写回数据就行。

配色上右侧用浅色底、每行圆角卡片,和左侧深色菜单形成"深色导航 / 浅色设置"的对比,也是常见的"左功能、右设置"配色习惯。

适用场景:设置面板、通知偏好、筛选条件等"临时滑出的配置区"。

四个方向对比

维度 顶部抽屉 底部抽屉 左侧抽屉 右侧抽屉
滑出方向(edge Qt.TopEdge(上→下) Qt.BottomEdge(下→上) Qt.LeftEdge(左→右) Qt.RightEdge(右→左)
尺寸形态 限高、随窗口 固定较矮 撑满高度、定宽 撑满高度、定宽
内容方向 纵向列表 横向宫格 纵向菜单 纵向设置
数据源 内置 ListModel CustomModel CustomModel CustomModel
色调 浅色 浅色 深色 浅色
交互 点消息关闭 点图标关闭 选中高亮 + 信号 Switch 开关

四个抽屉用的是同一套做法(挂到窗口覆盖层 + 定好尺寸 + 用 Repeater 批量渲染),差别只在滑出方向(edge)、尺寸和内容形态------每个抽屉都独立,可以整体替换。


已验证环境

相关推荐
≮傷£≯√12 小时前
QGraphicsScene(一) drawCAD 问题
qt
qq_401700411 天前
Qt QUrl 详解与代码示例
开发语言·数据库·qt
雨田言炎1 天前
Qt的常用类QList--序列数据管理
嵌入式硬件·qt
朽棘不雕2 天前
Qt项目代码解释
开发语言·qt
CVer儿2 天前
vs2022配置qt
开发语言·qt
kaixin_learn_qt_ing2 天前
Qt 打开目录多选对话框
qt
lqj_本人2 天前
WinPcap 鸿蒙 PC 适配全记录:从 NPF 原始抓包到 VPN_TUN 与 libpcap 双通路
qt·华为·harmonyos
Quz2 天前
QML StackView:三种入栈方式(Item、Component 与 URL)
qt
Quz2 天前
QML StackView:入栈、出栈、替换与批量操作
qt