Qt QML Flickable入門:スマホアプリのようなフリック操作を実装

2025-05-27

「Flickable」は、Qt Quick(QML)で提供される要素の一つで、その名の通り、「フリック操作」によってコンテンツをスクロールさせる機能を提供します。スマートフォンやタブレットのUIでよく見られる、指で画面をなぞってコンテンツを上下左右に移動させる操作を簡単に実装することができます。

Flickableの主な特徴と機能

  • プログラムからのフリック制御
    flick() メソッドを使って、プログラム的にコンテンツを特定の速度でフリックさせることができます。
  • コンテンツの端かどうかの判定
    atXBeginningatXEndatYBeginningatYEndプロパティで、コンテンツがそれぞれの方向の端に位置しているかどうかを判定できます。
  • 現在の速度の取得
    horizontalVelocityverticalVelocityプロパティで、コンテンツの現在の水平・垂直方向のスクロール速度を取得できます。
  • 端での跳ね返り効果
    スクロールがコンテンツの端に達したときに、軽く跳ね返るような視覚効果を出すことができます。
  • 慣性スクロール
    フリック操作後、指を離してもコンテンツが勢いよくスクロールし続ける「慣性スクロール」の機能が組み込まれています。
  • フリック方向の制御
    • flickableDirection: フリック可能な方向(縦方向のみ、横方向のみ、両方向、またはコンテンツのサイズに応じて自動)を設定できます。
  • コンテンツの座標管理
    • contentXcontentY: Flickableの表示領域の左上隅に現在表示されている、コンテンツの座標を表します。例えば、上にフリックするとcontentYの値が増加します。
    • contentWidthcontentHeight: Flickableの内部にあるコンテンツ全体の幅と高さを表します。
  • コンテンツの自動的な親子関係
    Flickableの子要素として宣言されたアイテムは、自動的にFlickableのcontentItemの子として配置されます。これは、コンテンツの実際の表示位置やサイズを扱う際に重要になります。
  • スクロール可能な領域の提供
    Flickableの中に配置された子要素(コンテンツ)は、Flickableの表示領域よりも大きい場合、フリック操作によって表示領域内を移動(スクロール)させることができます。

Flickableの利用例

Flickableは、以下のようなUI要素を実装する際の基盤としてよく利用されます。

  • カスタムスクロールビュー
    ListViewGridViewのように、多数のアイテムを表示するカスタムコンポーネントを作成する際に、スクロール機能の基盤として利用できます。実際、ListViewGridViewも内部的にFlickableの機能を利用しています。
  • 画像ビューア
    大きな画像をパン(移動)して表示したり、ズーム機能と組み合わせたりします。
  • 長いテキストビューア
    長い文章を表示し、ユーザーがスクロールして読むことができるようにします。
import QtQuick 2.0
import QtQuick.Window 2.0

Window {
    width: 400
    height: 400
    visible: true
    title: "Flickable Example"

    Flickable {
        id: myFlickable
        anchors.fill: parent // 親要素いっぱいに広げる
        contentWidth: 800 // コンテンツの幅をFlickableの幅より大きくする
        contentHeight: 800 // コンテンツの高さをFlickableの高さより大きくする

        // スクロール可能なコンテンツ
        Rectangle {
            width: myFlickable.contentWidth
            height: myFlickable.contentHeight
            color: "lightgray"

            Text {
                text: "これは長いテキストです。フリックしてスクロールしてください。\n" +
                      "これをコピーして何度も貼り付けて、さらに長くしてください。\n" +
                      "これをコピーして何度も貼り付けて、さらに長くしてください。\n" +
                      "これをコピーして何度も貼り付けて、さらに長くしてください。\n" +
                      "これをコピーして何度も貼り付けて、さらに長くしてください。\n" +
                      "これをコピーして何度も貼り付けて、さらに長くしてください。\n" +
                      "これをコピーして何度も貼り付けて、さらに長くしてください。\n" +
                      "これをコピーして何度も貼り付けて、さらに長くしてください。\n" +
                      "これをコピーして何度も貼り付けて、さらに長くしてください。\n" +
                      "これをコピーして何度も貼り付けて、さらに長くしてください。\n" +
                      "これをコピーして何度も貼り付けて、さらに長くしてください。\n" +
                      "これをコピーして何度も貼り付けて、さらに長くしてください。\n" +
                      "これをコピーして何度も貼り付けて、さらに長くしてください。\n" +
                      "これをコピーして何度も貼り付けて、さらに長くしてください。\n" +
                      "これをコピーして何度も貼り付けて、さらに長くしてください。\n" +
                      "これをコピーして何度も貼り付けて、さらに長くしてください。"
                font.pixelSize: 20
                anchors.centerIn: parent
                width: parent.width - 20
                wrapMode: Text.WordWrap
            }
        }
    }
}

