Qt Flickable boundsBehaviorとは?端の動作を徹底解説【Qt Quick】

2025-05-27

具体的には、以下の4つの値を設定できます。

  • Flickable.WrapAround
    この設定は、水平方向のスクロールに対してのみ有効です。コンテンツが右端に達すると、左端から再び現れ、逆もまた同様です。つまり、コンテンツがループしているような振る舞いをします。無限スクロールのような効果を実現できます。垂直方向のスクロールに対してこの値を設定しても効果はありません。

  • Flickable.OvershootBounds
    この設定も、コンテンツが端に達した後もスクロールを続けることができますが、「DragOverBounds」よりもさらに大きく端を超えてスクロールできます。そして、指やマウスボタンを離すと、通常は勢いをつけて元の端の位置に戻ります。より明確な「バウンスバック」効果を得たい場合に適しています。

  • Flickable.DragOverBounds
    この設定では、コンテンツが端に達した後も、さらにドラッグ操作を続けると、少しだけ端を超えてスクロールできます。しかし、指やマウスボタンを離すと、コンテンツは元の端の位置に戻ります。これは、端に到達したことをユーザーに視覚的に示す効果があります。いわゆる「バウンスバック」効果に近いですが、より直接的なドラッグ操作に追従する動きです。

  • Flickable.StopAtBounds
    これはデフォルトの振る舞いです。コンテンツが端に達すると、スクロールは完全に停止します。端を超えてスクロールしようとしても、コンテンツはそれ以上動きません。

これらの値を「Flickable.boundsBehavior」プロパティに割り当てることで、アプリケーションのUIにおけるスクロールの体験を細かく調整できます。例えば、リストの最後に到達したことを明確に示したい場合は「DragOverBounds」や「OvershootBounds」を、水平方向に無限に続くコンテンツを表示したい場合は「WrapAround」を使用するといった具合です。



期待したバウンド効果が得られない

  • トラブルシューティング
    • FlickableのcontentWidthcontentHeightが、内包するアイテムのサイズに合わせて適切に設定されているか確認してください。Layoutを使用している場合は、Layoutが正しくアイテムのサイズを管理しているか確認します。
    • Flickableのサイズが、スクロールさせたいコンテンツよりも小さいことを確認してください。
    • 親要素にclip: trueが設定されていないか確認し、必要に応じてclip: falseに変更するか、構造を見直してください。
  • 原因
    • コンテンツのサイズがFlickableのビューポートよりも小さい
      Flickableのコンテンツ領域が、実際に表示されている範囲よりも小さい場合、そもそも端に到達しないため、boundsBehaviorの効果が発揮されません。
    • contentWidthやcontentHeightが正しく設定されていない
      Flickable内でスクロールさせるコンテンツの幅や高さが、明示的にまたはレイアウトによって正しく設定されていないと、スクロール可能な範囲が意図したものにならず、端の振る舞いも期待通りになりません。
    • 親要素のクリッピング
      Flickableの親要素でclip: trueが設定されている場合、オーバーシュートしたコンテンツが親要素の境界でクリップされ、バウンド効果が見えないことがあります。

WrapAroundが期待通りに動作しない

  • トラブルシューティング
    • FlickableのorientationQt.Horizontalに設定されていることを確認してください。
    • contentWidthがFlickableのwidthよりも大きいことを確認してください。
    • 他のスクロール関連のコードやアニメーション処理を見直し、WrapAroundと競合していないか確認してください。
  • 原因
    • 水平方向のスクロールでない
      WrapAroundは水平方向のスクロールにのみ有効です。垂直方向のFlickableに設定しても効果はありません。
    • contentWidthがFlickableの幅よりも小さい
      コンテンツの幅がFlickableの表示幅よりも小さい場合、端が存在しないため、ラッピング効果は起こりません。
    • 他のプロパティとの干渉
      他のカスタムなスクロール制御やアニメーションが、WrapAroundの動作を妨げている可能性があります。

