3

I want to have a single (vertical) ListView with (horizontal) ListView delegates.

The horizontal delegates should scroll synchronously. To do so, I put a Flickable on top of the ListViews and bind the contentX of the horizontal ListView to the contentX of the Flickable (and the same for the contentY of the vertical ListView) (Note: Here a different approach was described for the synchronous ListView scrolling but this seems to have performance issues on mobile devices)

The code below kind of works but still has the following issues

  • I don't get the onClicked in the Rectangle (I do get it when I remove the top Flickable)
  • I want either horizontal flicking or vertical flicking but not both at the same time. I can restrict the flicking of the top Flickable by setting flickableDirection: Flickable.HorizontalFlick but then I can't flick vertically anymore (I was hoping that the Flickable would pass on unused mouse events to the vertical ListView but this doesn't seem to happen)

Suggestions on how to fix these issues?

Any help appreciated!

import QtQuick 2.0

Item {
    id: main
    visible: true
    width: 600
    height: 600

    ListView {
        id: verticalList
        width: parent.width;
        height: parent.height;
        contentY : flickable.contentY
        anchors.fill: parent
        spacing: 10
        orientation: ListView.Vertical
        model: 100
        delegate:
            ListView {
                id: horizontalList
                width: parent.width;
                height: 100;
                contentX : flickable.contentX
                spacing: 10
                orientation: ListView.Horizontal
                model: 20
                property var verticalIndex : index
                delegate:
                    Rectangle
                    {
                        property var colors : ['red', 'green', 'blue']
                        property var widths : [100, 200, 300]
                        height: 100
                        width: widths[(verticalIndex + model.index) % widths.length]
                        color: colors[model.index % colors.length]

                        MouseArea {
                            anchors.fill: parent
                            onClicked: {
                                console.log("Rectangle.onClicked")
                            }

                        }
                    }

           }

    }

    //on top a Flickable
    Flickable {
       id: flickable
       height: parent.height
       width: parent.width
       contentHeight: 100*100 //nrOfRows * rowHeight
       contentWidth: 20*300 //nrOfEvent * max/averageEventWidth
     }


}
Community
  • 1
  • 1
Marc Van Daele
  • 2,856
  • 1
  • 26
  • 52

3 Answers3

0

I'm not giving you a perfect solution, but it's working. When you are using Flickable on the top of the ListView, you are not able to interact with it. So, I've used Flickable bellow the ListView and bounded the contentX of Flickable and ListView, but this is causing a loop and I'm getting the following output, but we're getting the desired behavior.

QML Binding: Binding loop detected for property "value" 

EDIT

So, instead of using ListView for vertical list, I just used a Repeater and Column and used property binding. It's working well now.

Following is the updated version.

import QtQuick 2.0

