QML Text 换行、省略与字体自适应

0 阅读6分钟

摘要:介绍 Text 的换行、省略和字体自适应能力,帮助文本在有限宽度、动态尺寸和大屏场景下保持良好阅读体验。

实际项目里,文本长度和容器尺寸往往是不确定的。列表项标题可能超长、卡片宽度会随窗口变化、大屏上的数字需要自动撑满空间。Text 提供了三组属性来应对这些问题:换行、省略和字体自适应。这篇把这三组属性的用法和注意点一次讲清楚。

三种场景一览

需求关键属性解决的问题
控制换行wrapMode长文本在有限宽度内如何折行
截断显示elide + maximumLineCount超出范围的文本用省略号收尾
自动缩放fontSizeMode + minimumPixelSize文字随容器大小自动调整字号

这三个能力经常组合使用,比如新闻列表的摘要需要 "两行省略",大屏 KPI 数字需要 "自适应容器"

Text 文本换行

wrapMode 决定长文本如何折行。这个 demo 展示了 NoWrapWordWrapWrap 三种模式的区别。

text-wrap.png

演示代码

import QtQuick
import QtQuick.Layouts

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

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

        ColumnLayout {
            Layout.leftMargin: 20
            spacing: 15

            Text {
                Layout.preferredWidth: 250
                text: "NoWrap: This is a very long text that will not wrap by default"
                wrapMode: Text.NoWrap
                font.pointSize: 12
            }

            Text {
                Layout.preferredWidth: 250
                text: "WordWrap: This is a very long text that only wraps at word boundaries"
                wrapMode: Text.WordWrap
                font.pointSize: 12
            }

            Text {
                Layout.preferredWidth: 250
                text: "Wrap: This is a very long text that prefers word boundaries but can break anywhere if needed"
                wrapMode: Text.Wrap
                font.pointSize: 12
            }
        }

        Item { Layout.fillHeight: true }
    }
}

关键逻辑解析

  • Text.NoWrap:不换行,超出部分直接绘制出去
  • Text.WordWrap:只在单词边界换行,阅读体验最好
  • Text.Wrap:优先单词边界,必要时可在任意位置断行
  • 常用搭配:wrapMode: Text.WordWrap + Layout.fillWidth: true

适用场景

WordWrap 适合文章正文、说明文字;Wrap 适合路径、URL、代码片段这类需要严格填满宽度的内容;NoWrap 适合配合 elide 做单行截断标题。

注意点

中文没有明显的单词边界,WordWrapWrap 在中文下的表现差异不大。但在英文界面里,两者区别很明显,不要乱用。

Text 文本省略

列表、卡片里经常需要把过长的文字截断显示。这个 demo 展示了右侧省略、左侧省略、中间省略,以及多行省略的写法。

text-elide.png

演示代码

import QtQuick
import QtQuick.Layouts

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

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

        ColumnLayout {
            Layout.leftMargin: 20
            spacing: 12

            Text {
                Layout.preferredWidth: 250
                text: "ElideRight: 这是一段需要被省略的长文本"
                elide: Text.ElideRight
                font.pointSize: 11
            }

            Text {
                Layout.preferredWidth: 250
                text: "ElideLeft: 这是一段需要被省略的长文本"
                elide: Text.ElideLeft
                font.pointSize: 11
            }

            Text {
                Layout.preferredWidth: 250
                text: "ElideMiddle: 这是一段需要被省略的长文本"
                elide: Text.ElideMiddle
                font.pointSize: 11
            }

            Text {
                Layout.preferredWidth: 250
                text: "多行省略: 这是一段很长的文本,用来演示 maximumLineCount 属性的效果。当文本超过指定行数时,会被省略。"
                maximumLineCount: 2
                elide: Text.ElideRight
                wrapMode: Text.WordWrap
                font.pointSize: 10
            }
        }

        Item { Layout.fillHeight: true }
    }
}

关键逻辑解析

  • elide:省略模式,ElideRight / ElideLeft / ElideMiddle
  • maximumLineCount:最多显示几行,配合 wrapMode 使用
  • 多行省略必须同时设置 wrapMode: Text.WordWrapelide: Text.ElideRight

适用场景

ElideRight 适合大多数列表标题;ElideMiddle 适合文件路径,保留开头和结尾;ElideLeft 适合显示日志时间戳后面的关键信息。多行省略适合新闻摘要、商品简介等场景。

注意点

单行省略只需要设置 elide 和固定宽度;多行省略必须同时开启 wrapMode,否则 maximumLineCount 不会生效。另外,elide 对富文本(RichText)的支持有限,复杂场景建议用 TextMetrics 自己截断。

Text 字体自适应

做响应式布局或大屏展示时,希望文字自动填满容器。fontSizeMode 就是干这个的。这个 demo 展示了 HorizontalFitVerticalFitFit 三种模式,最后一个容器还可以拖动改变大小来观察效果。

text-autofit.gif

演示代码

