Diego
Diego

Reputation: 357

How to determine the scroll position of a ListView in a ScrollView

I have a QML application with a ScrollView and a ListView inside. The items from the ListView have variable heights.

I need to know when the scroll bar is moved, in fact, when it is at the bottom and when it is not. My target is to keep the scroll bar at the bottom (positionViewAtEnd()) when I add an item to the ListView only if the scroll is at the bottom. If not at the bottom, positionViewAtEnd() will not be used.

I have tried "playing" with the height, contentHeight, and contentY. Sometimes it works (when scroll is at bottom: contentHeight == contentY + height), but other times, contentY value changes to negative values, and my code fails.

How do I achieve this?


I have tried with atYEnd in several property changes, to detect if the scroll bar is at the bottom or not.

It seems to work, and then I use that in onCountChanged to put (or not) the scroll bar at the bottom.

That works once all the ListView height is full of messages, but not in one case: when the incoming message is the one that fills the ListView height (1st time that contentY is not 0).

I have simplified my code to test (including the delegate):

FocusScope {
    clip: true

    id: focusScopeView

    width: parent.width; height: parent.height

    ScrollView {

        width: parent.width; height: parent.height

        ListView {
            id: listTexts

            width: parent.width; height: parent.height

            property bool bScrolled: false

            model: textsModel
            delegate: Text { text: "Contact:\t" + eventText }

            onCountChanged: {

                if (!bScrolled)
                     positionViewAtEnd();
            }

            onContentYChanged: {
                bScrolled = !atYEnd;

                if (atYEnd)
                    positionViewAtEnd()
            }

            onContentHeightChanged: {
                if (!bScrolled)
                     positionViewAtEnd();
            }
        }
    }
}

Upvotes: 0

Views: 4290

Answers (4)

Stephen Quan
Stephen Quan

Reputation: 25871

If you create the following test:

ListView {
    readonly property real tst: contentHeight - height - contentY
}

and you scroll/flick very fast you will note that the value decreases to 0 and briefly goes negative before bouncing back to 0. Therefore, if you want this overshoot portion, the following inequality would cover it

ListView {
    readonly property bool atYEnd: contentHeight - height - contentY <= 0
}

Upvotes: 0

Alex Grex
Alex Grex

Reputation: 11

ListView { 
    ScrollBar.vertical: ScrollBar {
        id: taskScroll
            }
        property bool atBottom: (taskScroll.position + taskScroll.size) == 1
    }

Upvotes: 1

jpnurmi
jpnurmi

Reputation: 5836

The reason why the calculation fails is that ListView has had to adjust its origin due to delegates of variable height. Including originY to the calculation would help to get the correct value, but there is a much more convenient way to check if any Flickable is atYEnd.


This is a bit of a trick, but another way to do the auto-scrolling (or rather, to avoid the need of scrolling) is to use ListView.BottomToTop as a vertical layout direction. Then, instead of appending to the end, insert new messages at the beginning of the model. This way the content stays aligned to the bottom when new messages arrive, without doing any manual positioning. There's a little catch, though. The content is also aligned to the bottom when there is less content than fits in the view.

Upvotes: 1

Vector Li
Vector Li

Reputation: 11

ListView {
    onContentYChanged: {
        if (contentY === contentHeight - height) {
            console.log("scrolled to bottom");
        }
    }
}
    
ScrollView {
    flickableItem.onContentYChanged: {
        if (flickableItem.contentY === flickableItem.contentHeight - viewport.height) {
            console.log("scrolled to bottom");
        }
    }
}

Upvotes: 1

Related Questions