Item {
    id: main
    visible: true
    width: 600
    height: 600
    property bool virticalFlick: false  //To get either vertical or horizontal flicking

    Flickable {
        anchors.fill: parent
        contentWidth: contentItem.childrenRect.width
        contentHeight: contentItem.childrenRect.height
        flickableDirection: Flickable.VerticalFlick
        interactive: (virticalFlick === true)?true:false
        Column {
            id: column
            spacing: 10
            Repeater {
                id: repeater
                model: 20
                ListView {
                    id: horizontalList
                    width: 600;
                    height: 100;
                    clip: true
                    interactive: (virticalFlick === true)?false:true
                    spacing: 10
                    orientation: ListView.Horizontal
                    model: 20
                    property var verticalIndex : index
                    onMovingChanged: {
                        if(moving == true) {
                            for(var i=0; i<repeater.count ; i++) {
                                /* If the property is later assigned a static value from a JavaScript statement,
                            this will remove the binding.
                            However if the intention is to create a new binding then the property
                            must be assigned a Qt.binding() value instead. This is done by passing a function to
                            Qt.binding() that returns the desired result */
                                if (i !== index)
                                    repeater.itemAt(i).contentX = Qt.binding(function() { return contentX });
                            }
                        }
                        else {
                            for(var i=0; i<repeater.count ; i++) {
                                if (i !== index)
                                    repeater.itemAt(i).contentX = contentX; // This will remove binding
                            }
                        }

                    }

                    delegate: Rectangle {
                        property var colors : ['red', 'green', 'blue']
                        property var widths : [100, 200, 300]
                        height: 100
                        width: widths[(ListView.view.verticalIndex + model.index) % widths.length]
                        color: colors[model.index % colors.length]

                        MouseArea {
                            anchors.fill: parent
                            onClicked: {
                                console.log("Rectangle.onClicked")
                            }

                        }
                    }
                }
            }
        }
    }
}
Vedanshu
  • 2,230
  • 23
  • 33
  • Thanks for your input. The horizontal flicking is indeed not smooth (stops immediately) most likely due to the binding loop. But I might experiment a bit more based on this version. – Marc Van Daele Nov 13 '15 at 11:11
  • Thanks again! The flicking is indeed smooth now. However, I really need a ListView iso a Repeater (since a Repeater uses more memory and paints outside the visible window as can be seen when setting QSG_VISUALIZE=overdraw). Additionally, I need both vertical and horizontal scrolling (but no diagonal scrolling which I had in my initial attempt). My answer meets these requirements but I still have to compare both options wrt performance. – Marc Van Daele Nov 15 '15 at 10:20
0

The following does work, however the initial attempt seemed more elegant.

I still need to compare the performance (fps) when flicking, especially on a mobile device. I also get "Binding loop" warnings but I think they are false positives.

import QtQuick 2.0

Item {
id: main
visible: true
width: 600
height: 600

ListView {
    id: verticalList
    width: parent.width;
    height: parent.height;
    anchors.fill: parent
    spacing: 10
    cacheBuffer: 500 // in pixels
    orientation: ListView.Vertical
    model: 100
    property var activeIndex : 0
    property var lastContentX : 0
    delegate:
        ListView {
            id: horizontalList
            width: parent.width;
            height: 100;
            spacing: 10
            cacheBuffer: 500 // in pixels
            orientation: ListView.Horizontal
            model: 20
            property var verticalIndex : index
            delegate:
                Rectangle
                {
                    property var colors : ['red', 'green', 'blue']
                    color: colors[model.index % colors.length]

                    height: 100
                    property var widths : [100, 200, 300]
                    width: widths[(verticalIndex + model.index ) % widths.length]

                    MouseArea {
                        z:10
                        anchors.fill: parent
                        onClicked: {
                            console.log("Rectangle.onClicked")
                        }
                        onPressed: {
                            console.log("Rectangle.onPressed")
                        }
                        onReleased: {
                            console.log("Rectangle.onReleased")
                        }

                    }
                }

        onContentXChanged: {
            if(model.index === verticalList.activeIndex)
            {
                verticalList.lastContentX = contentX
            }
        }
        onMovementStarted: {
            verticalList.activeIndex = model.index
            unbind();
        }
        onMovementEnded: {
            bind();
        }

        Component.onCompleted: {
            bind();
        }

        function bind()
        {
            contentX = Qt.binding(function() { return verticalList.lastContentX })
        }
        function unbind()
        {
            contentX = contentX ;
        }
       }
  }
}
Marc Van Daele
  • 2,856
  • 1
  • 26
  • 52
0

The following modifications were needed to my initial attempt

  • limit the Flickable to flickableDirection : Flickable.HorizontalFlick and remove the contentY : flickable.contentY on the verticalList

  • by doing so, there is no vertical scrolling anymore. This can be fixed by moving the Flickable inside the ListView

  • onClicked events are received by adding the following MouseArea to the Flickable

eg.

MouseArea {
   anchors.fill: parent
   //see http://stackoverflow.com/questions/29236762/mousearea-inside-flickable-is-preventing-it-from-flicking
   onReleased: {
      if (!propagateComposedEvents) {
         propagateComposedEvents = true
      }
   }
}
Marc Van Daele
  • 2,856
  • 1
  • 26
  • 52