import QtQuick
import QtQuick.Layouts

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

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

        ColumnLayout {
            Layout.fillWidth: true
            Layout.leftMargin: 20
            Layout.rightMargin: 20
            spacing: 15

            Rectangle {
                Layout.preferredWidth: 300
                Layout.preferredHeight: 40
                color: "#fff3e0"
                border.color: "#ffb74d"
                radius: 4

                Text {
                    anchors.fill: parent
                    anchors.margins: 4
                    text: "HorizontalFit 适配宽度"
                    fontSizeMode: Text.HorizontalFit
                    font.pixelSize: 72
                    horizontalAlignment: Text.AlignHCenter
                    verticalAlignment: Text.AlignVCenter
                }
            }

            Rectangle {
                Layout.preferredWidth: 300
                Layout.preferredHeight: 50
                color: "#e3f2fd"
                border.color: "#42a5f5"
                radius: 4

                Text {
                    anchors.fill: parent
                    anchors.margins: 4
                    text: "VerticalFit 适配高度"
                    fontSizeMode: Text.VerticalFit
                    font.pixelSize: 72
                    horizontalAlignment: Text.AlignHCenter
                    verticalAlignment: Text.AlignVCenter
                }
            }

            Rectangle {
                Layout.preferredWidth: 300
                Layout.preferredHeight: 60
                color: "#e8f5e9"
                border.color: "#66bb6a"
                radius: 4

                Text {
                    anchors.fill: parent
                    anchors.margins: 4
                    text: "Fit 适配宽高"
                    fontSizeMode: Text.Fit
                    minimumPixelSize: 10
                    font.pixelSize: 72
                    horizontalAlignment: Text.AlignHCenter
                    verticalAlignment: Text.AlignVCenter
                }
            }

            Rectangle {
                id: resizableBox
                Layout.preferredWidth: 300
                Layout.preferredHeight: 60
                color: "#f3e5f5"
                border.color: "#ab47bc"
                border.width: 2
                radius: 4

                Text {
                    anchors.fill: parent
                    anchors.margins: 4
                    text: "拖动改变大小"
                    fontSizeMode: Text.Fit
                    minimumPixelSize: 10
                    font.pixelSize: 72
                    horizontalAlignment: Text.AlignHCenter
                    verticalAlignment: Text.AlignVCenter
                }

                MouseArea {
                    anchors.right: parent.right
                    anchors.bottom: parent.bottom
                    width: 24
                    height: 24
                    cursorShape: Qt.SizeFDiagCursor
                    preventStealing: true

                    property real startX
                    property real startY

                    onPressed: (mouse) => {
                        startX = mouse.x
                        startY = mouse.y
                    }
                    onPositionChanged: (mouse) => {
                        var newW = resizableBox.width + (mouse.x - startX)
                        var newH = resizableBox.height + (mouse.y - startY)
                        if (newW > 80) resizableBox.width = newW
                        if (newH > 30) resizableBox.height = newH
                        startX = mouse.x
                        startY = mouse.y
                    }
                }
            }
        }

        Item { Layout.fillHeight: true }
    }
}

关键逻辑解析

  • fontSizeMode: Text.HorizontalFit:宽度变化时字体跟着变,高度固定
  • fontSizeMode: Text.VerticalFit:高度变化时字体跟着变,宽度固定
  • fontSizeMode: Text.Fit:同时适配宽高
  • minimumPixelSize:限制最小字号,避免容器太小时字缩到看不见
  • font.pixelSize 设一个较大值作为上限,实际大小由容器决定

适用场景

Text.Fit 最适合大屏数字展示、欢迎页标题、广告牌文字。HorizontalFit 适合宽度会变化但高度固定的标签栏。VerticalFit 适合竖向布局里的标题。

注意点

fontSizeMode 只在文字内容较短时效果最好。如果文字很长,字号会缩到很小才能塞进容器,这时应该改用 wrapModeelide。另外,自适应模式下建议始终设置 minimumPixelSize,避免极端情况下文字不可读。

对比表格

需求关键属性注意点
控制换行wrapMode多行用 WordWrap
截断显示elide单行省略只需 elide,多行需加 maximumLineCount + wrapMode
自动缩放fontSizeMode配合 minimumPixelSize 使用

运行验证

  1. Qt Creator 打开 qml_text/CMakeLists.txt
  2. Ctrl+R 运行
  3. 在左侧导航切换文本换行、文本省略、字体自适应

扩展复用方向

  • 封装 EllipsisText 组件,默认开启 ElideRight,支持设置最大行数
  • 在大屏 KPI 展示中用 Text.Fit 让数字自动撑满卡片
  • 新闻列表用 maximumLineCount: 2 + elide: ElideRight 做两行摘要
  • 做响应式卡片时,结合 Layout.fillWidthWordWrap 让正文随窗口自动换行

小结

换行、省略、自适应是 Text 处理动态内容的三把利器。wrapMode 解决文本折行,elide 解决超长截断,fontSizeMode 解决字号适配。三者可以单独使用,也可以组合:比如一个响应式新闻卡片,标题用 elide 单行省略,摘要用 wrapMode + maximumLineCount 多行省略,核心数据用 fontSizeMode 自动撑满。掌握这些属性,就能让文本在不同尺寸下都保持良好的阅读体验。


已验证环境