スムーズなスクロールやバウンド効果が得られない

  • トラブルシューティング
    • Flickable内のアイテムをできるだけ軽量化し、不要な処理を減らすことを検討してください。
    • コンテンツの更新頻度を最適化し、必要な時だけ更新するようにしてください。
    • カスタムアニメーションを使用している場合は、DurationやEasing Curveなどの設定を見直し、スムーズな動きになるように調整してください。
  • 原因
    • コンテンツが複雑すぎる
      Flickable内のコンテンツが非常に複雑で描画に時間がかかる場合、スクロールやバウンドのアニメーションがカクついたり、遅延したりすることがあります。
    • 高頻度の更新
      Flickableのコンテンツが頻繁に更新される場合、スクロール処理に負荷がかかり、スムーズな動作が妨げられることがあります。
    • 不適切なアニメーション設定
      カスタムアニメーションを使用している場合、その設定が適切でないと、期待したスムーズな効果が得られないことがあります。
  • トラブルシューティング
    • Flickable内部のインタラクティブな要素のmouseChildrenプロパティをfalseに設定するなどして、イベントの伝播を適切に制御してください。
    • Flickableが常に意図した通りにマウス/タッチイベントを受け取れるように、UIの構造を見直してください。
  • 原因
    • マウス/タッチイベントの競合
      Flickableの内部にあるインタラクティブな要素(Buttonなど)が、マウスやタッチイベントを横取りし、Flickableへのイベント伝播を妨げている可能性があります。
    • フォーカスの問題
      Flickableがフォーカスを失うことで、スクロール操作が中断されることがあります。


Flickable.StopAtBounds (デフォルト)

この例では、コンテンツがFlickableの端に達すると、それ以上スクロールできなくなります。

import QtQuick 2.0

Rectangle {
    width: 200
    height: 300

    Flickable {
        id: flickable
        width: parent.width
        height: parent.height
        contentWidth: 400 // Flickableの幅より大きい
        contentHeight: 500 // Flickableの高さより大きい
        boundsBehavior: Flickable.StopAtBounds

        Rectangle {
            width: flickable.contentWidth
            height: flickable.contentHeight
            color: "lightgray"

            Text {
                text: "コンテンツ (StopAtBounds)"
                anchors.centerIn: parent
            }
        }
    }
}

このコードを実行すると、グレーのRectangleがFlickableの範囲よりも大きく表示されます。マウスやタッチでスクロールできますが、端に達するとピタリと止まります。

Flickable.DragOverBounds

この例では、コンテンツが端に達した後も少しだけドラッグできますが、指やマウスボタンを離すと元の端の位置に戻ります。

import QtQuick 2.0

Rectangle {
    width: 200
    height: 300

    Flickable {
        id: flickable
        width: parent.width
        height: parent.height
        contentWidth: 400
        contentHeight: 500
        boundsBehavior: Flickable.DragOverBounds

        Rectangle {
            width: flickable.contentWidth
            height: flickable.contentHeight
            color: "lightblue"

            Text {
                text: "コンテンツ (DragOverBounds)"
                anchors.centerIn: parent
            }
        }
    }
}

同様にスクロールできますが、端までドラッグした後も少しだけ引っ張ることができ、離すと元の位置に戻るのが確認できます。

Flickable.OvershootBounds

この例では、端に達した後もさらに大きくスクロールでき、指やマウスボタンを離すとバウンドして元の端の位置に戻ります。

import QtQuick 2.0

Rectangle {
    width: 200
    height: 300

    Flickable {
        id: flickable
        width: parent.width
        height: parent.height
        contentWidth: 400
        contentHeight: 500
        boundsBehavior: Flickable.OvershootBounds

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

            Text {
                text: "コンテンツ (OvershootBounds)"
                anchors.centerIn: parent
            }
        }
    }
}

スクロールして端を超えると、DragOverBoundsよりも大きくコンテンツが動き、指を離すと跳ね返るようなアニメーションが見られます。

Flickable.WrapAround (水平方向)

この例では、水平方向にスクロールするコンテンツが右端に達すると左端から現れ、逆も同様です。垂直方向には通常の停止動作となります。

import QtQuick 2.0

Rectangle {
    width: 200
    height: 150

    Flickable {
        id: flickable
        width: parent.width
        height: parent.height
        contentWidth: 600 // Flickableの幅の3倍
        contentHeight: 100
        boundsBehavior: Flickable.WrapAround
        orientation: Qt.Horizontal // 水平スクロールを明示

        Row {
            width: flickable.contentWidth
            height: flickable.contentHeight
            spacing: 10

            Rectangle { width: 180; height: 100; color: "yellow"; Text { text: "Item 1"; anchors.centerIn: parent } }
            Rectangle { width: 180; height: 100; color: "orange"; Text { text: "Item 2"; anchors.centerIn: parent } }
            Rectangle { width: 180; height: 100; color: "red"; Text { text: "Item 3"; anchors.centerIn: parent } }
        }
    }
}

