ListView shows items from the bottom when scrolled beyond the top

Apologies for the confusingly named title, the problem is a bit weird to explain. I have created a ListView element and set up the layout like this:

<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" editor-extension-mode="False">
    <Style src="project://database/Assets/UI/main-style.uss?fileID=7433441132597879392&guid=93dd405dd4c015548865f2e6745f4b3e&type=3#main-style" />
    <ui:VisualElement name="bg-body" style="flex-grow: 1; position: absolute; width: 100%; height: 100%;">
        <ui:VisualElement name="bg-container" style="flex-grow: 1; background-color: rgb(231, 212, 156);" />
        <ui:VisualElement name="bg-nav" style="background-color: rgb(53, 57, 63); height: 60px; flex-direction: row; align-items: stretch; justify-content: space-around;" />
    </ui:VisualElement>
    <SafeAreaManager style="position: relative; flex-basis: auto; width: 100%; height: 100%; max-height: initial; min-height: initial;">
        <ui:VisualElement name="body" style="flex-grow: 1;">
            <ui:VisualElement name="bg-container" style="flex-grow: 0; background-color: rgb(231, 212, 156); max-height: initial; min-height: initial; border-left-color: rgb(74, 255, 54); border-right-color: rgb(74, 255, 54); border-top-color: rgb(74, 255, 54); border-bottom-color: rgb(74, 255, 54); border-left-width: 0; border-right-width: 0; border-top-width: 0; border-bottom-width: 0; height: 100%;">
                <ui:ListView focusable="true" name="list-view" selection-type="None" show-add-remove-footer="false" show-foldout-header="false" class="list-view" style="flex-grow: 1; max-height: initial; min-height: initial;" />
            </ui:VisualElement>
            <ui:VisualElement name="bg-nav" style="background-color: rgb(53, 57, 63); height: 60px; flex-direction: row; align-items: stretch; justify-content: space-around; max-height: initial; min-height: initial; border-left-color: rgb(255, 0, 0); border-right-color: rgb(255, 0, 0); border-top-color: rgb(255, 0, 0); border-bottom-color: rgb(255, 0, 0); border-left-width: 0; border-right-width: 0; border-top-width: 0; border-bottom-width: 0; flex-grow: 0;">
                <ui:Button tabindex="-1" display-tooltip-when-elided="true" name="add-line-button" style="background-color: rgba(88, 88, 88, 0); border-left-color: rgb(255, 255, 255); border-right-color: rgb(255, 255, 255); border-top-color: rgb(255, 255, 255); border-bottom-color: rgb(255, 255, 255); border-left-width: 1px; border-right-width: 1px; border-top-width: 1px; border-bottom-width: 1px; border-top-left-radius: 100px; border-bottom-left-radius: 100px; border-top-right-radius: 100px; border-bottom-right-radius: 100px; margin-left: 8px; margin-right: 8px; margin-top: 8px; margin-bottom: 8px; background-image: url('project://database/Assets/UI/battle-gear.png?fileID=2800000&guid=91545d92cd79282458086d5b615ce50c&type=3#battle-gear'); max-height: none; min-height: auto; width: 12%; -unity-slice-left: 4; -unity-slice-top: 4; -unity-slice-right: 4; -unity-slice-bottom: 4; -unity-background-scale-mode: stretch-to-fill; padding-left: 0; padding-right: 0; padding-top: 0; padding-bottom: 0;" />
                <ui:Button tabindex="-1" text="Button" display-tooltip-when-elided="true" />
                <ui:Button tabindex="-1" text="Button" display-tooltip-when-elided="true" />
            </ui:VisualElement>
        </ui:VisualElement>
    </SafeAreaManager>
</ui:UXML>

This is my styling, it’s just hiding the scroll bars really:

Scroller {
    display: none;
    opacity: 0;
    overflow: hidden;
    visibility: hidden;
}

And this is my controller code. It just sets up 20 items to display and wires up one of the buttons to add new items.

using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UIElements;

public class UIController : MonoBehaviour
{    void Start()
    {
        var root = this.GetComponent<UIDocument>().rootVisualElement;

        const int itemCount = 20;
        var items = new List<string>(itemCount);
        for (int i = 1; i <= itemCount; i++)
            items.Add(i.ToString());


        Func<VisualElement> makeItem = () => new Label();

        Action<VisualElement, int> bindItem = (e, i) => (e as Label).text = items[i];

        var listView = root.Q<ListView>("list-view");
        listView.makeItem = makeItem;
        listView.bindItem = bindItem;
        listView.itemsSource = items;
        listView.selectionType = SelectionType.Multiple;

        root.Q<ListView>("list-view").selectionType = SelectionType.None;
        root.Q<ListView>("list-view").Q<ScrollView>().touchScrollBehavior = ScrollView.TouchScrollBehavior.Elastic;
        root.Q<ListView>("list-view").Q<ScrollView>().scrollDecelerationRate = 0.25f;
        root.Q<ListView>("list-view").Q<ScrollView>().elasticity = 0.01f;

        root.Q<Button>("add-line-button").clicked += () =>
        {
            items.Add((items.Count + 1).ToString());
            listView.RefreshItems();
            listView.ScrollToItemById(items.Count - 1);
        };
    }
}

The problem now comes from setting elasticity on the scroll view. Like commonly used on mobile phones, I am now able to scroll “beyond” the top (like lots of apps do to allow you to refresh the displayed content). However, instead of just showing empty space, the list view takes the items from the bottom (i.e. the numbers 20, 19, 18, etc…) and appends them on the top again.

gloomyuntimelygyrfalcon

I wonder if this is a side effect of using the ListView instead of ScrollView? I am planning on displaying dynamically loaded content with different heights as well, I read that ListView was better suited for this scenario, which is why I went with it.

Anyway, any help on this would be appreciated :slight_smile:

Hi,
Indeed, the virtualization of the list view expects items to be drawn whenever they can be visible, which would explain why it behaves like that in elastic mode… you can report this as a bug using Help → Report a bug… and we’ll address the issue
Thanks!

Thanks, I’ll do that. In the mean time I will attempt to switch over to a simple ScrollView for this.