Qt Quick 入門 第8回: QML の描画処理

今回の「Qt Quick 入門」では、QML の各要素で共通する描画関連の処理やプロパティについて説明していきます。前回の「レイアウト」と合わせて、QML のレイアウトと描画に関する基本をマスターしましょう。

表示の優先順位

今まで、QML の各要素を表示する優先順位については、特に触れてきませんでした。前回の「レイアウト」で説明したような各要素が重ならない場合には、どの要素から描画されても問題ありません。また、親子関係にある要素では、子要素が優先されて前面に表示されます。このあたりは一般的なルールだと思います。
それでは、親子関係にはない複数の要素が互いに重なっていた場合、そのどちらが優先して表示されるのでしょうか。その場合、ソース内で後から定義された要素が優先されて前面に表示されます。たとえば、次の二つのリストを実行してみてください。

// リストA
import Qt 4.7

Rectangle {
width: 200; height: 200

Rectangle {
id: rect1
x: 25; y: 25
width: 100; height: 100
color: "red"
}

Rectangle {
id: rect2
x: 75; y: 75
width: 100; height: 100
color: "green"
}
}

// リストB
import Qt 4.7

Rectangle {
width: 200; height: 200

Rectangle {
id: rect2
x: 75; y: 75
width: 100; height: 100
color: "green"
}

Rectangle {
id: rect1
x: 25; y: 25
width: 100; height: 100
color: "red"
}
}


リストAとリストBはそれぞれルート要素の [qml Rectangle] の中に二つの重なり合う Rectangle(rect1, rect2) を持っています。それぞれのリストで rect1 と rect2 の内容は同じですが、定義する順番(7〜12行と14〜19行)が異なります。リストAでは rect1(赤い Rectangle) を rect2(緑の Rectangle) よりも先に定義し、リストBでは逆に rect2 を先に定義しています。それぞれの実行結果を以下に示します。

[caption id="attachment_2516" align="aligncenter" width="280" caption="リストAの実行結果"] リストAの実行結果[/caption]
[caption id="attachment_2517" align="aligncenter" width="280" caption="リストBの実行結果"] リストBの実行結果[/caption]

リストAでは rect1(赤い Rectangle) が後ろ側に位置するため、その一部が rect2(緑の Rectangle) によって隠されています。リストBではその逆です。このように QML では [qml '後から定義された要素を前面に表示' e=item p=z]します。

ソースの定義順によって、表示の重なりが処理されることは確認できました。しかし、重なりが生じるレイアウトを作成する場合に、その順番を変更する方法がソースの変更だけでは困ります。QML では [qml '' z e=item] プロパティを用いて、表示の優先順位を変更することが可能です。z プロパティは擬似的な3次元座標として考えてください。手前側がプラス方向となります。
先ほどのリストBの rect2(緑の Rectangle) に以下の z プロパティを追加して実行してみましょう。

z: 1

リストBでは赤い Rectangle の後ろにあった緑の Rectangle がリストAと同様に赤い Rectangle の前に表示されました。このように z プロパティを変更することによって、要素の重なりを変更することが出来ます。

// リストB の rect2 に z:1 を追加
import Qt 4.7

Rectangle {
width: 200; height: 200

Rectangle {
id: rect2
x: 75; y: 75
width: 100; height: 100
color: "green"
z: 1
}

Rectangle {
id: rect1
x: 25; y: 25
width: 100; height: 100
color: "red"
}
}

[caption id="attachment_2567" align="aligncenter" width="280" caption="リストBの rect2 に z: 1 を追加したときの実行結果"] リストBの rect2 に z: 1 を追加したときの実行結果[/caption]

上記で追加した z プロパティの値を 0 や -1 にしてどのように描画が変化するか試してください。0 にした場合は z プロパティを追加しないリストBと同じ結果になり、-1 にした場合にはルート要素よりも奥に配置されるため、緑の Rectangle はルート要素で隠されて見えなくなってしまいます。

上記の結果からも推測できますが、z プロパティを省略した場合の値は 0 となります。この値は [qml '' x e=item] や [qml '' y e=item] プロパティと同様に、その親要素からの相対値になります。そのため、z プロパティが負の値を持つ場合には、必ず親要素よりも後ろに表示されます。ただし、z プロパティの値が有効なのは、その親要素と兄弟要素(同じ親を持つ要素)の間でだけとなります。親要素の z プロパティの値と合算して z プロパティの値が計算されるわけではないことに注意してください。たとえば、次のリストCを実行してみてください。このリストは リストA をベースに z プロパティと Rectangle(rect3) を一つ追加したものです。

