可扩展性
当您为多种不同的移动设备平台开发应用程序时,会面临以下挑战:
- 移动设备平台支持具有不同屏幕配置的设备:尺寸、宽高比、方向和像素密度。
- 不同平台有不同的 UI 规范,您需要满足每个平台用户的期望。
Qt Quick 使您能够开发可在不同类型设备(如平板电脑和手机)上运行的应用程序。特别是,这些应用程序能够适应不同的屏幕配置。不过,为了在每个目标平台上创造最佳的用户体验,通常仍需要进行一定程度的调整和优化。
在以下情况下,您需要考虑可扩展性:
- 您希望将应用程序部署到多个设备平台(例如 Android 和 iOS)或支持多种设备屏幕配置时。
- 您希望为初始部署后可能出现在市场上的新设备做好准备。
要使用 Qt Quick:
- 使用 Qt Quick Controls 提供一组 UI 控件。
- 使用 Qt Quick Layouts,该布局可调整其项的大小。
- 使用属性绑定来实现布局未涵盖的使用场景。例如,在像素密度较低和较高的屏幕上显示不同版本的图像,或根据当前屏幕方向自动调整视图内容。
- 选择一个参考设备,并计算缩放比例,以根据实际屏幕尺寸调整图像、字体大小和边距。
- 使用文件选择器加载特定于平台的资源。
- 使用Loader 按需加载组件。
在设计应用程序时,请考虑以下模式:
- 视图的内容在所有屏幕尺寸下可能非常相似,但内容区域会有所扩展。如果您使用来自Qt Quick Controls 的ApplicationWindow QML 类型,它将根据其内容项的大小自动计算窗口尺寸。如果您使用Qt Quick Layouts 来定位内容项,它们会自动调整推送给它们的项的大小。
- 在较小设备上整页的内容,在较大设备上可能仅作为布局的一个组件元素。因此,建议将其作为独立组件(即在单独的 QML 文件中定义),而在较小设备上,视图只需包含该组件的一个实例即可。 在较大设备上,可能有足够的空间使用加载器来显示额外项目。例如,在电子邮件查看器中,如果屏幕足够大,则可能将邮件列表视图和邮件阅读视图并排显示。
- 对于游戏,通常建议创建一个不随屏幕大小调整的游戏界面,以免给使用大屏幕的玩家带来不公平的优势。 一种解决方案是定义一个安全区域,使其适应支持的最小宽高比(通常为 3:2)的屏幕,并在 4:3 或 16:9 屏幕上会被隐藏的区域中添加仅用于装饰的内容。
动态调整应用程序窗口大小
Qt Quick Controls 提供了一套 UI 控件,用于在Qt Quick 中创建用户界面。通常,您会将一个 `ApplicationWindow ` 控件声明为应用程序的根项。`ApplicationWindow ` 控件便于以平台无关的方式定位其他控件,例如 `MenuBar`、`ToolBar` 和 `StatusBar`。`ApplicationWindow ` 在计算实际窗口的有效尺寸约束时,会将内容项的尺寸约束作为输入。
除了定义应用程序窗口标准组件的控件外,还提供了用于创建视图和菜单,以及呈现或接收用户输入的控件。您可以使用Qt Quick Controls 样式为预定义控件应用自定义样式。
Qt Quick Controls,例如“ToolBar ”,这些控件本身不提供布局,而是需要您手动定位其内容。为此,您可以使用“Qt Quick Layouts ”。
动态布局屏幕控件
Qt Quick Layouts 通过RowLayout 、ColumnLayout 和GridLayout 等QML类型,提供将屏幕控件以行、列或网格形式布局的方法。这些QML类型的属性用于指定其布局方向以及单元格之间的间距。
您可以使用 Qt Quick Layouts QML类型,为推入布局中的项附加额外属性。例如,您可以为项的高度、宽度和大小指定最小值、最大值和首选值。
布局可确保在调整窗口和屏幕大小时,您的 UI 能够正确缩放,并始终充分利用可用空间。
GridLayout 类型的具体用例之一是根据屏幕方向将其用作行或列。

