C
QML最佳实践
您的应用程序的QML代码可能会对闪存和随机访问内存占用量产生重大影响。通过编写干净的QML代码(没有重复),您可以减少生成的C++代码量及其闪存内存占用量。以下部分将描述您可以使用的技术来减小内存占用量并提高性能。
创建可重用组件
不要在几个地方重复相同的代码模式,而是考虑将模式封装在单独的QML文件中。
例如,下面的代码包含一个标签列表,每个标签中都包含一些文本和一张图片
Column { spacing: 15 anchors.centerIn: parent Rectangle { width: 250 height: 120 color: "#e7e3e7" Row { anchors.centerIn: parent spacing: 10 Text { anchors.verticalCenter: parent.verticalCenter text: "Entry 1" color: "#22322f" font.pixelSize: 22 } Image { anchors.verticalCenter: parent.verticalCenter source: "img1.png" } } } Rectangle { width: 250 height: 120 color: "#e7e3e7" Row { anchors.centerIn: parent spacing: 10 Text { anchors.verticalCenter: parent.verticalCenter text: "Entry 2" color: "#22322f" font.pixelSize: 22 } Image { anchors.verticalCenter: parent.verticalCenter source: "img2.png" } } } }
您可以通过创建具有可配置属性或属性别名(如所示)的 Label.qml
文件来简化此代码
import QtQuick 2.15 Rectangle { property alias imageSource: imageItem.source property alias text: textItem.text width: 250 height: 120 color: "#e7e3e7" Row { anchors.centerIn: parent spacing: 10 Text { id: textItem anchors.verticalCenter: parent.verticalCenter color: "#22322f" font.pixelSize: 22 } Image { id: imageItem anchors.verticalCenter: parent.verticalCenter } } }
您可以在原始QML代码中重用此QML组件,以避免重复
Column { spacing: 15 anchors.centerIn: parent Label { text: "Entry 1" imageSource: "img1.png" } Label { text: "Entry 2" imageSource: "img2.png" } }
限制PropertyChanges
具有许多状态以及许多通过PropertyChanges受到影响的属性(这些属性通过状态更改)的QML文件,会导致生成大量且复杂的C++代码。生成的代码量将是N x M
,其中N是状态的数量,M是那些状态中PropertyChanges更新的唯一属性的数量。
以下示例只有两个状态和两个属性,但想象一下,会有很多类似的选项在同一个QML组件中供您选择不同的视图
Item { state: "0" states: [ State { name: "0" PropertyChanges { target: viewA; visible: true } }, State { name: "1" PropertyChanges { target: viewB; visible: true } } ] ViewA { id: viewA visible: false } ViewB { id: viewB visible: false } }
您可以通过直接根据状态设置视图的可见性来优化此过程
Item { id: root state: "0" states: [ State { name: "0" }, State { name: "1" } ] ViewA { id: viewA visible: root.state == "0" } ViewB { id: viewB visible: root.state == "1" } }
避免空容器项
Item类型可以用于对其他项进行分组,使得能够以组合方式设置它们的可见性和位置。限制容器项的使用,因为它们会增加内存使用量。例如,以下代码片段中的外层Item是不必要的
相反,您可以直接使用包含的Image项
Image { anchors.fill: parent source: "img.png" }
动态加载组件
您的应用程序可能包含复杂且具有许多项的QML组件,这些项在不同的时间可见。您可以通过使用Loader类型动态加载此类组件来减少RAM使用量。
在加载新项之前,明确地卸载现有隐藏的项,以避免内存峰值。根据应用程序的用户界面设计和内存限制,确保在任何给定时间内只加载一定数量的项。以下示例演示了您如何确保在任何给定时间内,SwipeView只将一页加载到内存中。
SwipeView { id: theSwipe width: parent.width * 0.5 height: parent.height * 0.5 anchors.centerIn: parent clip: true function updateLoaderStates() { console.log("updating loader states ...") if (theSwipe.currentIndex === 0) { loader1.source = "" loader0.source = "FirstPage.qml" } else if (theSwipe.currentIndex === 1) { loader0.source = "" loader1.source = "SecondPage.qml" } } Component.onCompleted: updateLoaderStates() onCurrentIndexChanged: updateLoaderStates() Loader { id: loader0 onItemChanged: { if (item) { console.log("loader0 loaded") } else { console.log("loader0 free") } } } Loader { id: loader1 onItemChanged: { if (item) { console.log("loader1 loaded") } else { console.log("loader1 free") } } } }
作为一般规则,不要依赖绑定的评估顺序。在以下示例中,您无法控制加载和卸载数项的顺序。这可能会导致暂时为应用程序的两个页面分配内存。
SwipeView { id: mySwipe width: parent.width * 0.5 height: parent.height * 0.5 anchors.centerIn: parent clip: true onCurrentIndexChanged: { console.log("index changed ...") } Loader { source: "FirstPage.qml" active: mySwipe.currentIndex === 0 onItemChanged: { if (item) { console.log("loader0 loaded") } else { console.log("loader0 free") } } } Loader { source: "SecondPage.qml" active: mySwipe.currentIndex === 1 onItemChanged: { if (item) { console.log("loader1 loaded") } else { console.log("loader1 free") } } } }
减少视觉组件的数量
每个视觉组件在运行时通常会携带一些处理和渲染开销。如果可能,减少组成UI所需的视觉组件数量。以下是一些如何实现此目的的示例。
减少重叠的图像
如果两个图像在UI中始终重叠,则将它们合并到单个图像中可能更好。许多重叠的图像会降低性能并消耗更多内存。例如,以下代码片段中的inner.png图像比outer.png图像小。
不要单独使用,将inner.png和outer.png合并成单个图像。
Image { source: "combined.png" }
如果静态文本与图像重叠,添加它到图像中而不是使用单独的Text或StaticText项是有价值的。
减少Text项目
如果可能将多个Text项组合成一个单一的Text项,则避免在行中排列多个Text项。
您可以将它们合并成一个Text项。
Text { text: "Temperature: " + root.temperature }
减少绑定的数量
减少绑定的数量将节省ROM。
使用隐式尺寸
当可能时,您可以通过使用隐式尺寸来减少绑定的数量。
为图像使用隐式尺寸
创建具有正确尺寸的图像,这样您每次使用图像时不必指定宽度和高度属性。
Image { width: 64 height: 64 fillMode: Image.pad source: "image/background.png" }
而是使用隐式宽度和高度。
Image { source: "image/background.png" }
为组件使用隐式尺寸
您可以通过使用隐式尺寸来减少常用组件上的绑定数量。
例如,考虑定义在IconButton.qml中的IconButton组件,没有隐式大小。
这迫使组件用户指定组件的宽度和高度。
IconButton { width: 64 height: 64 iconSource: "home.png" }
而是创建如下所示的IconButton
MouseArea { implicitWidth: img.implicitWidth implicitHeight: img.implicitHeight property alias iconSource: img.source Image { id: img source: "" } }
它减少了绑定数量。
IconButton { iconSource: "home.png" }
在这种情况下,您增加了组件的大小,但如果组件经常被使用,总体的ROM节省将更大。
文本和图像的可见性
通过使用空字符串作为text和source属性的值,分别控制应用程序中Text和Image项目的可见性。
例如,以下代码定义的组件
Item { property alias iconVisible: img.visible property alias textVisible: txt.visible property alias imageSource: img.source property alias text: txt.text Image { id: img source: "" } Text { id: txt text: "" } }
您可以如下使用此类组件
MyComponent { textVisible: false text: "" iconVisible: true imageSource: "images/background.png" }
您也可以使用可见性属性实现相同的结果
Item { property alias imageSource: img.source property alias text: txt.text Image { id: img source: "" } Text { id: txt text: "" } }
当您使用以下示例中所示的组件时,图像项是可见的,但文本项不可见。
MyComponent { imageSource: "images/background.png" }
使用状态来打包属性
将此方法应用于常用组件。例如以下 Header
组件
Row { property alias button1Text: btn1.text property alias button2Text: btn2.text property alias button3Text: btn3.text Button { id: btn1 text: "" } Button { id: btn2 text: "" } Button { id: btn3 text: "" } }
您将需要指定 3 个绑定
Header { button1Text: "Back" button2Text: "OK" button3Text: "Info" }
相反,您可以将这些属性打包到状态中
Row { Button { id: btn1 text: "" } Button { id: btn2 text: "" } Button { id: btn3 text: "" } states: [ State { name: "VariantA" PropertyChanges { target: btn1 text: "Back" } PropertyChanges { target: btn2 text: "OK" } PropertyChanges { target: btn3 text: "Info" } } ] }
这样可以减少绑定数量,只留下一个绑定
Header { state: "VariantA" }
保持信号简单
尽可能简化信号。例如,在组件 MyComponent.qml 中有多个逻辑上相似的按钮时
Item { id: root signal button1Clicked signal button2Clicked Row { Button { text: "Ok" onClicked: { root.button1Clicked() } } Button { text: "Cancel" onClicked: { root.button2Clicked() } } } }
您将需要 2 个绑定,每个信号一个
Rectangle { MyComponent { onButton1Clicked: { console.log("Ok") } onButton2Clicked: { console.log("Cancel") } } }
相反,使用一个传递索引的信号,该索引标识哪个按钮被点击
Item { id: root signal buttonClicked(index: int) Row { Button { text: "Ok" onClicked: { root.buttonClicked(0) } } Button { text: "Cancel" onClicked: { root.buttonClicked(1) } } } }
这样,您只需要一个绑定
Rectangle { MyComponent { onButtonClicked: { switch(index) { case 0: { console.log("Ok") break; } case 1: { console.log("Cancel") break; } } } } }
减少模型大小
不要在模型中包含所有委托属性。通过在视图中直接指定来减少使用的属性数量。
例如,不要创建类似以下模型
Rectangle { property ListModel myModel : ListModel { ListElement { textcolor: "blue" name: "John" age: 20 } ListElement { textcolor: "blue" name: "Ochieng" age: 30 } } ListView { anchors.fill: parent model: myModel delegate: Text { width: 50 height: 50 color: model.textcolor text: "Name: %1 Age: %2".arg(model.name).arg(model.age) } } }
如果 textcolor 属性的所有数据值都相同,请将其从模型中删除并声明为属性,以减少模型的大小并避免不必要的重复
Rectangle { property ListModel myModel : ListModel { ListElement { name: "John" age: 20 } ListElement { name: "Ochieng" age: 30 } } property string textcolor ListView { anchors.fill: parent model: myModel delegate: Text { width: 50 height: 50 color: textcolor text: "Name: %1 Age: %2".arg(model.name).arg(model.age) } } }
共享大型的 ListModel
在 qml Singleton 中使用 ListModel 属性,以在应用程序的不同部分之间共享 ListModel。这将有助于节省 ROM。
// AppConfig.qml pragma Singleton .. QtObject { property ListModel mySharedModel: ListModel { ListElement { bgcolor: 'red' } ListElement { bgcolor: 'yellow' } ListElement { bgcolor: 'blue' } ListElement { bgcolor: 'green' } ListElement { bgcolor: 'orange' } ListElement { bgcolor: 'black' } ListElement { bgcolor: 'gray' } ... } }
// Page1.qml Repeater { model: AppConfig.mySharedModel delegate: .. } // Page2.qml ListView { model: AppConfig.mySharedModel delegate: .. }
在特定的 Qt 许可下提供。
了解更多信息。