この例では、Flickableの中にRectangleTextを配置しています。FlickablecontentWidthcontentHeightを、Flickable自体のサイズよりも大きく設定することで、テキストがスクロール可能になります。



スクロールできない、または期待通りに動かない

これは最もよくある問題です。原因はいくつか考えられます。

原因と解決策

  • 他のMouseAreaとの競合

    • 原因
      Flickable内に別のMouseAreaがあり、そのMouseAreaがフリックイベントを「奪って」しまっている可能性があります。特に、MouseAreapropagateComposedEventsfalse(デフォルト)で、Flickableの領域全体を覆っている場合によく起こります。
    • 解決策
      • MouseAreapropagateComposedEvents: trueを設定して、イベントをFlickableに伝播させる。
      • MouseAreamouse.accepted = falseを使って、イベント処理後に他の要素にイベントを渡す。
      • FlickableとMouseAreaの階層構造を見直す。多くの場合、Flickableがイベントを処理すべき領域では、MouseAreaを直接Flickableの親に配置するのではなく、Flickableの子として配置し、特定の要素に限定してクリックイベントなどを処理するようにします。
  • interactive プロパティ

    • 原因
      interactiveプロパティがfalseに設定されている場合、Flickableはユーザーからの操作を受け付けません。
    • 解決策
      interactive: true(デフォルト)であることを確認してください。
  • flickableDirection の設定

    • 原因
      flickableDirectionFlickable.HorizontalFlickまたはFlickable.VerticalFlickに設定されている場合、設定されていない方向にはフリックできません。
    • 解決策
      両方向にフリックしたい場合はFlickable.AutoFlickDirection(デフォルト)またはFlickable.HorizontalAndVerticalFlickを使用します。
    • 原因
      Flickable がスクロール可能になるには、そのcontentWidthまたはcontentHeightが、Flickable自体のwidthまたはheightよりも大きくなければなりません。コンテンツのサイズがFlickableのサイズと同じか小さい場合、スクロールする余地がないため、フリックしても何も起こりません。
    • 解決策
      Flickable内に表示したいコンテンツ全体のサイズに合わせて、contentWidthcontentHeightを適切に設定してください。
      Flickable {
          width: 300
          height: 200
          contentWidth: myContent.width // コンテンツの実際の幅を設定
          contentHeight: myContent.height // コンテンツの実際の高さを設定
      
          Rectangle {
              id: myContent
              width: 600 // Flickableの幅より大きい
              height: 400 // Flickableの高さより大きい
              color: "lightblue"
              // ... コンテンツ
          }
      }
      
    • ヒント
      コンテンツのサイズが動的に変わる場合は、contentItem.childrenRect.widthcontentItem.childrenRect.heightを使って、コンテンツの合計サイズにバインドすると便利です。
      Flickable {
          // ...
          contentWidth: contentItem.childrenRect.width
          contentHeight: contentItem.childrenRect.height
      
          Column { // contentItemの子として自動的に配置される
              // ... 多くのアイテム
          }
      }
      

コンテンツがFlickableの境界からはみ出す/クリッピングされない

Flickableの領域を超えてコンテンツが表示されてしまう問題です。

原因と解決策

  • clip プロパティがfalse
    • 原因
      Flickableのclipプロパティがfalse(デフォルト)の場合、Flickableの境界を超えるコンテンツはそのまま表示されます。
    • 解決策
      clip: trueを設定して、Flickableの境界外のコンテンツをクリッピング(非表示に)します。
      Flickable {
          // ...
          clip: true // これを設定することで、Flickableの領域外は表示されなくなる
          // ... コンテンツ
      }
      

