Scroll two or more List views in QML

2.3k views Asked by At

I need to scroll two or more list view at once using a single scrollBar. Initially, i used Column inside a Flickable but scroll was not happening as expected. Later, I used ListView and even that was not scrolling correctly.

So how to scroll a listview/layout content item with a scroll bar? Should I use ScrollView or Flickable or something else?

Example of My UI

2

There are 2 answers

4
GrecKo On BEST ANSWER

You could just use a Flickable with your Columns. I don't know how your Columns are laid out horizontally but if they are inside a Row it's pretty straightforward:

import QtQuick 2.7
import QtQuick.Controls 2.0

ApplicationWindow {
    visible: true
    width: 640
    height: 480
    title: qsTr("Multi Column")

    Flickable {
        anchors.fill: parent
        contentWidth: row.implicitWidth
        contentHeight: row.implicitHeight
        Row {
            id: row
            Column {
                spacing: 5
                Repeater {
                    model: 20
                    delegate: Rectangle {
                        width: 50
                        height: 50
                        color: "red"
                        Text {
                            anchors.centerIn: parent
                            text: index
                        }
                    }
                }
            }
            Column {
                spacing: 5
                Repeater {
                    model: 30
                    delegate: Rectangle {
                        width: 50
                        height: 50
                        color: "cyan"
                        Text {
                            anchors.centerIn: parent
                            text: index
                        }
                    }
                }
            }
        }
        ScrollBar.vertical: ScrollBar { }
    }
}

Even if they are not in a Row you could do :
contentHeight: Math.max(column1.height, column2.height, ...)

Demonstration :
Multi columns scroll

7
dtech On

The stock scrollbar will only hook to a single scrollable item. However, it is trivial to make a custom scroller and hook multiple views to it:

  Row {
    Flickable {
      width: 50
      height: main.height
      contentHeight: contentItem.childrenRect.height
      interactive: false
      contentY: (contentHeight - height) * scroller.position
      Column {
        spacing: 5
        Repeater {
          model: 20
          delegate: Rectangle {
            width: 50
            height: 50
            color: "red"
            Text {
              anchors.centerIn: parent
              text: index
            }
          }
        }
      }
    }
    Flickable {
      width: 50
      height: main.height
      contentHeight: contentItem.childrenRect.height
      interactive: false
      contentY: (contentHeight - height) * scroller.position
      Column {
        spacing: 5
        Repeater {
          model: 30
          delegate: Rectangle {
            width: 50
            height: 50
            color: "cyan"
            Text {
              anchors.centerIn: parent
              text: index
            }
          }
        }
      }
    }
    Rectangle {
      id: scroller
      width: 50
      height: 50
      color: "grey"
      property real position: y / (main.height - 50)
      MouseArea {
        anchors.fill: parent
        drag.target: parent
        drag.minimumY: 0
        drag.maximumY: main.height - 50
        drag.axis: Drag.YAxis
      }
    }
  }

Note that it will work adequately even if the the views are of different content height, scrolling each view relative to the scroller position:

enter image description here

Realizing the question was not put that well, just in case someone wants to actually scroll multiple views at the same time comes around, I will nonetheless share another interesting approach similar to a jog wheel, something that can go indefinitely in every direction rather than having a limited range like a scrollbar. This solution will scroll the two views in sync until they hit the extent of their ranges. Unlike GrecKo's answer, this never leaves you with an "empty view" when the view size is different:

enter image description here

  Row {
    Flickable {
      id: f1
      width: 50
      height: main.height
      contentHeight: contentItem.childrenRect.height
      interactive: false
      Connections {
        target: jogger
        onScroll: f1.contentY = Math.max(0, Math.min(f1.contentHeight - f1.height, f1.contentY + p))
      }
      Column {
        spacing: 5
        Repeater {
          model: 20
          delegate: Rectangle {
            width: 50
            height: 50
            color: "red"
            Text {
              anchors.centerIn: parent
              text: index
            }
          }
        }
      }
    }
    Flickable {
      id: f2
      width: 50
      height: main.height
      contentHeight: contentItem.childrenRect.height
      interactive: false
      Connections {
        target: jogger
        onScroll: f2.contentY = Math.max(0, Math.min(f2.contentHeight - f2.height, f2.contentY + p))
      }
      Column {
        spacing: 5
        Repeater {
          model: 30
          delegate: Rectangle {
            width: 50
            height: 50
            color: "cyan"
            Text {
              anchors.centerIn: parent
              text: index
            }
          }
        }
      }
    }
    MouseArea {
      id: jogger
      width: 50
      height: main.height
      drag.target: knob
      drag.minimumY: 0
      drag.maximumY: main.height - 50
      drag.axis: Drag.YAxis
      signal scroll(real p)
      property real dy: 0
      onPressed: dy = mouseY
      onPositionChanged: {
        scroll(dy - mouseY)
        dy = mouseY
      }
      onScroll: console.log(p)
      Rectangle {
        anchors.fill: parent
        color: "lightgrey"
      }
      Rectangle {
        id: knob
        visible: parent.pressed
        width: 50
        height: 50
        color: "grey"
        y: Math.max(0, Math.min(parent.mouseY - 25, parent.height - height))
      }
    }
  }

Another advantage the "jog" approach has it is it not relative but absolute. That means if your view is huge, if you use a scroller even a single pixel may result in a big shift in content, whereas the jog, working in absolute mode, will always scroll the same amount of pixels regardless the content size, which is handy where precision is required.