このコードでは、3つの色の付いたRectangleが横に並んでいます。水平方向にスクロールすると、右端のRectangleが見えなくなった後、左端から再び現れるのが確認できます。垂直方向には通常のスクロール停止動作となります。



シグナルとアニメーションによるカスタム実装

Flickable は、スクロール位置が変化したときや、端に達したときに様々なシグナルを発行します。これらのシグナルとアニメーションを組み合わせることで、boundsBehavior と同様、あるいはそれ以上に柔軟な端の振る舞いを実装できます。

  • contentXChanged / contentYChanged
    コンテンツのXまたはY方向のスクロール位置が変化したときに発行されるシグナルです。
  • flickStarted / flickEnded
    フリック操作が開始または終了したときに発行されるシグナルです。
  • atXEndChanged / atYEndChanged
    スクロール位置がそれぞれの軸の末尾に達したときに発行されるシグナルです。
  • atXBeginningChanged / atYBeginningChanged
    スクロール位置がそれぞれの軸の先頭に達したときに発行されるシグナルです。

これらのシグナルをトリガーにして、PropertyAnimation などを利用してコンテンツの位置を調整することで、バウンスバック効果や、端での減速、あるいは全く異なるカスタムな振る舞いを実現できます。

例 (簡単なバウンスバック)

import QtQuick 2.0
import Qt.TweenAnimation 1.0

Rectangle {
    width: 200
    height: 300

    Flickable {
        id: flickable
        width: parent.width
        height: parent.height
        contentWidth: 400
        contentHeight: 500

        Rectangle {
            width: flickable.contentWidth
            height: flickable.contentHeight
            color: "skyblue"

            Text {
                text: "カスタムバウンス"
                anchors.centerIn: parent
            }
        }

        // 水平方向の端に達した際の処理
        onAtXBeginningChanged: if (atXBeginning) animateBackX(0)
        onAtXEndChanged: if (atXEnd) animateBackX(contentWidth - width)

        // 垂直方向の端に達した際の処理
        onAtYBeginningChanged: if (atYBeginning) animateBackY(0)
        onAtYEndChanged: if (atYEnd) animateBackY(contentHeight - height)

        function animateBackX(targetX) {
            PropertyAnimation {
                target: flickable
                property: "contentX"
                to: targetX
                duration: 300
                easing.type: Easing.OutCubic
                running: true
            }
        }

        function animateBackY(targetY) {
            PropertyAnimation {
                target: flickable
                property: "contentY"
                to: targetY
                duration: 300
                easing.type: Easing.OutCubic
                running: true
            }
        }
    }
}

この例では、atXBeginningChangedatXEndChangedatYBeginningChangedatYEndChanged シグナルを監視し、端に達したときに PropertyAnimation を開始して、コンテンツを元の端の位置に戻すことで、簡単なバウンスバック効果を実現しています。

QAbstractItemView のサブクラス化 (Qt Widgets / Qt Quick Controls 1)

もし ListViewGridView のようなアイテムビューを使用している場合、QAbstractItemView を直接サブクラス化することで、スクロールの振る舞いをより深く制御できます。これにはC++の知識が必要になりますが、非常に高度なカスタマイズが可能です。例えば、独自のスクロール物理演算を実装したり、端での特殊なインタラクションを追加したりできます。

ScrollView と ScrollIndicator の組み合わせ (Qt Quick Controls 2)

Qt Quick Controls 2 の ScrollView は、より高度なスクロール機能を提供し、boundsBehavior に近い機能も内包しています。また、ScrollIndicator を組み合わせて使用することで、スクロールの視覚的なフィードバックを細かく制御できます。ScrollViewpolicy プロパティや、内部の viewport の動作をカスタマイズすることで、端の振る舞いを間接的に制御できます。

マウス/タッチイベントの直接処理

より低レベルなアプローチとして、MouseArea などを利用してマウスやタッチイベントを直接処理し、スクロールのロジックを完全に自前で実装することも可能です。この方法は非常に柔軟性が高い反面、スクロールの物理演算や慣性、端の処理などを全て自分で実装する必要があるため、複雑になります。

QtGraphicalEffects を利用した視覚効果

直接的なスクロールの振る舞いを変更するわけではありませんが、QtGraphicalEffects モジュールに含まれるエフェクト(例えば GlowDropShadow)を、スクロールの端に達した際に適用することで、視覚的なフィードバックを提供し、boundsBehavior の効果を補完することができます。