Flickableが想定外の初期位置にある

アプリケーション起動時やページ遷移時に、Flickableのコンテンツが予期しない位置から始まることがあります。

原因と解決策

  • contentX / contentY の初期値
    • 原因
      明示的に設定しない場合、contentXcontentYは通常0(左上)から始まりますが、コンテンツの動的なサイズ変更やレイアウトによって予期せぬ位置になることがあります。
    • 解決策
      contentXcontentYプロパティを初期位置として設定したい値に明示的に設定します。
      Flickable {
          // ...
          contentX: 0 // 左端に設定
          contentY: 0 // 上端に設定
          // ...
      }
      
    • ヒント
      contentItemの子のサイズが変化した場合に、FlickableのcontentX/contentYが自動的に調整されることがあります。これは意図的な動作の場合もありますが、もし固定したい場合は、明示的に値を設定するか、コンテンツのサイズ変更イベントに応じて再計算・設定することを検討してください。

flick() メソッドが機能しない

プログラムからflick()メソッドを呼び出しても、コンテンツが動かないことがあります。

原因と解決策

  • 速度の不足
    • 原因
      flick()メソッドの引数はピクセル/秒の速度を表します。非常に小さな値を渡すと、視覚的にほとんど動きが見えないことがあります。
    • 解決策
      十分な速度(例:flick(0, 1000))を設定してみてください。また、flickDecelerationプロパティがデフォルトで設定されているため、すぐに減速してしまうことも考慮してください。

ListView や GridView 内の Flickable の挙動

ListViewGridViewのデリゲート内にFlickableを配置した場合、予期せぬスクロールの競合が発生することがあります。

原因と解決策

  • イベントの競合
    • 原因
      親のListView(またはGridView)もスクロール機能を持っているため、デリゲート内のFlickableとイベントが競合する可能性があります。
    • 解決策
      一般的には、ListViewGridViewのデリゲート内でFlickableを使用することは推奨されません。代わりに、ListViewGridView自体が提供するスクロール機能(orientationプロパティなど)を使用し、デリゲート内のコンテンツはFlickableを使わずにレイアウトを工夫することを検討してください。どうしても必要な場合は、MouseAreapropagateComposedEventsmouse.acceptedを慎重に設定し、どちらがイベントを処理するかを明確にする必要がありますが、複雑になりがちです。

特に多数の複雑なアイテムをFlickable内に配置すると、スクロールがカクカクしたり、パフォーマンスが低下したりすることがあります。

原因と解決策

  • アイテムの数や複雑さ
    • 原因
      多くのQMLアイテムは描画コストが高く、Flickableの描画領域外にあるアイテムも描画計算に含まれることがあります。
    • 解決策
      • clip: true
        これを設定することで、Flickableの表示領域外のアイテムの描画負荷を軽減できます。
      • 遅延ロード (Deferred Loading)
        LoaderRepeaterdelegateプロパティとvisibleプロパティを組み合わせて、現在表示されている範囲のアイテムのみをロード・描画するようにします。
      • 要素の簡素化
        可能であれば、複雑なグラフィック効果や多数の要素を持つアイテムを減らすことを検討してください。
      • GPUの活用
        Qt QuickはGPUを利用して描画されますが、不適切な使用(例えば、CPUに負荷がかかる操作の多用)はパフォーマンスを低下させます。


例1:基本的なFlickable(縦方向スクロール)

最も基本的なFlickableの例です。縦方向に長いテキストをスクロールします。

// main.qml
import QtQuick 2.15
import QtQuick.Window 2.15

