mous16
mous16

Reputation: 121

QML: prevent ListView from returning at beginning on model change

When I try to display data through a ListView, everytime the model is updated the view returns to the beginning.
Is there a way to make ListView handle the model updates without changing the visibleArea of the inner Flickable (or changing it in a reasonable way)?
Ideally, the same area should remain visible.

Here a minimal example where you can see that behaviour: if you scroll down in flickable (on the left) and in listView (on the right), when the timer is triggered the first one will stay where you left it, the second one will go back to beginning.
QML online live example

import QtQuick 2.15

Rectangle {
    id:root

    property int model: 10
    property int delegateHeight: height/10*0.8
    property int delegateSpacing: height/10*0.2

    color: "#afafaf"
    anchors.fill: parent

    Timer {
        interval: 5000
        repeat: true
        running: true

        onTriggered: root.model++
    }

    Flickable {
        id: flickable

        x: 0
        y: 0
        width: parent.width/2
        height: parent.height
        clip: true
        contentHeight: root.delegateHeight*repeater.count

        Repeater {
            id: repeater

            model: root.model

            delegate: Rectangle {
                x: 0
                y: (root.delegateHeight+root.delegateSpacing)*index
                width: flickable.width
                height: root.delegateHeight
                color: "#abcdef"

                Text {
                    text: index+1
                }
            }
        }
    }

    ListView {
        id: listView

        x: parent.width/2
        y: 0
        width: parent.width/2
        height: parent.height
        clip: true
        model: root.model
        spacing: root.delegateSpacing

        delegate: Rectangle {
            width: ListView.view.width
            height: root.delegateHeight
            color: "#fedcba"

            Text {
                text: index+1
            }
        }
    }
}

EDIT: I found a sort of workaround, but I don't think it's the right way to go.
Adding to listView something like this, will keep the visibleArea position on model increasing, but will probably have some drawback when decreasing the counter.

        property real lastY: 0

        onContentYChanged: {
            if(!moving){
                listView.contentY = listView.lastY+listView.originY
            }
            listView.lastY = listView.contentY-listView.originY
        }

Upvotes: 4

Views: 1107

Answers (1)

mous16
mous16

Reputation: 121

I've adapted the workaround presented to handle also the shrink case.

Basically, every time the user moves the content, the component stores the new position.
When the content moves by itself, like for a change in the model, the component restores the last position setted by the user, avoiding placing it outside the view.

To achieve this behavior something like this should be a added to ListView:

        property real lastY: 0

        onContentYChanged: {
            if (!moving) {
                listView.contentY = Math.max(0, Math.min(listView.lastY, listView.contentHeight-listView.height))+listView.originY
            }
            listView.lastY = listView.contentY-listView.originY
        }

Working reviewed example here.

There is probably still room for some improvement, but for the moment this mechanism satisfies my needs.

Upvotes: 3

Related Questions