Flickable.atXBeginningで何ができる?Qt QMLでのスクロールUI実装例
Flickableとは何か?
まず、Flickable
について簡単に説明します。Flickable
は、タッチデバイスなどでコンテンツを「フリック」してスクロールさせるUI要素を実装するためのQt Quickのコンポーネントです。大きな画像の一部を表示したり、長いリストをスクロールさせたりする際に使われます。ユーザーが指で画面をドラッグしたり、素早くフリックしたりすることで、表示されているコンテンツが移動し、隠れていた部分が見えるようになります。
Flickable.atXBeginning
とは何か?
Flickable.atXBeginning
は、ブール型(真偽値)のプロパティで、Flickable
のコンテンツが水平方向(X軸方向)の先頭(一番左端)に位置しているかどうかを示します。
- falseの場合
コンテンツが水平方向のスクロール範囲の先頭にないことを意味します。まだ左にスクロールできるか、すでに右にスクロールされています。 - trueの場合
コンテンツが水平方向のスクロール範囲の先頭に達していることを意味します。つまり、それ以上左にスクロールできない状態です。
使用例と目的
このプロパティは、UIの状態を判断したり、それに基づいて何らかの動作をトリガーしたりするのに役立ちます。例えば、以下のようなシナリオで利用できます。
- エッジ効果の実装
先頭に達したときに、コンテンツが少し跳ね返るような視覚効果(バウンド効果)を実装する。 - 特定のアクションの有効/無効化
一番左までスクロールされたときに、「前のページへ」といったナビゲーションボタンを無効にする。 - スクロールインジケーターの表示/非表示
コンテンツが先頭にあるときに、水平スクロールバーや矢印を非表示にする。
関連するプロパティ
atXBeginning
と同様に、以下のプロパティも存在します。
atYEnd
: コンテンツが垂直方向の末尾(一番下)に位置しているかどうか。atYBeginning
: コンテンツが垂直方向の先頭(一番上)に位置しているかどうか。atXEnd
: コンテンツが水平方向の末尾(一番右端)に位置しているかどうか。
contentWidth または contentHeight の設定不足/誤り
問題
Flickable.atXBeginning
が常にtrue
になる、または期待通りにスクロールしない。
原因: Flickable
は、表示するコンテンツの総幅 (contentWidth
) と総高さ (contentHeight
) を知る必要があります。これらのプロパティが正しく設定されていないと、Flickable
はコンテンツがその境界内に収まっていると誤解し、スクロールを許可しなかったり、atXBeginning
が常にtrue
になったりします。
トラブルシューティング:
- contentItem.childrenRect.width / heightを使用する
Flickable
内に複数の子要素があり、それらの合計サイズをコンテンツ幅/高さにしたい場合、contentItem.childrenRect.width
やcontentItem.childrenRect.height
が役立つことがあります。ただし、これは子要素の原点(x
,y
)が考慮されるため、意図しない結果になる可能性もあります。Flickable { id: flickableArea width: 400 height: 300 contentWidth: contentItem.childrenRect.width contentHeight: contentItem.childrenRect.height clip: true Column { // contentItemの直接の子 width: childrenRect.width // Column自身の幅を子要素に合わせて調整 spacing: 10 Rectangle { width: 200; height: 50; color: "red" } Rectangle { width: 300; height: 70; color: "green" } Rectangle { width: 500; height: 100; color: "blue" } } }
- contentWidthとcontentHeightを明示的に設定する
Flickable { width: 400 height: 300 contentWidth: myContent.width // または具体的な値 contentHeight: myContent.height // または具体的な値 clip: true // コンテンツがFlickableの境界外に出るのを防ぐ Item { id: myContent width: 800 // Flickableの幅より大きくする height: 600 // Flickableの高さより大きくする // ... コンテンツ ... } }
Flickable内にスクロール可能な別の要素がある
問題
Flickable
内にListView
や別のFlickable
など、独自のスクロール機能を持つ要素を配置した場合、親のFlickable
のatXBeginning
が期待通りに更新されない、またはフリックが正しく伝播しない。
原因: イベントの伝播(event propagation)の問題です。タッチイベントが子要素で消費され、親のFlickable
に到達しないことがあります。
トラブルシューティング:
- カスタムのタッチハンドリング
複雑なインタラクションが必要な場合は、MultiPointTouchArea
などを使用して、より低レベルでタッチイベントを処理し、独自のロジックでスクロールを制御することを検討します。 - Flickableのinteractiveプロパティを制御する
特定の状況で親または子のFlickable
のinteractive
プロパティをfalse
に設定することで、フリックの競合を避けることができます。例えば、子要素がアクティブな間は親のフリックを無効にするなど。 - MouseAreaのpropagateComposedEventsを使用する
子要素がMouseArea
を使用している場合、MouseArea { propagateComposedEvents: true }
を設定することで、イベントを親に伝播させることができます。ただし、これによりフリックの競合が発生する可能性があります。 - Flickableのネストを避ける
可能であれば、複数のスクロール領域をネストするのではなく、単一のFlickable
で全てを管理できないか検討します。
コンテンツの動的な変更
問題
Flickable
のコンテンツ(例えば、リストのアイテムなど)が動的に追加・削除された後、atXBeginning
の状態が一時的に不安定になる、または更新が遅れる。
原因: コンテンツのサイズが変更された際に、Flickable
がその新しいサイズを検知し、スクロール範囲を再計算するまでに若干の遅延が生じることがあります。特に、contentItem.childrenRect
を使用している場合に顕著です。
トラブルシューティング:
- 強制的な更新(非推奨): 通常は不要ですが、非常に稀なケースで、
Flickable.forceLayout()
のようなメソッド(QMLでは直接提供されていないが、C++からアクセスできる場合がある)を呼び出すことで、レイアウトの再計算を強制することができます。ただし、これはパフォーマンスに影響を与える可能性があります。 - アニメーションとの連携
コンテンツの追加・削除にアニメーションを使用している場合、アニメーションが完了するまでatXBeginning
の状態に依存するロジックを遅延させることを検討します。 - FlickableのonContentWidthChanged / onContentHeightChangedシグナルを監視する
コンテンツの幅や高さが変更された際に、関連するロジック(例えば、ナビゲーションボタンの有効/無効化)を更新します。
Flickableの境界動作 (boundsBehavior) の理解不足
問題
atXBeginning
がtrue
になるタイミングが、コンテンツが完全に端に到達した時と異なるように見える。
原因: Flickable
のboundsBehavior
プロパティは、コンテンツがスクロール可能な境界を超えて移動できるかどうかを制御します。デフォルトではFlickable.DragAndOvershootBounds
になっており、ユーザーがドラッグしたりフリックしたりした際にコンテンツが少しだけ境界をはみ出すことができます。この「はみ出し」があるため、見た目上は端に達していてもatXBeginning
がまだfalse
である可能性があります。
トラブルシューティング:
- オーバーシュート量 (horizontalOvershoot, verticalOvershoot) を考慮する
boundsBehavior
がDragAndOvershootBounds
の場合、horizontalOvershoot
やverticalOvershoot
プロパティで指定された量だけコンテンツがはみ出すことができます。これらの値が0でない限り、視覚的な端と論理的なatXBeginning
が一致しない可能性があります。 - boundsBehaviorを確認する
Flickable.StopAtBounds
を設定すると、コンテンツは境界で正確に停止し、はみ出しがなくなります。これにより、atXBeginning
がより直感的に動作するようになります。Flickable { // ... boundsBehavior: Flickable.StopAtBounds // ... }
レイアウトの問題とFlickableのサイズ
問題
Flickable
自体が意図したサイズになっておらず、結果としてatXBeginning
が正しく機能しない。
原因: Flickable
のwidth
やheight
、またはanchors
の設定が間違っていると、Flickable
の表示領域が正しく設定されず、コンテンツのスクロール範囲もおかしくなります。
トラブルシューティング:
- anchorsの適切な使用
親要素に対するanchors.fill: parent
や、具体的なwidth
/height
設定が正しいことを確認します。 - Flickableのサイズと位置をデバッグする
Rectangle
などでFlickable
を囲み、border.color
などを設定して、実際にFlickable
がどのくらいの領域を占めているかを確認します。
例1: 先頭にいるときに左スクロール矢印を非表示にする
これは最も一般的なユースケースの一つです。コンテンツが既に一番左にある場合、それ以上左にスクロールする必要がないため、左方向へのスクロールを示すUI要素(例えば、矢印アイコン)を非表示にします。
// FlickableAtXBeginningExample1.qml
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
ApplicationWindow {
width: 600
height: 400
visible: true
title: "Flickable.atXBeginning Example 1"
ColumnLayout {
anchors.fill: parent
anchors.margins: 20
Text {
text: "水平スクロール可能なコンテンツ"
font.pixelSize: 20
Layout.alignment: Qt.AlignHCenter
}
// Flickable
Flickable {
id: myFlickable
Layout.fillWidth: true
Layout.preferredHeight: 200
clip: true // Flickableの境界外に出るコンテンツをクリップする
flickableDirection: Flickable.HorizontalFlick // 水平方向のみフリック可能
// コンテンツの幅を設定することが重要!
// この例では、子要素の合計幅を使う
contentWidth: contentRow.width
contentHeight: contentRow.height // 垂直スクロールしないので、子要素の高さに合わせる
Row {
id: contentRow
spacing: 10
// 複数の長方形を並べて、Flickableの幅よりも広くする
Repeater {
model: 10 // 10個のアイテム
delegate: Rectangle {
width: 150
height: 150
color: Qt.rgba(Math.random(), Math.random(), Math.random(), 1)
border.color: "black"
border.width: 1
Text {
text: index + 1
anchors.centerIn: parent
font.pixelSize: 30
color: "white"
}
}
}
}
// スクロール位置の表示 (デバッグ用)
Text {
id: debugText
anchors.top: parent.bottom
anchors.left: parent.left
anchors.leftMargin: 5
text: "X: " + Math.round(myFlickable.contentX) + " atXBeginning: " + myFlickable.atXBeginning
color: "black"
font.pixelSize: 14
}
}
RowLayout {
Layout.fillWidth: true
Layout.preferredHeight: 50
Layout.alignment: Qt.AlignHCenter
// 左矢印ボタン
Button {
text: "<"
font.pixelSize: 24
// Flickableが先頭にいる場合に非表示にする
visible: !myFlickable.atXBeginning
// ボタンが非表示になったときもスペースを占有しないようにする
Layout.preferredWidth: visible ? 50 : 0
Layout.preferredHeight: 50
onClicked: {
myFlickable.contentX = Math.max(0, myFlickable.contentX - 100); // 100px左にスクロール
}
}
// 右矢印ボタン (おまけ: Flickable.atXEnd の例)
Button {
text: ">"
font.pixelSize: 24
// Flickableが末尾にいる場合に非表示にする
visible: !myFlickable.atXEnd
Layout.preferredWidth: visible ? 50 : 0
Layout.preferredHeight: 50
onClicked: {
myFlickable.contentX = Math.min(myFlickable.contentWidth - myFlickable.width, myFlickable.contentX + 100); // 100px右にスクロール
}
}
}
}
}
解説
myFlickable.flickableDirection: Flickable.HorizontalFlick
で、水平方向のみスクロールできるようにしています。myFlickable.contentWidth: contentRow.width
で、Flickable
がスクロールすべき総幅を、内部のRow
要素の幅に設定しています。これが正しく設定されていないと、Flickable
はスクロール可能であると認識せず、atXBeginning
も常にtrue
のままになる可能性があります。- 左矢印の
Button
のvisible
プロパティを!myFlickable.atXBeginning
にバインドしています。これにより、Flickable
が一番左端に位置すると、ボタンが非表示になります。 Layout.preferredWidth: visible ? 50 : 0
を設定することで、visible: false
になった際にボタンが占有するレイアウトスペースも0になり、他の要素がそのスペースを埋めることができます。
例2: atXBeginning
の状態変化に基づいてアニメーションをトリガーする
atXBeginning
プロパティはonAtXBeginningChanged
シグナルも持っています。このシグナルを監視することで、状態の変化に応じて特定のアニメーションや動作をトリガーすることができます。
// FlickableAtXBeginningExample2.qml
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
ApplicationWindow {
width: 600
height: 400
visible: true
title: "Flickable.atXBeginning Example 2"
ColumnLayout {
anchors.fill: parent
anchors.margins: 20
Text {
text: "先頭に到達すると、フリック可能エリアが点滅"
font.pixelSize: 20
Layout.alignment: Qt.AlignHCenter
}
Flickable {
id: animatedFlickable
Layout.fillWidth: true
Layout.preferredHeight: 200
clip: true
flickableDirection: Flickable.HorizontalFlick
contentWidth: contentContainer.width
contentHeight: contentContainer.height
Rectangle {
id: contentContainer
width: 1000 // Flickableの幅より広くする
height: parent.height
color: "lightgray"
border.color: "darkgray"
border.width: 2
Text {
text: "スクロールしてください"
anchors.centerIn: parent
font.pixelSize: 40
color: "darkblue"
}
}
// `atXBeginning` が変化したときに実行されるロジック
onAtXBeginningChanged: {
if (atXBeginning) {
console.log("Flickableが先頭に到達しました!");
// Rectangleの境界色をアニメーションで点滅させる
borderAnimation.start();
} else {
console.log("Flickableが先頭から離れました。");
borderAnimation.stop(); // アニメーションを停止
contentContainer.border.color = "darkgray"; // 色を元に戻す
}
}
ColorAnimation on border.color {
id: borderAnimation
target: contentContainer
from: "darkgray"
to: "red"
duration: 500
loops: Animation.Infinite // 無限ループ
running: false // デフォルトでは停止
}
}
}
}
解説
Flickable
のonAtXBeginningChanged
シグナルハンドラを使用しています。atXBeginning
がtrue
になった場合(コンテンツが先頭に到達した場合)、borderAnimation.start()
を呼び出して、contentContainer
の境界色を点滅させるアニメーションを開始します。atXBeginning
がfalse
になった場合(コンテンツが先頭から離れた場合)、borderAnimation.stop()
を呼び出し、境界色を元のdarkgray
に戻しています。
例3: atXBeginning
の状態に応じて異なるコンポーネントを表示する
Loader
やState
と組み合わせて、atXBeginning
の状態に基づいてUIの一部を切り替えることも可能です。
// FlickableAtXBeginningExample3.qml
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
ApplicationWindow {
width: 600
height: 400
visible: true
title: "Flickable.atXBeginning Example 3"
ColumnLayout {
anchors.fill: parent
anchors.margins: 20
Text {
text: "先頭にいると異なるメッセージを表示"
font.pixelSize: 20
Layout.alignment: Qt.AlignHCenter
}
Flickable {
id: messageFlickable
Layout.fillWidth: true
Layout.preferredHeight: 200
clip: true
flickableDirection: Flickable.HorizontalFlick
contentWidth: largeContent.width
contentHeight: largeContent.height
Rectangle {
id: largeContent
width: 1200 // 十分広くする
height: parent.height
color: "lightcyan"
Row {
anchors.fill: parent
spacing: 20
Repeater {
model: 10
delegate: Rectangle {
width: 100
height: parent.height - 20
y: 10
color: Qt.rgba(Math.random(), Math.random(), Math.random(), 1)
border.color: "black"
border.width: 1
Text {
text: "Item " + (index + 1)
anchors.centerIn: parent
font.pixelSize: 18
color: "black"
}
}
}
}
}
}
// Flickableの状態に応じて表示を切り替えるテキスト
Text {
id: statusMessage
Layout.fillWidth: true
Layout.preferredHeight: 50
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
font.pixelSize: 24
color: "darkblue"
// messageFlickable.atXBeginning の状態に基づいてテキストを切り替える
text: messageFlickable.atXBeginning ? "あなたはコンテンツの先頭にいます!" : "右にスクロールしてコンテンツを見てください。"
// テキストの切り替えを滑らかにするためのColorAnimation
ColorAnimation on color {
from: "darkblue"
to: messageFlickable.atXBeginning ? "green" : "darkblue"
duration: 300
}
}
}
}
statusMessage
のtext
プロパティを三項演算子 (? :
) を使ってmessageFlickable.atXBeginning
の値にバインドしています。これにより、atXBeginning
がtrue
なら「あなたはコンテンツの先頭にいます!」、false
なら「右にスクロールしてコンテンツを見てください。」と表示されます。ColorAnimation
を使って、テキストのcolor
もatXBeginning
の状態に応じてスムーズに変化させています。
Flickable.atXBeginning
は、Flickable
のcontentX
プロパティがゼロ(またはoriginX
)に等しいかどうかを内部的にチェックしています。したがって、このプロパティの代わりに、contentX
とoriginX
を直接比較することで同様のロジックを実装できます。
Flickable.contentX と Flickable.originX を直接比較する
なぜ代替となるか
atXBeginning
は、まさにこの比較を行っているラッパープロパティです。これを直接使用することで、より低レベルな制御が可能になります。特に、スクロールの「しきい値」をカスタマイズしたい場合や、わずかなオーバーシュートを許容したい場合に有用です。
コード例
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
ApplicationWindow {
width: 600
height: 400
visible: true
title: "Flickable.contentX Manual Check"
ColumnLayout {
anchors.fill: parent
anchors.margins: 20
Text {
text: "contentX を手動でチェック"
font.pixelSize: 20
Layout.alignment: Qt.AlignHCenter
}
Flickable {
id: myFlickable
Layout.fillWidth: true
Layout.preferredHeight: 200
clip: true
flickableDirection: Flickable.HorizontalFlick
contentWidth: contentRow.width
contentHeight: contentRow.height
Row {
id: contentRow
spacing: 10
Repeater {
model: 10
delegate: Rectangle {
width: 150
height: 150
color: Qt.rgba(Math.random(), Math.random(), Math.random(), 1)
border.color: "black"
border.width: 1
Text {
text: index + 1
anchors.centerIn: parent
font.pixelSize: 30
color: "white"
}
}
}
}
// atXBeginning の代わりに contentX を使用
property bool isAtBeginningManually: {
// わずかな浮動小数点の誤差を考慮して Math.abs() を使う
// Math.abs(myFlickable.contentX - myFlickable.originX) < 1.0 のように閾値を設けることも可能
return myFlickable.contentX <= myFlickable.originX + 1; // わずかな誤差を許容
}
Text {
id: debugText
anchors.top: parent.bottom
anchors.left: parent.left
anchors.leftMargin: 5
text: "contentX: " + Math.round(myFlickable.contentX) +
" originX: " + Math.round(myFlickable.originX) +
" isAtBeginningManually: " + myFlickable.isAtBeginningManually
color: "black"
font.pixelSize: 14
}
}
RowLayout {
Layout.fillWidth: true
Layout.preferredHeight: 50
Layout.alignment: Qt.AlignHCenter
Button {
text: "<"
font.pixelSize: 24
visible: !myFlickable.isAtBeginningManually // 手動チェックプロパティを使用
Layout.preferredWidth: visible ? 50 : 0
Layout.preferredHeight: 50
onClicked: {
myFlickable.contentX = Math.max(0, myFlickable.contentX - 100);
}
}
Button {
text: ">"
font.pixelSize: 24
visible: myFlickable.contentX + myFlickable.width < myFlickable.contentWidth - 1 // 手動で atXEnd に相当するチェック
Layout.preferredWidth: visible ? 50 : 0
Layout.preferredHeight: 50
onClicked: {
myFlickable.contentX = Math.min(myFlickable.contentWidth - myFlickable.width, myFlickable.contentX + 100);
}
}
}
}
}
利点
ListView
やGridView
のようにoriginX
が動的に変わる可能性があるコンテキストでも、正確な「先頭」判定ができる。- 必要に応じて、厳密な
0
ではなく、微小な誤差範囲(例:contentX < 5
など)を「先頭」と見なすようなカスタマイズが可能。 atXBeginning
が内部的に行っていることと全く同じことを理解できる。
欠点
- 浮動小数点数の比較には注意が必要(
=
ではなく、Math.abs(a - b) < epsilon
のような比較が推奨される)。 atXBeginning
プロパティを使う方が、コードが簡潔で意図が明確になることが多い。
onContentXChanged シグナルハンドラ内でロジックを実装する
なぜ代替となるか
atXBeginning
はあくまで現在の状態を示すプロパティであり、その変化を検知するにはonAtXBeginningChanged
シグナルを使用します。onContentXChanged
を使うことで、atXBeginning
プロパティが提供するよりも細かい粒度でスクロールイベントを監視し、特定のスクロール位置に到達した際の処理を実装できます。
コード例
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
ApplicationWindow {
width: 600
height: 400
visible: true
title: "Flickable.onContentXChanged Example"
ColumnLayout {
anchors.fill: parent
anchors.margins: 20
Text {
text: "onContentXChanged でイベントを監視"
font.pixelSize: 20
Layout.alignment: Qt.AlignHCenter
}
Flickable {
id: eventFlickable
Layout.fillWidth: true
Layout.preferredHeight: 200
clip: true
flickableDirection: Flickable.HorizontalFlick
contentWidth: contentGrid.width
contentHeight: contentGrid.height
Grid {
id: contentGrid
columns: 5
spacing: 5
Repeater {
model: 20 // 20個のアイテム
delegate: Rectangle {
width: 100
height: 100
color: Qt.rgba(Math.random(), Math.random(), Math.random(), 1)
border.color: "black"
border.width: 1
Text {
text: index + 1
anchors.centerIn: parent
font.pixelSize: 25
color: "white"
}
}
}
}
// スクロール位置が変更されるたびに呼び出される
onContentXChanged: {
if (contentX <= originX + 1) { // ほぼ先頭にいる
statusLabel.text = "コンテンツが先頭にあります!";
statusLabel.color = "green";
} else if (contentX >= contentWidth - width - 1) { // ほぼ末尾にいる
statusLabel.text = "コンテンツが末尾にあります!";
statusLabel.color = "red";
} else {
statusLabel.text = "スクロール中...";
statusLabel.color = "darkblue";
}
}
}
Text {
id: statusLabel
Layout.fillWidth: true
Layout.preferredHeight: 50
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
font.pixelSize: 24
color: "darkblue"
text: "スクロールしてください" // 初期値
}
}
}
利点
- スクロールの進行状況を示すプログレスバーのようなUIを実装する際に、
contentX
から直接計算できる。 atXBeginning
がtrue
/false
という単純な状態だけでなく、特定のスクロール位置に到達したときなど、より複雑な条件に基づいてアクションをトリガーできる。- スクロール位置の変化に即座に反応できる。
欠点
atXBeginning
のような単純な真偽値が必要なだけなら、オーバーキルになる場合がある。contentX
は非常に頻繁に更新される可能性があるため、重い計算をonContentXChanged
内で行うとパフォーマンスに影響を与える可能性がある。
Flickable.visibleArea を使用する (より高度な場合)
なぜ代替となるか
atXBeginning
は「先頭にいるかどうか」という単純なチェックですが、visibleArea.xPosition
は0から1までの正規化された値で現在の水平スクロール位置を示します。コンテンツが先頭にいる場合、xPosition
は0に近くなります。
コード例
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
ApplicationWindow {
width: 600
height: 400
visible: true
title: "Flickable.visibleArea.xPosition Example"
ColumnLayout {
anchors.fill: parent
anchors.margins: 20
Text {
text: "visibleArea.xPosition でスクロール位置を視覚化"
font.pixelSize: 20
Layout.alignment: Qt.AlignHCenter
}
Flickable {
id: ratioFlickable
Layout.fillWidth: true
Layout.preferredHeight: 200
clip: true
flickableDirection: Flickable.HorizontalFlick
contentWidth: largeImage.width // 例として大きな画像
contentHeight: largeImage.height
Image {
id: largeImage
source: "https://via.placeholder.com/1200x400/FF5733/FFFFFF?text=Large+Image+Content" // ダミー画像
width: 1200
height: 400
}
// visibleArea.xPosition は 0.0 (先頭) から 1.0 (末尾) まで
// 実際は contentWidth - width の範囲なので、最後の部分は見えない
property bool isAtBeginningViaRatio: ratioFlickable.visibleArea.xPosition < 0.01 // 1%の閾値
Text {
anchors.top: parent.bottom
anchors.left: parent.left
anchors.leftMargin: 5
text: "xPosition: " + ratioFlickable.visibleArea.xPosition.toFixed(2) +
" isAtBeginningViaRatio: " + ratioFlickable.isAtBeginningViaRatio
color: "black"
font.pixelSize: 14
}
}
// スクロールバーの代替としてのプログレスバー
ProgressBar {
id: scrollProgress
Layout.fillWidth: true
Layout.preferredHeight: 20
value: ratioFlickable.visibleArea.xPosition // xPosition をプログレスバーの値にバインド
from: 0
to: 1
contentItem: Rectangle {
implicitWidth: parent.width * parent.value
implicitHeight: parent.height
color: "blue"
}
background: Rectangle {
implicitWidth: parent.width
implicitHeight: parent.height
color: "lightgray"
border.color: "darkgray"
border.width: 1
}
}
Text {
Layout.fillWidth: true
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
font.pixelSize: 20
color: "darkgreen"
text: ratioFlickable.isAtBeginningViaRatio ? "コンテンツの先頭です!" : "スクロール中..."
}
}
}
利点
widthRatio
やheightRatio
を使って、表示されているコンテンツの割合を把握できる。- 正規化された値 (
0.0
から1.0
) でスクロール位置を把握できるため、プログレスバーのようなUIに直接バインドしやすい。