Window {
    width: 360
    height: 640
    visible: true
    title: "Basic Flickable (Vertical)"

    Flickable {
        id: myFlickable
        anchors.fill: parent // 親のWindowいっぱいに広げる
        contentWidth: parent.width // 幅はFlickableと同じ
        contentHeight: myText.implicitHeight + 40 // コンテンツの高さはテキストの高さ+余白

        clip: true // Flickableの境界外のコンテンツをクリッピング

        Text {
            id: myText
            width: parent.width - 20 // 左右に余白
            anchors.horizontalCenter: parent.horizontalCenter
            // 非常に長いテキストをここに配置
            text: "これは非常に長いテキストです。\n" +
                  "スクロールして全文を読んでください。\n" +
                  "Qt Flickableの基本的な機能を示しています。\n" +
                  "...\n" + // 実際にはもっと多くの行を追加
                  "スクロールの終点に到達しました。\n" +
                  "慣性スクロールと境界での跳ね返り効果を体験できます。\n" +
                  "このテキストは、Flickableの高さよりも意図的に長く設定されています。\n" +
                  "そのため、ユーザーは指で上下にスワイプしてコンテンツ全体を見ることができます。\n" +
                  "Flickableの `clip: true` プロパティにより、Flickableの境界の外側にある部分は表示されません。\n" +
                  "もし `clip: false` に設定すると、境界の外側の部分も表示されてしまいます。\n" +
                  "また、`contentHeight` がコンテンツの実際の高さに合致していることを確認してください。\n" +
                  "もし `contentHeight` が小さすぎると、コンテンツのすべてをスクロールで見ることができません。\n" +
                  "反対に、`contentHeight` が大きすぎると、余分なスクロール領域ができてしまいます。\n" +
                  "この例では、`Text` アイテムの `implicitHeight` を利用して、テキストの高さに合わせて `contentHeight` を動的に設定しています。\n" +
                  "これにより、テキストの内容が変わっても、常に適切なスクロール範囲が確保されます。\n" +
                  "スクロールの際に、コンテンツが指の動きに合わせてスムーズに移動することを確認してください。\n" +
                  "指を離すと、慣性でしばらくスクロールが続き、徐々に減速して停止します。\n" +
                  "これがFlickableの「フリック」という名前の由来です。\n" +
                  "コンテンツがスクロールの端に到達すると、軽く跳ね返るような視覚効果が発生します。\n" +
                  "これは、コンテンツの端に到達したことをユーザーに知らせるための一般的なUIのフィードバックです。\n" +
                  "Flickableは、このようなユーザーフレンドリーなスクロール体験を簡単に実現できます。"
            font.pixelSize: 18
            wrapMode: Text.WordWrap // 単語の途中で改行しない
            color: "darkblue"
            y: 20 // 上からの余白
        }
    }
}

解説

  • clip: true: これがないと、長いテキストがFlickableの境界をはみ出して表示されてしまいます。
  • contentHeight: 内部のTextアイテムのimplicitHeight(テキストの内容によって自動的に計算される高さ)に少し余白を加えることで、テキストの高さに合わせて動的にスクロール範囲を設定しています。
  • contentWidth: Flickable自体の幅と同じにすることで、横方向にはスクロールしないようにしています。

例2:両方向スクロール(画像ビューアの例)

非常に大きな画像をパン(移動)して表示するようなFlickableの例です。

// main.qml
import QtQuick 2.15
import QtQuick.Window 2.15

Window {
    width: 600
    height: 400
    visible: true
    title: "Flickable (Both Directions)"

    Flickable {
        id: imageFlickable
        anchors.fill: parent
        clip: true

        // 非常に大きな画像を用意するか、一時的に色で表現
        // 例: content.png (1200x800など) をプロジェクトフォルダに配置
        // contentWidthとcontentHeightは、画像の実際のサイズに合わせる
        contentWidth: 1200 // 例: 実際の画像の幅
        contentHeight: 800  // 例: 実際の画像の高さ

        // 画像を表示するアイテム
        Image {
            source: "https://upload.wikimedia.org/wikipedia/commons/thumb/1/1d/Mount_Everest_as_seen_from_Dudh_Koshi_Valley%2C_Nepal.jpg/1200px-Mount_Everest_as_seen_from_Dudh_Koshi_Valley%2C_Nepal.jpg" // 実際の画像パスを指定
            width: imageFlickable.contentWidth
            height: imageFlickable.contentHeight
            fillMode: Image.PreserveAspectFit // アスペクト比を維持してフィット
            // fillMode: Image.Stretch // 歪ませてフィットさせる場合
        }

        // 現在のスクロール位置を表示するText
        Text {
            anchors.bottom: parent.bottom
            anchors.left: parent.left
            text: "X: " + imageFlickable.contentX.toFixed(0) +
                  ", Y: " + imageFlickable.contentY.toFixed(0)
            color: "white"
            font.pixelSize: 16
            z: 1 // 手前に表示
            Rectangle {
                anchors.fill: parent
                color: "#80000000" // 半透明の背景
                z: -1
            }
        }
    }
}