// リストC
import Qt 4.7

Rectangle {
width: 200; height: 200

Rectangle {
id: rect1
x: 25; y: 25
width: 100; height: 100
color: "red"
z: 100
}

Rectangle {
id: rect2
x: 75; y: 75
width: 100; height: 100
color: "green"
z: 1

Rectangle {
id: rect3
x: 25; y: 25
width: 50; height: 50
color: "blue"
z: 999
}
}
}

[caption id="attachment_2569" align="aligncenter" width="280" caption="リストCの実行結果"] リストCの実行結果[/caption]

rect3(青い Rectangle)は rect2(緑の Rectangle)の子要素であり、rect1(赤い Rectangle)と rect2 が兄弟関係にあります。rect1 の z は 100 であり、rect2 が 1、rect3 が 999 なので、rect1 が rect2 と rect3 の間に描画されるようにも思えますが、rect1 が rect3 よりも手前に描画されています。このように、描画の優先順位は全ての要素間で計算されるものではなく、親要素と兄弟要素の間でのみ計算します。

以上をまとめると

  1. (親要素とその直接の子要素の中で)z 座標の小さい要素 → 大きい要素
  2. z 座標が同じ場合、親要素 → 先に定義された子要素 → 後から定義された子要素

の順に、それぞれ先に描画されることとなります。
ダイアログ要素のように、その他の要素よりも優先して表示したい要素がある場合、その z プロパティの値をその他のどの要素よりも大きくしてください。

子要素の座標とクリッピング

まずは次のリスト(リストD)を見てください。

// リストD
import Qt 4.7

Rectangle {
width: 200; height: 200

Rectangle {
id: parentRect
x: 25; y: 25
width: 150; height: 150
color: "red"

Rectangle {
id: childRect
x: 25; y: 25
width: 150; height: 150
color: "green"
}
}
}

id を付けた二つの Rectangle(parentRect と childRect) があります。それぞれは親子関係にあり、子要素である childRect の座標は親要素である parentRect からずらしてありますが、そのサイズ([qml '' width e=item], [qml '' height e=item])は同じです。この場合、childRect はどのように描画されるのでしょうか。

リストDの実行結果

