QML TabBar 自定义样式与动态标签

默认 TabBar 样式不讨喜,到了实际的项目中多半要改样式。选中态要圆角背景、颜色要有过渡动画;标签数量和内容还得跟着数据动态增删。

这篇介绍两个简单的例子:

  • 自定义 TabButton 样式 --- 重写原始组件的 contentItembackground 属性,选中标签显示蓝底白字、带颜色过度动画
  • 动态添加/删除标签 --- 用 ListModel 管理标签和页面,增删数据行,界面能够自动同步

自定义 TabButton 样式

三个圆角胶囊按钮:没选中是灰字透明底,选中变蓝底白字,颜色变化带过渡效果。

演示代码

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

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

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

        TabBar {
            id: tabBar
            Layout.fillWidth: true
            background: Rectangle {
                color: "#f5f5f5"
                radius: 4
            }

            TabButton {
                text: "首页"
                width: implicitWidth + 20

                contentItem: Text {
                    text: parent.text
                    font.pixelSize: 14
                    color: parent.checked ? "#fff" : "#666"
                    horizontalAlignment: Text.AlignHCenter
                    verticalAlignment: Text.AlignVCenter
                }

                background: Rectangle {
                    implicitWidth: 60
                    implicitHeight: 36
                    color: parent.checked ? "#3498db" : "transparent"
                    radius: 4

                    Behavior on color {
                        ColorAnimation { duration: 150 }
                    }
                }
            }

            // ... 省略结构相同的「发现」「我的」两个 TabButton ...
        }

        Rectangle {
            Layout.fillWidth: true
            Layout.fillHeight: true
            color: Qt.rgba(0.95, 0.95, 0.95, 1)
            radius: 4

            Text {
                anchors.centerIn: parent
                text: "自定义样式:圆角背景 + 颜色动画"
                font.pointSize: 11
                color: "#666"
            }
        }
    }
}

关键逻辑解析

自定义 TabButton 从两部分下手:contentItem 管文字/图标,background 管底子 。QML 控件"内容与背景分离",想换样子不用改控件逻辑,只需替换这两块。示例里 contentItem 用一个 Text 显示标签文字,background 用一个圆角 Rectangle 当底。

选中态直接读 parent.checked 。TabButton 是 AbstractButton,自带 checked 属性,选中时为真。内容文字的 color: parent.checked ? "#fff" : "#666"、背景的 color: parent.checked ? "#3498db" : "transparent" 都挂在它上面,点选切换时样式跟着状态自动变,不用自己监听信号。

Behavior on color 让颜色切换"滑过去" 。选中不是瞬间跳色,而是 150ms 的 ColorAnimation 渐变。Behavior 是 QML 里给属性变化加动画的标准手法------checked 一翻转,color 从透明到蓝色就带上了过渡。

width: implicitWidth + 20 保留内容宽度。TabBar 默认把按钮均分占满,这里显式设宽度让按钮只比文字宽一点,三个胶囊并排靠左,不再拉伸满行。

implicitWidth/implicitHeight 定义背景的最小尺寸。background 里给 60×36 的隐式尺寸,保证 TabButton 即使文字很短也有可点的面积;设置的是隐式值而不是硬宽高,布局系统仍有缩放余地。

动态添加/删除标签

「添加标签」每次在末尾追加一个标签,「删除当前」删掉选中的那个(至少留一个),「清空全部」重置回单个标签,标签栏和内容页面同步增减。

演示代码

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

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

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

        RowLayout {
            Layout.fillWidth: true
            spacing: 10

            Button {
                text: "添加标签"
                onClicked: {
                    var newIndex = tabModel.count + 1
                    tabModel.append({"tabName": "标签" + newIndex, "pageColor": Qt.rgba(Math.random(), Math.random(), Math.random(), 1)})
                }
            }

            Button {
                text: "删除当前"
                enabled: tabModel.count > 1
                onClicked: {
                    if (tabBar.currentIndex >= 0 && tabModel.count > 1) {
                        tabModel.remove(tabBar.currentIndex)
                    }
                }
            }

            Button {
                text: "清空全部"
                onClicked: {
                    tabModel.clear()
                    tabModel.append({"tabName": "标签1", "pageColor": "#3498db"})
                }
            }
        }

        TabBar {
            id: tabBar
            Layout.fillWidth: true
            clip: true

            Repeater {
                model: ListModel {
                    id: tabModel
                    ListElement { tabName: "标签1" }
                    ListElement { tabName: "标签2" }
                }
                delegate: TabButton {
                    text: tabName
                    width: implicitWidth + 10
                }
            }
        }

        StackLayout {
            Layout.fillWidth: true
            Layout.fillHeight: true
            currentIndex: tabBar.currentIndex

            Repeater {
                model: tabModel
                delegate: Rectangle {
                    color: "#3498db"
                    Text {
                        anchors.centerIn: parent
                        text: tabName
                        color: "#fff"
                        font.pointSize: 18
                        font.bold: true
                    }
                }
            }
        }

        Text {
            text: "标签数量: " + tabModel.count
            font.pointSize: 11
            color: "#666"
        }
    }
}