解説

  • Textで現在のcontentXcontentYを表示し、スクロール位置を視覚的に確認できるようにしています。
  • ImageアイテムのwidthheightFlickablecontentWidthcontentHeightにバインドすることで、画像がコンテンツ領域全体を占めるようにしています。
  • contentWidthcontentHeightFlickable自体のサイズよりも大きく設定することで、水平・垂直両方向にスクロール可能になります。

Flickableの様々なプロパティ(atXBeginningflickableDirectionなど)や信号(contentXChangedなど)を使った例です。

// main.qml
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15 // Sliderなどのコントロールを使用する場合

Window {
    width: 400
    height: 600
    visible: true
    title: "Flickable Properties & Signals"

    Column {
        anchors.fill: parent
        spacing: 10
        padding: 10

        Row {
            spacing: 10
            Text { text: "Direction:" }
            Button {
                text: "Horizontal"
                onClicked: myFlickable.flickableDirection = Flickable.HorizontalFlick
            }
            Button {
                text: "Vertical"
                onClicked: myFlickable.flickableDirection = Flickable.VerticalFlick
            }
            Button {
                text: "Both"
                onClicked: myFlickable.flickableDirection = Flickable.HorizontalAndVerticalFlick
            }
        }

        Flickable {
            id: myFlickable
            width: parent.width - 20
            height: 300
            clip: true
            flickableDirection: Flickable.AutoFlickDirection // 初期値は自動

            // コンテンツの実際のサイズを設定
            contentWidth: 800
            contentHeight: 600

            Rectangle {
                width: parent.contentWidth
                height: parent.contentHeight
                color: "lightgreen"

                Text {
                    text: "Flickable Content"
                    anchors.centerIn: parent
                    font.pixelSize: 30
                    color: "darkgreen"
                }

                // スクロール位置に応じて色を変える例
                // contentXが0に近いほど赤、contentYが0に近いほど青
                Rectangle {
                    anchors.fill: parent
                    color: Qt.rgba(myFlickable.contentX / myFlickable.contentWidth,
                                   myFlickable.contentY / myFlickable.contentHeight,
                                   0, 0.3) // 半透明
                    z: 1
                }
            }

            // スクロール位置の変化を監視
            onContentXChanged: {
                console.log("Content X changed: " + contentX.toFixed(2));
            }
            onContentYChanged: {
                console.log("Content Y changed: " + contentY.toFixed(2));
            }

            // 端に到達したかを監視
            onAtXBeginningChanged: {
                console.log("At X Beginning: " + atXBeginning);
            }
            onAtYEndChanged: {
                console.log("At Y End: " + atYEnd);
            }

            // プログラム的にスクロールするボタン
            Button {
                text: "Scroll to Center"
                anchors.bottom: parent.bottom
                anchors.horizontalCenter: parent.horizontalCenter
                z: 2 // 手前に表示
                onClicked: {
                    // コンテンツの中心にスクロール
                    myFlickable.contentX = (myFlickable.contentWidth - myFlickable.width) / 2;
                    myFlickable.contentY = (myFlickable.contentHeight - myFlickable.height) / 2;
                }
            }

            Button {
                text: "Flick Down"
                anchors.bottom: parent.bottom
                anchors.right: parent.right
                x: -10
                z: 2
                onClicked: {
                    myFlickable.flick(0, 1000); // 縦方向に1000px/sの速度でフリック
                }
            }
        }

        // Flickableの状態を表示するText
        Text {
            width: parent.width - 20
            wrapMode: Text.WordWrap
            text: "Scroll Position: X=" + myFlickable.contentX.toFixed(0) +
                  ", Y=" + myFlickable.contentY.toFixed(0) + "\n" +
                  "At Start: X=" + myFlickable.atXBeginning + ", Y=" + myFlickable.atYBeginning + "\n" +
                  "At End: X=" + myFlickable.atXEnd + ", Y=" + myFlickable.atYEnd
        }
    }
}
  • "Flick Down"ボタンで、flick()メソッドを使って、指定した速度でフリック動作を開始させています。
  • "Scroll to Center"ボタンで、contentXcontentYを直接設定して、プログラム的にスクロール位置を制御しています。
  • atXBeginningatYEndなどのプロパティをTextで表示し、端に到達したかどうかを確認できます。
  • onContentXChangedなどの信号ハンドラを使って、スクロール位置の変化をコンソールに出力しています。
  • 3つのボタンでflickableDirectionを変更し、スクロール可能な方向を動的に切り替えられます。


