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 、更新されるユニークなプロパティの数です。
ここでは、たった2つの状態と2つのプロパティの例を示しますが、同じ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
}
}状態に基づいてビューのvisibleプロパティを直接設定することで、これを最適化することができます:
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は不要です:
代わりに、含まれているImageアイテムを直接使用することができます:
Image {
anchors.fill: parent
source: "img.png"
}コンポーネントを動的にロードする
アプリケーションには、多くのアイテムを持つ複雑なQMLコンポーネントが含まれているかもしれません。このようなコンポーネントは、Loader を使って動的にロードすることで、RAMの使用量を減らすことができます。
新しいアイテムをロードする前に、既存の非表示アイテムを明示的にアンロードすることで、メモリのピークを避けることができます。アプリケーションのUI設計とメモリの制約に応じて、常に選択された数のアイテムだけがロードされるようにします。次の例では、SwipeView の 1 ページだけが、いつでもメモリに読み込まれるようにする方法を示します:
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で2つの画像が常に重なっている場合、それらを1つの画像にまとめた方がよいかもしれません。重なり合う画像が多いと、パフォーマンスが低下し、メモリの消費量も増えます。例えば、以下のコード・スニペットのinner.png は、outer.png の画像よりも小さい:
これらを別々に使うのではなく、inner.png とouter.png を1つの画像にまとめましょう:
Image {
source: "combined.png"
}静的テキストが画像に重なっている場合は、TextやStaticText のアイテムを別々に使うのではなく、画像に追加する価値があります。
テキストアイテムを減らす
テ キ ス ト ア イ テ ム を 1 つに ま と め る こ と がで き る 場合は、 多数のテ キ ス ト ア イ テ ム を並べ る こ と は避け ま し ょ う 。た と えば下記の コ ー ド では、 2 個のテ キ ス ト アイテムを一列に並べ てい ます:
これを1つのテキスト・アイテムにまとめることができます:
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 プロパティにそれぞれ空の文字列を使用することで、アプリケーション内のテキストとイメージアイテムの可視性を制御できます。
例えば、以下のコードで定義されたコンポーネント:
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"
}visibilityプロパティを使わなくても、同じ結果を得ることができます:
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"
}
}
]
}これにより、バインディングの数を1つに減らすことができる:
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()
}
}
}
}各シグナルに1つずつ、合計2つのバインディングが必要になります:
Rectangle {
MyComponent {
onButton1Clicked: {
console.log("Ok")
}
onButton2Clicked: {
console.log("Cancel")
}
}
}代わりに、どのボタンがクリックされたかを識別するインデックスを渡すシグナルを1つ使います。
Item {
id: root
signal buttonClicked(index: int)
Row {
Button {
text: "Ok"
onClicked: {
root.buttonClicked(0)
}
}
Button {
text: "Cancel"
onClicked: {
root.buttonClicked(1)
}
}
}
}そうすれば、必要なバインディングは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の共有
アプリケーションの異なるパート間でListModel を共有するには、qml Singleton の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: ..
}大きな ListModel を分割する
ListModel が大きく、それぞれのListElement に多くのプロパティがある場合、データを複数のQML Basic Type のリストに分割することを検討してください。こうすることで ROM を節約し、バインディングのために生成される C++ コードの量を減らすことができます。詳しくは「バインディングの数を減らす」を参照してください。
例
ListModel {
id: myModel
ListElement { name: "Hans"; age: 25; city: "Berlin"; x: 10; y: 20; }
ListElement { name: "Marie"; age: 30; city: "Paris"; x: 15; y: 25; }
ListElement { name: "Luca"; age: 35; city: "Rome"; x: 20; y: 30; }
ListElement { name: "Eva"; age: 40; city: "Madrid"; x: 25; y: 35; }
// ...
}
Repeater {
model: myModel
Text {
text: model.name + ", " + model.age + ", " + model.city
}
}Qt Quick Ultraliteは先ほどの例で各プロパティに対して20個(5 * number of ListElements)のバインディングを生成しました。これを以下の方法で4個に減らすことができます:
readonly property list user_name: ["Hans", "Marie", "Luca", "Eva", ...]
readonly property list user_age: [25, 30, 35, 40, ...]
readonly property list user_city: ["Berlin", "Paris", "Rome", "Madrid", ...]
readonly property list user_position: [Qt.point(10, 20), Qt.point(15, 25), Qt.point(20, 30), Qt.point(25, 35), ...]
Repeater {
model: parent.names.length
Text {
x: user_position[index].x
y: user_position[index].y
text: user_name[index] + ", " + user_age[index] + ", " + user_city[index]
}
}注: Qt Quick Ultralite はオブジ ェ ク ト の リ ス ト 内の各プ ロ パテ ィ に対 し てバ イ ンデ ィ ン グ を生成 し ます。この方法は {QML 基本型} でのみ使用してください。
現在、length プロパティはコピーされ、モデルにはバインドされません。つまり、モデルの値を明示的に更新して、リストのサイズに適応させる必要があります。
特定の Qt ライセンスの下で利用可能です。
詳細はこちら。