关键逻辑解析

标签和数据用 ListModel 一份数据驱动两份 UI 。同一个 tabModel 同时喂给 TabBar 里的 Repeater(生成 TabButton)和 StackLayout 里的 Repeater(生成内容页),append/remove/clear 改数据,两边的界面自动增删------这是"标签和页面数量永远一致"的保证,不会出现标签 5 个页面 3 个的错位。

新增标签名按现有数量取名var newIndex = tabModel.count + 1,再 append({"tabName": "标签" + newIndex, ...}),每次追加的标签自然递增,无需外部计数。

删除前两重保护enabled: tabModel.count > 1 让"只剩一个标签"时按钮直接置灰;onClicked 里再判一次 count > 1,双保险防止把最后一个也删掉,保证标签栏不为空。

删掉当前标签后,currentIndex 由容器自己收拾tabModel.remove(tabBar.currentIndex) 只负责删数据行;TabBar 检测到按钮少了,会自动把选中索引收敛到合法范围(比如删了第 2 个,选中会落到新的第 1 个),内容页跟着联动,不需要手动修正。

标签多了会横向滚动,所以要 clip: true 。按钮总宽超出 TabBar 可视区后会自动变成可滑动,clip 保证滚动过程中按钮不会越界画到内容区外面。

模型里预留了 pageColor 字段,页面配色可以更活append 时传了随机颜色、清空时传了蓝色,说明数据层已经支持每页不同色;当前 demo 内容页 Rectangle 把颜色写死成 #3498db,想真正"一页一色",把 delegate 里的 color 换成 pageColor 即可。

自定义样式和动态数据怎么结合

需求 手段
换标签外观 重写 contentItem + background,状态用 parent.checked
加选中过渡 backgroundBehavior on color + ColorAnimation
不让按钮均分 设显式 widthimplicitWidth + N
标签随数据增删 Repeater + ListModel,改数据即改界面
删标签的边界 count > 1 判空,currentIndex 交给容器收敛
标签多了可滑动 自动 Flickable + clip: true

自定义只动"皮",动态增删只动"数据",两层可以叠着用:数据驱动出一排按钮,按钮再套上自定义 contentItem/background,就是一套能增删、能换肤的标签栏。

运行验证

  1. Qt Creator 打开 qml_tabbar/CMakeLists.txt,按 Ctrl+R 运行;
  2. 左侧点「自定义按钮」,点三个胶囊按钮看蓝底白字与颜色渐变;
  3. 点「动态标签」,反复点「添加标签 / 删除当前」,观察标签栏和内容页同步增减、数量文字实时变化;点「清空全部」回到单标签。

扩展复用方向

  • 把自定义 TabButton 抽成单独 QML 文件(比如 PillTabButton.qml),三个按钮引用同一组件,样式只维护一处;
  • 标签数量多时让 TabBar 支持关闭按钮(每个 TabButton 右上角加个 ×,点击从 model 里 remove(index)),做"浏览器标签页"体验;
  • 配合 SwipeView 而不是 StackLayout,动态标签加滑动翻页,内容页仍由同一个 tabModel 驱动。

已验证环境

相关推荐
似水এ᭄往昔2 小时前
【Qt】--常用控件(输入类控件)
开发语言·qt
qq_401700412 小时前
Qt无边框窗体移动详解
qt
励志不掉头发的内向程序员3 小时前
【LibreCAD 2D架构】RS_Line创建之后发生了什么?从图形容器到屏幕渲染
开发语言·c++·qt·学习·系统架构
mengzhi啊13 小时前
QPluginLoader 动态 DLL 插件版,支持插件之间通信,广播。请求和应答
c++·qt
老赵的博客16 小时前
c++ QT之动态库加载问题
c++·qt
Quz21 小时前
QML Loader:动态创建控件与延迟加载
qt
Quz21 小时前
QML Loader:组件切换与属性传递
qt
老赵的博客21 小时前
c++QT之动态库加载常见报错
c++·qt
Quz2 天前
QML Loader:从文件加载组件、加载内联组件
qt