ScrollView (Qt Quick Controls)

ScrollViewは、Qt Quick Controlsモジュールが提供する高レベルのコンポーネントです。内部的にはFlickableを使用していますが、スクロールバーの表示や、コンテンツの自動的なサイズ調整など、一般的なスクロールビューで必要となる機能をあらかじめ備えています。

特徴

  • テーマ対応
    Qt Quick Controlsのテーマシステムに準拠しており、見た目を簡単にカスタマイズできます。
  • コンテンツのサイズ調整
    contentItemの子のサイズに基づいて、contentWidthcontentHeightを自動的に調整します。
  • スクロールバーの自動表示
    コンテンツが大きすぎる場合に、自動的にスクロールバーが表示されます。

利点

  • スクロールバーのロジックを自分で書く必要がありません。
  • 一般的なスクロールビューが必要な場合に、Flickableを直接使うよりも手軽に実装できます。

欠点

  • Qt Quick Controlsモジュールへの依存が発生します。
  • Flickableよりも抽象度が高いため、低レベルでのカスタマイズの自由度は低くなります。

使用例

// main.qml
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15 // ScrollViewを使用するために必要

Window {
    width: 360
    height: 480
    visible: true
    title: "ScrollView Example"

    ScrollView {
        anchors.fill: parent
        // スクロールバーの表示ポリシーを設定可能
        // scrollBarPolicy: Qt.Vertical, Qt.ScrollBarAlwaysOn など

        // ここにスクロールさせたいコンテンツを配置
        Column {
            width: parent.width // ScrollViewの幅に合わせてコンテンツの幅を調整
            spacing: 10
            padding: 10

            Repeater {
                model: 50 // 50個の項目
                Rectangle {
                    width: parent.width - 20
                    height: 50
                    color: index % 2 === 0 ? "lightblue" : "lightgreen"
                    Text {
                        text: "Item " + (index + 1)
                        anchors.centerIn: parent
                    }
                }
            }
        }
    }
}

ListView / GridView (データモデルに基づくスクロール)

ListViewGridViewは、データモデルに基づいて大量のアイテムを効率的に表示し、スクロールさせるためのコンポーネントです。これらは内部的にFlickableの機能を利用していますが、単なるスクロールビューではなく、特定のデータを表示するための構造を持っています。

特徴

  • カスタムレイアウト
    ListViewは線形リスト、GridViewはグリッド状のレイアウトを提供します。
  • アイテムのリサイクル
    表示領域外のアイテムの描画をスキップしたり、再利用したりすることで、パフォーマンスを最適化します。
  • モデル/デリゲート方式
    データモデル(ListModel、JavaScript配列など)とデリゲート(各アイテムの表示方法)を組み合わせることで、大量のデータを効率的に表示します。

利点

  • データの結合
    データモデルとの結合が容易です。
  • パフォーマンス
    アイテムのリサイクルにより、パフォーマンスが非常に優れています。
  • 大量のデータ表示に最適
    何百、何千ものアイテムをスムーズにスクロールできます。

欠点

  • デリゲートの概念を理解する必要があります。
  • 固定されたコンテンツ(例:長いテキストファイル全体をスクロール)には向いていません。

使用例

// main.qml
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15

Window {
    width: 360
    height: 480
    visible: true
    title: "ListView Example"

    ListView {
        anchors.fill: parent
        model: ListModel {
            ListElement { name: "Apple"; color: "red" }
            ListElement { name: "Banana"; color: "yellow" }
            ListElement { name: "Cherry"; color: "darkred" }
            ListElement { name: "Date"; color: "brown" }
            // ... さらに多くの要素を追加
        }
        delegate: Rectangle {
            width: parent.width
            height: 60
            color: model.color
            border.color: "black"
            border.width: 1
            Text {
                text: model.name
                anchors.centerIn: parent
                font.pixelSize: 24
                color: "white"
            }
            MouseArea {
                anchors.fill: parent
                onClicked: console.log("Clicked: " + model.name)
            }
        }
    }
}