childRect(緑のRectangle) は parentRect(赤いRectangle) の範囲を超えて描画されました。このように、QML のデフォルトでは、[qt QWidget] とは違ってクリッピングを行いません。これは [qt 'パフォーマンス' l=qdeclarativeperformance m=#clipping] を確保するためです。もちろん、それだけでは不便ですのでクリッピングする手段を用意してあります。parentRect に以下の [qml '' clip e=item] プロパティを追加してください。

cilp: true

clip プロパティが true である場合、その要素の子要素を描画する際にクリッピング処理を行います。リストD で parentRect がクリッピングを行った場合の実行結果を以下に示します。

リストDにclipを追加した場合の実行結果

この連載ではまだ扱っていませんが、[qml Flickable] やそれを継承した [qml ListView] などのスクロールする要素では特にこの clip プロパティは重要です。これらの要素で描画がはみ出て困った場合には、clip プロパティの値を true に設定してください。
また、クリッピングと親要素の範囲外の座標を組み合わせることによって、アニメーション時に各要素が親要素の範囲外へ出て行ったり、逆に外から入ってくるような動きを作ることも可能です。

要素の変形

ある要素のサイズを変えて描画したい場合には width, height を変更する方法もありますが、[qml '' scale e=item] プロパティを使う方法もあります。scale プロパティには実数型の値を指定します。 また、回転させる場合には [qml '' rotation e=item] プロパティを使います。角度(degree)を実数型の値で指定します。これらのプロパティは、指定されたパラメータに従って、自分自身とその子要素を変形させます。
scale や rotation プロパティでは width や height を変更する場合と違って、見かけ上の変形だけを行います。すなわち、x, y や width, height といった各プロパティの値そのものは変化しません。
次のリストE(scale)およびリストF(rotation)のそれぞれを実行してみてください。rect2(緑の Rectangle)はアンカーレイアウトを用いて rect1(赤い Rectangle) に隣接させているつもりですが、変形後の rect1 には隣接していません。
変形後の要素に対してレイアウトを行いたい場合には注意してください。
より複雑な変形を行いたい場合には [qml '' transformOrigin e=item] や [qml '' transform e=item] プロパティ、[qml Translate], [qml Scale], [qml Rotation] 要素を使用してください。

// リストE
import Qt 4.7

Rectangle {
width: 200; height: 125

Rectangle {
id: rect1
x: 25; y: 25;
width: 75; height: 75
color: "red"
scale: 1.5
}

Rectangle {
id: rect2
anchors.left: rect1.right
anchors.top: rect1.top
width: 75; height: 75
color: "green"
}
}

// リストF
import Qt 4.7

Rectangle {
width: 200; height: 125

Rectangle {
id: rect1
x: 25; y: 25;
width: 75; height: 75
color: "red"
rotation: 45
}

Rectangle {
id: rect2
anchors.left: rect1.right
anchors.top: rect1.top
width: 75; height: 75
color: "green"
}
}

[caption id="attachment_2574" align="aligncenter" width="280" caption="リストEの実行結果"] リストEの実行結果[/caption]
[caption id="attachment_2575" align="aligncenter" width="280" caption="リストFの実行結果"] リストFの実行結果[/caption]

可視化と半透明

各要素の表示/非表示の切り替えには [qml '' visible e=item] プロパティを使います。bool 型の値(true/false)で指定します。デフォルトは true ですので、非表示にしたい要素では false を指定してください。なお、visible が false な要素は表示されませんが、その逆(表示されてない場合に visible が false)や裏(visible が true なら表示されている)は必ずしも成り立たないことに注意してください。他の要素の後ろに隠れていたり、座標が画面の範囲外だったり、様々な理由で visible が true であっても表示されていない場合があります。なお、このプロパティは子要素にも影響します。visible の値が false な親要素を持つ要素は、たとえその要素の visible が true であっても表示されません。リストA の rect2 に visible プロパティを追加した例をリストGに示します。
半透明の状態を作成する場合には [qml '' opacity e=item] プロパティを使います。0 〜 1.0 までの実数型の値で指定してください。opacity の値が 0 の場合には完全に透明になり、表示されません。デフォルトの 1.0 の場合には完全に不透明となります。ただし、Rectangle の [qml '' color e=rectangle] プロパティに透明色を指定している場合などは、opacity が 1.0 であっても指定した色に従って半透明などで描画されます。このプロパティも子要素に影響することに注意してください。子要素の実際の不透明度は親要素の透明度にその要素の opacity プロパティの値を掛け合わせたものになります。そのため opacity を使って、親要素は半透明だが子要素は不透明である状態を作成することは出来ません。そのような状態を作成したい場合には color プロパティなどを使用してください。その場合は "#AARRGGBB" と8桁の16進数の最初の2桁で透明度を指定します。色の指定方法の詳細は [qml color] を参考にしてください。リストA の rect2 に opacity プロパティを追加した例をリストHに示します。

// リストG
import Qt 4.7

Rectangle {
width: 200; height: 200

Rectangle {
id: rect1
x: 25; y: 25
width: 100; height: 100
color: "red"
}

Rectangle {
id: rect2
x: 75; y: 75
width: 100; height: 100
color: "green"
visible: false
}
}

// リストH
import Qt 4.7

Rectangle {
width: 200; height: 200

Rectangle {
id: rect1
x: 25; y: 25
width: 100; height: 100
color: "red"
}

Rectangle {
id: rect2
x: 75; y: 75
width: 100; height: 100
color: "green"
opacity: 0.5
}
}

それぞれのリストの実行結果は以下の通りです。リストGで visible が false となった rect2 は表示されていません。リストHで opacity が 0.5 (50%)になった rect2 は半透明となり、rect1 が透けて見えています。

[caption id="attachment_2580" align="aligncenter" width="280" caption="リストGの実行結果"] リストGの実行結果[/caption]
[caption id="attachment_2581" align="aligncenter" width="280" caption="リストHの実行結果"] リストHの実行結果[/caption]

まとめ

前回のレイアウトに引き続いて、今回は各要素に共通した、描画に関する様々なプロパティなどを紹介しました。この2回の内容を組み合わせることで、静止した UI に関しては、かなりの領域をカバーできるのではないかと思います。
次回はようやく Qt Creator 2.1 が正式リリースされましたので、Qt Quick デザイナの使用方法について説明しようと思います。


Blog Topics:

Comments