Reputation: 357
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
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
Reputation: 11
ListView {
ScrollBar.vertical: ScrollBar {
id: taskScroll
}
property bool atBottom: (taskScroll.position + taskScroll.size) == 1
}
Upvotes: 1
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
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