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是不必要的

Item {
    Image {
        anchors.fill: parent
        source: "img.png"
    }
}

相反,您可以直接使用包含的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图像小。

Image {
    id: outer
    source: "outer.png"
}
Image {
    anchors.centerIn: outer
    source: "inner.png"
}

不要单独使用,将inner.png和outer.png合并成单个图像。

Image {
    source: "combined.png"
}

如果静态文本与图像重叠,添加它到图像中而不是使用单独的Text或StaticText项是有价值的。

减少Text项目

如果可能将多个Text项组合成一个单一的Text项,则避免在行中排列多个Text项。

Row {
    Text {
        text: "Temperature: "
    }
    Text {
        text: root.temperature
    }
}

您可以将它们合并成一个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组件,没有隐式大小。

MouseArea {
    property alias iconSource: img.source

    Image {
        id: img
        source: ""
    }
}

这迫使组件用户指定组件的宽度和高度。

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 许可下提供。
了解更多信息。