以下代码片段使用flow 属性,当屏幕宽度大于屏幕高度时,将网格的流向设置为从左到右(作为一行);否则,设置为从上到下(作为一列):
ApplicationWindow {
id: root
visible: true
width: 480
height: 620
GridLayout {
anchors.fill: parent
anchors.margins: 20
rowSpacing: 20
columnSpacing: 20
flow: width > height ? GridLayout.LeftToRight : GridLayout.TopToBottom
Rectangle {
Layout.fillWidth: true
Layout.fillHeight: true
color: "#5d5b59"
Label {
anchors.centerIn: parent
text: "Top or left"
color: "white"
}
}
Rectangle {
Layout.fillWidth: true
Layout.fillHeight: true
color: "#1e1b18"
Label {
anchors.centerIn: parent
text: "Bottom or right"
color: "white"
}
}
}
}屏幕的持续调整大小和重新计算会带来性能开销。例如,移动设备和嵌入式设备可能不具备每帧重新计算动画对象大小和位置所需的处理能力。如果您在使用布局时遇到性能问题,请考虑改用其他方法,例如绑定。
以下是使用布局时应避免的做法:
- 不要将绑定与布局中项的 x、y、width 或 height 属性相关联,因为这会与布局的设计目标相冲突,并导致绑定循环。
- 请勿定义会被定期评估的复杂 JavaScript 函数。这会导致性能下降,特别是在动画过渡期间。
- 不要对容器大小或子项的大小做任何假设。应尽量制定灵活的布局定义,以便适应可用空间的变化。
- 若希望设计达到像素级精确,请勿使用布局。内容项会根据可用空间自动调整大小和位置。
使用绑定
如果Qt Quick Layouts 无法满足您的需求,您可以回退到使用属性绑定。绑定使对象能够根据其他对象属性的变化或某些外部事件的发生,自动更新其属性。
当为对象的属性赋值时,既可以赋给静态值,也可以绑定到 JavaScript 表达式。 在前一种情况下,除非为该属性赋予新值,否则其值不会改变。在后一种情况下,将创建一个属性绑定,每当被评估表达式的值发生变化时,QML 引擎会自动更新该属性的值。
这种定位方式动态性最强。然而,不断求值 JavaScript 表达式会带来性能开销。
在那些不自动支持低像素密度和高像素密度(如 Android、macOS 和 iOS 所支持)的平台上,您可以使用绑定来处理这些情况。以下代码片段使用 `Screen.pixelDensity ` 附加属性,为低像素密度、高像素密度或正常像素密度的屏幕指定不同的图像:
Image {
source: {
if (Screen.pixelDensity < 40)
"image_low_dpi.png"
else if (Screen.pixelDensity > 300)
"image_high_dpi.png"
else
"image.png"
}
}在 Android、macOS 和 iOS 上,您可以通过为图标和图片使用相应的标识符(例如@2x、@3x 或@4x),并在资源文件中放置更高分辨率的替代资源。系统会自动选择与屏幕像素密度匹配的版本进行使用。
例如,以下代码片段将在 Retina 显示屏上尝试加载artwork@2x.png:
Image {
source: "artwork.png"
}处理像素密度
某些 QML 类型(如Image 、BorderImage 和Text )会根据为其指定的属性自动缩放。如果未指定 Image 的宽度和高度,它将自动使用源图像的尺寸,该尺寸通过source 属性指定。 默认情况下,指定宽度和高度会将图像缩放至该尺寸。通过设置fillMode 属性可以更改此行为,允许图像被拉伸并平铺。但是,在高DPI显示器上,原始图像尺寸可能会显得过小。
BorderImage 用于通过缩放或平铺每张图像的局部区域来创建图像边框。它将源图像划分为 9 个区域,并根据属性值对这些区域进行缩放或平铺。但是,角落区域完全不会被缩放,这可能会导致在高 DPI 显示器上显示效果不尽如人意。
Text QML 类型会尝试确定所需空间大小,并据此设置 `width ` 和 `height ` 属性,除非这些属性已被显式设置。`fontPointSize ` 属性以与设备无关的方式设置点大小。但是,如果将字体以点为单位指定,而其他尺寸以像素为单位指定,则会引发问题,因为点与显示密度无关。 在低 DPI 显示器上看起来正确的字符串边框,在高 DPI 显示器上可能会变得过小,导致文本被截断。
各平台对高 DPI 的支持程度以及所采用的技术各不相同。以下各节介绍了在高 DPI 显示器上缩放屏幕内容的不同方法。
有关 Qt 中的高 DPI 支持及其支持平台的更多信息,请参阅“高 DPI”。
高 DPI 缩放
如果目标设备支持高 DPI 缩放,操作系统会向 Qt 提供一个缩放比例,用于缩放图形输出。
这种方法的优势在于矢量图形和字体会自动缩放,且现有应用程序通常无需修改即可正常运行。但对于光栅内容,则需要高分辨率的替代资源。
缩放功能已在Qt Quick 和Qt Widgets 堆栈中实现,并在Qt GUI中提供了通用支持。
低级图形 API 以设备像素为单位进行操作。这包括使用 OpenGL API 的代码,以及使用QRhi API 的代码。 例如,这意味着一个size() 为1280x720、QWindow::devicePixelRatio()为2的QWindow ,其渲染目标(swapchain)的设备像素尺寸为2560x1440。
操作系统会对窗口、事件和桌面几何形状进行缩放。Cocoa 平台插件将缩放比例设置为QWindow::devicePixelRatio() 或QScreen::devicePixelRatio(),并在后端存储中进行设置。
对于Qt Widgets ,QPainter 会从后端存储中获取devicePixelRatio() 并将其解释为缩放比例。
然而,在 OpenGL 中,像素始终是设备像素。例如,传递给 glViewport() 的几何体需要通过 devicePixelRatio() 进行缩放。
指定的字体大小(以点或像素为单位)不会改变,字符串相对于 UI 其余部分的相对大小也会保持不变。 字体的缩放是绘制过程的一部分,因此,无论指定的是点还是像素,12 号字体在 2 倍缩放下实际上会变成 24 号字体。px单位被解释为与设备无关的像素,以确保字体在高 DPI 显示器上不会显得更小。
计算缩放比例
您可以选择一台高 DPI 设备作为参考设备,并计算缩放比例,从而根据实际屏幕尺寸调整图像、字体大小和边距。
以下代码片段使用 Nexus 5 Android 设备的 DPI、高度和宽度参考值,QRect 类返回的实际屏幕尺寸,以及qApp 全局指针返回的屏幕逻辑 DPI 值,来计算图像尺寸和边距的缩放比例(m_ratio )以及字体尺寸的缩放比例(m_ratioFont ):
qreal refDpi = 216.;
qreal refHeight = 1776.;
qreal refWidth = 1080.;
QRect rect = QGuiApplication::primaryScreen()->geometry();
qreal height = qMax(rect.width(), rect.height());
qreal width = qMin(rect.width(), rect.height());
qreal dpi = QGuiApplication::primaryScreen()->logicalDotsPerInch();
m_ratio = qMin(height/refHeight, width/refWidth);
m_ratioFont = qMin(height*refDpi/(dpi*refHeight), width*refDpi/(dpi*refWidth));为了获得合理的缩放比例,高度和宽度值必须根据参考设备的默认方向进行设置,本例中为纵向。
以下代码片段将字体缩放比例设置为1 ,以防该比例小于1导致字体尺寸过小:
int tempTimeColumnWidth = 600;
int tempTrackHeaderWidth = 270;
if (m_ratioFont < 1.) {
m_ratioFont = 1;您应针对目标设备进行测试,以发现需要额外计算的边界情况。某些屏幕可能因高度或宽度不足而无法容纳所有计划内容,因此需要专门的布局。例如,对于具有非典型宽高比(如 1:1)的屏幕,您可能需要隐藏或替换部分内容。
缩放比例可应用于QQmlPropertyMap 中的所有尺寸,以缩放图像、字体和边距:
m_sizes = new QQmlPropertyMap(this);
m_sizes->insert(QLatin1String("trackHeaderHeight"), QVariant(applyRatio(270)));
m_sizes->insert(QLatin1String("trackHeaderWidth"), QVariant(applyRatio(tempTrackHeaderWidth)));
m_sizes->insert(QLatin1String("timeColumnWidth"), QVariant(applyRatio(tempTimeColumnWidth)));
m_sizes->insert(QLatin1String("conferenceHeaderHeight"), QVariant(applyRatio(158)));
m_sizes->insert(QLatin1String("dayWidth"), QVariant(applyRatio(150)));
m_sizes->insert(QLatin1String("favoriteImageHeight"), QVariant(applyRatio(76)));
m_sizes->insert(QLatin1String("favoriteImageWidth"), QVariant(applyRatio(80)));
m_sizes->insert(QLatin1String("titleHeight"), QVariant(applyRatio(60)));
m_sizes->insert(QLatin1String("backHeight"), QVariant(applyRatio(74)));
m_sizes->insert(QLatin1String("backWidth"), QVariant(applyRatio(42)));
m_sizes->insert(QLatin1String("logoHeight"), QVariant(applyRatio(100)));
m_sizes->insert(QLatin1String("logoWidth"), QVariant(applyRatio(286)));
m_fonts = new QQmlPropertyMap(this);
m_fonts->insert(QLatin1String("six_pt"), QVariant(applyFontRatio(9)));
m_fonts->insert(QLatin1String("seven_pt"), QVariant(applyFontRatio(10)));
m_fonts->insert(QLatin1String("eight_pt"), QVariant(applyFontRatio(12)));
m_fonts->insert(QLatin1String("ten_pt"), QVariant(applyFontRatio(14)));
m_fonts->insert(QLatin1String("twelve_pt"), QVariant(applyFontRatio(16)));
m_margins = new QQmlPropertyMap(this);
m_margins->insert(QLatin1String("five"), QVariant(applyRatio(5)));
m_margins->insert(QLatin1String("seven"), QVariant(applyRatio(7)));
m_margins->insert(QLatin1String("ten"), QVariant(applyRatio(10)));
m_margins->insert(QLatin1String("fifteen"), QVariant(applyRatio(15)));
m_margins->insert(QLatin1String("twenty"), QVariant(applyRatio(20)));
m_margins->insert(QLatin1String("thirty"), QVariant(applyRatio(30)));以下代码片段中的函数将缩放比例应用于字体、图片和边距:
int Theme::applyFontRatio(const int value)
{
return int(value * m_ratioFont);
}
int Theme::applyRatio(const int value)
{
return qMax(2, int(value * m_ratio));
}当目标设备的屏幕尺寸差异不大时,此方法可产生合理的效果。如果差异巨大,请考虑创建多个具有不同参考值的布局。
根据平台加载文件
您可以使用QQmlFileSelector 将QFileSelector 应用于QML文件的加载。这使您能够根据应用程序运行的平台加载相应的替代资源。例如,当在Android设备上运行时,您可以使用+android 文件选择器加载不同的图像文件。
您可以将文件选择器与单例对象结合使用,以在特定平台上访问对象的单个实例。
文件选择器是静态的,并强制执行一种文件结构:特定于平台的文件存储在以平台名称命名的子文件夹中。如果您需要更动态的解决方案来按需加载 UI 的部分内容,可以使用 Loader。
目标平台可能会通过各种方式自动为不同的显示密度加载替代资源。在 Android 和 iOS 上,使用@2x文件名后缀来标识高 DPI 版本的图像。Image QML 类型和QIcon 类会在提供 @2x 版本的图像和图标时自动加载它们。QImage 和QPixmap 类会自动将@2x版本图像的devicePixelRatio 属性设置为2 ,但您需要添加代码才能实际使用@2x版本:
if ( QGuiApplication::primaryScreen()->devicePixelRatio() >= 2 ) {
imageVariant = "@2x";
} else {
imageVariant = "";
}Android 定义了通用的屏幕尺寸(小、中、大、特大)和密度(ldpi、mdpi、hdpi、xhdpi、xxhdpi 以及 xxxhdpi),您可以针对这些尺寸和密度创建替代资源。 Android 会在运行时检测当前设备配置,并为您的应用加载相应的资源。不过,从 Android 3.2(API 级别 13)开始,这些尺寸分组已被废弃,取而代之的是基于可用屏幕宽度管理屏幕尺寸的新方法。
按需加载组件
Loader 可以加载 QML 文件(使用source 属性)或 Component 对象(使用sourceComponent 属性)。这有助于延迟组件的创建,直到需要时才创建。例如,当组件应按需创建时,或者出于性能考虑不应不必要地创建组件时。
您还可以使用加载器来应对以下情况:由于特定平台不支持某些功能,导致用户界面的部分内容在该平台上无需显示。与其在应用程序运行的设备上显示不需要的视图,不如将其设为隐藏,并使用加载器在该位置显示其他内容。
切换屏幕方向
Screen.orientation 附加属性包含来自加速度计(如果可用)的屏幕当前方向。在台式计算机上,该值通常不会改变。
如果 `primaryOrientation ` 遵循 `orientation`,则表示屏幕会根据您握持设备的方式自动旋转所有显示内容。如果 `primaryOrientation ` 值未发生变化,但屏幕方向发生了变化,则可能是设备未自动旋转其显示屏。在这种情况下,您可能需要使用 `Item.rotation ` 或 `Item.transform ` 来旋转您的内容。
应用程序的顶级页面定义和可重用组件定义应使用一个 QML 布局定义来描述布局结构。该单一定义应包含针对不同设备方向和宽高比的布局设计。 原因在于,方向切换时的性能至关重要,因此确保在方向变化时,两种方向所需的所有组件均已加载是一个明智的做法。
相反,如果您选择使用Loader 来加载不同方向所需的额外QML,则应进行彻底的测试,因为这会影响方向切换时的性能。
为了在不同方向之间启用布局动画,锚点定义必须位于同一个包含组件内。因此,页面或组件的结构应由一组公共子组件、一组公共锚点定义以及代表该组件所支持的不同宽高比的状态集合(在StateGroup 中定义)组成。
如果页面中包含的某个组件需要托管在多种不同的外形尺寸定义中,则该视图的布局状态应取决于页面的宽高比(即其直接容器)。同样,一个组件的不同实例可能位于 UI 中的多个不同容器内,因此其布局状态应由其父容器的宽高比决定。 结论是:布局状态应始终遵循直接容器的宽高比(而非当前设备屏幕的“方向”)。
在每个布局State 中,应使用原生QML布局定义来定义项目之间的关系。更多信息请参见下文。在状态转换过程中(由顶级方向变化触发),对于锚点布局,可以使用AnchorAnimation 元素来控制转换。 在某些情况下,您还可以对项目(例如其宽度)使用NumberAnimation 。请注意,应避免在动画的每一帧中进行复杂的 JavaScript 计算。在大多数情况下,使用简单的锚点定义和锚点动画有助于解决这个问题。
还有几个需要考虑的额外情况:
- 如果有一个页面在横向和纵向显示时外观完全不同,即所有子项都不同,该怎么办?对于每个页面,设置两个子组件,并为它们分别定义布局,同时确保在每种状态下,其中一个子项的不透明度为零。 只需对不透明度应用NumberAnimation 过渡效果,即可实现淡入淡出动画。
- 如果有一个页面,其纵向和横向布局内容有 30% 或更多是相同的,该怎么办? 在这种情况下,建议使用一个同时包含横向和纵向状态的组件,以及一组独立的子项,其不透明度(或位置)取决于设备方向状态。这样,您就可以对不同方向下共用的项使用布局动画,而其他项则可以淡入淡出,或者通过动画效果在屏幕上显示或隐藏。
- 如果在手持设备上(例如大屏设备)有两个页面需要同时显示在屏幕上,该怎么办?这种情况下,请注意您的视图组件将不再占据整个屏幕。 因此,请务必记住:所有组件(特别是列表委托项)都应根据包含组件的宽度来确定尺寸,而非屏幕宽度。在此情况下,可能需要在 Component.onCompleted() 处理程序中设置宽度,以确保在设置该值之前,列表项委托已构建完成。
- 如果两种方向的视图占用内存过多,无法同时驻留内存中该怎么办?如果无法同时将视图的两个版本保存在内存中,必要时可使用Loader ,但需注意布局切换期间淡入淡出动画的性能表现。 一种解决方案是在 Page 上创建两个“启动画面”项作为其子项,然后在旋转过程中让它们之间进行淡入淡出过渡。接着,你可以使用Loader 加载另一个子组件,该组件将实际模型数据加载到另一个子项中,并在Loader 完成后淡入淡出切换到该项。
另请参阅 Qt Quick 响应式布局。
© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.