PathView (カスタムパスに沿ったスクロール)

PathViewは、ListViewGridViewと同様にデータモデルとデリゲートを使用しますが、アイテムを線形やグリッドではなく、カスタム定義されたパスに沿って配置・スクロールさせることができます。

特徴

  • カスタマイズ性
    パスに沿ったアイテムのスケール、透明度、回転などを制御できます。
  • パスベースのレイアウト
    Path要素を使って、アイテムが配置される曲線を定義できます。

利点

  • カバーフローのようなUIや、円形・らせん状のリストなどに適しています。
  • 非常にユニークで視覚的に魅力的なスクロール体験を作成できます。

欠点

  • 一般的なリスト表示にはオーバーキルとなることが多いです。
  • ListViewGridViewよりも複雑で、学習コストが高いです。

使用例

// main.qml
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15

Window {
    width: 800
    height: 600
    visible: true
    title: "PathView Example"

    PathView {
        anchors.fill: parent
        model: 10 // 10個のアイテム
        path: Path {
            startX: 100; startY: 300
            PathLine { x: 700; y: 300 } // 直線パス
            // PathArcやPathQuad、PathCubicなども使える
        }
        delegate: Rectangle {
            width: 100; height: 100
            color: Qt.rgba(Math.random(), Math.random(), Math.random(), 1)
            radius: 10

            // パス上の位置に基づいてスケールや透明度を変更する例
            scale: 1 - Math.abs(PathView.progress - 0.5) * 1.5 // 中央で大きく、端で小さく
            opacity: 1 - Math.abs(PathView.progress - 0.5) * 2 // 中央で不透明、端で透明

            Text {
                text: index
                anchors.centerIn: parent
                font.pixelSize: 30
                color: "white"
            }
        }
        // スナップ設定なども可能
        // snapMode: PathView.SnapOneItem
    }
}

非常に稀なケースですが、Flickableを使わず、RepeaterColumn/RowなどのPositionerと組み合わせて、contentX/contentYプロパティを直接操作し、ユーザー入力(MouseAreaなど)に応じてスクロールを実装することも理論的には可能です。

特徴

  • 完全な手動制御
    スクロールロジックのすべてを自分で実装できます。

利点

  • 非常に特殊なスクロール動作が必要な場合に、柔軟に対応できます。

欠点

  • パフォーマンス
    適切に実装しないと、パフォーマンスが低下する可能性があります。
  • 非常に複雑
    慣性スクロール、スナップ、境界での跳ね返りなど、Flickableが標準で提供する機能をゼロから実装する必要があります。これは非常に手間がかかり、バグの温床になります。

ほとんどの場合、この方法は推奨されません。 特殊な要件がない限り、上記の高レベルなコンポーネントを使用すべきです。

コンポーネント用途の推奨利点欠点
Flickable特定の領域内のコンテンツをフリック操作でスクロールさせたい場合。スクロールバーが不要、またはカスタムしたい場合。シンプルで軽量。低レベルな制御が可能。スクロールバーは自分で実装する必要がある。
ScrollView一般的なスクロールビューが必要な場合。スクロールバーが自動で欲しい場合。スクロールバーが付属。コンテンツの自動サイズ調整。手軽に利用できる。Flickableよりカスタマイズ性は低い。
ListView/GridView大量のアイテム(リストやグリッド形式)をデータモデルに基づいて効率的に表示・スクロールさせたい場合。大量のデータに最適。高パフォーマンス。固定コンテンツのスクロールには不向き。
PathViewアイテムをカスタムパスに沿って配置・スクロールさせたい場合。視覚的にユニークなUIが必要な場合。独自の視覚効果。高度なカスタマイズ性。学習コストが高い。一般的な用途には不向き。
手動スクロール非常に特殊なスクロール動作をゼロから実装したい場合。非推奨究極の柔軟性。非常に複雑で実装コストが高い。