Scrolled List - the code

Scrolled List - the code

Emilia Szymańska bio photo By Emilia Szymańska Comment

Hi! So, in the last post we learned how to create a scrollable list component, but we could only „hardcode” its elements. Today I’m gonna show you my proposal of a scrolled list implementation, that can be supplied by the elements list from the script. It requires defining a data type for all of the elements – for example user/team/level.

The element script

First of all, we will create an abstract class for the elements – ScrolledListElement. It will be a generic class, which means that we have to provide an additional type when inheriting from it. This type will be naturally our data type. This class will have only two fields:

• DataCache – this will store our actual data,

• _transform – this will hold the transform cache for performance reasons.

All of the methods will be virtual. In the Awake method we will just cache the transform. The SetData method allows to attach this element to its new parent and also store the data. The whole class looks like this:

public abstract class ScrolledListElement<T> : MonoBehaviour
{
    public T            DataCache { get; protected set; }
    private Transform   _transform;

    protected virtual void Awake()
    {
        _transform = transform;
    }

    public virtual void SetData(T data, Transform parentTransform)
    {
        _transform.SetParent(parentTransform, false);
        UpdateData(data);
    }

    public virtual void UpdateData(T data)
    {
        DataCache = data;
    }

    public abstract void Click();
}

The desired usage would be to create a new class that inherits from ScrolledListElement and override at least SetData method – to fill all of the visual components (images, text) representing the element.

Loading the data

The ScrolledList component will also be a generic class, which requires providing two types – one is the data type and the other is an element type. We can also add a restriction that the element type must inherit from the Component class. The signature looks like this:

public class ScrolledList<TDataType, TComponentType> : MonoBehaviour
        where TComponentType : Component

While creating several lists I noticed that it is really inconvenient to create an element prefab, delete it so that finally the lists starts empty, and then add it again when I want to make some visual improvements. That’s why I decided to leave the sample element as a child of the list and then treat it as a prefab in the list’s script. That involves looking for the element during the initialization phase, saving it and setting it as inactive. The only thing left to do in that phase is looking for the container game object in case it wasn’t set up in the Inspector. We also initialize the children list:

public void Init()
{
    if (_isInitialized)
        return;
    if (_container == null)
        _container = GetComponentInChildren<ScrolledPanel>(true);
    _gameObjectCache = gameObject;
    _containerTransform = _container.GetComponent<RectTransform>();
    _listElementCache = _container.GetComponentInChildren<TComponentType>(true);
    _listElementCache.gameObject.SetActive(false);
    _children = new List<GameObject>();
    _isInitialized = true;
}

The most crucial is the LoadView method. It takes a list of elements as an argument and takes care of updating the children, then sets itself as active and sends an event OnViewLoaded – in case something should happen when all of the elements are created.

public virtual void LoadView( IEnumerable<TDataType> elements )
{
    if ( !_isInitialized ) {
        Init();
    }
    UpdateList( elements );
    _gameObjectCache.SetActive( true );
    OnViewLoaded();
}

The next method we have to take care of is UpdateList. It invokes clearing method and AddElement method for every list item:

public virtual void UpdateList(IEnumerable<TDataType> elements)
{
    ClearPanel();
    if (elements == null) {
        return;
    }
    foreach (var element in elements)
        AddElement(element);
}

ClearPanel does what its name stands for – it clears present elements from the container game object. Firstly, it creates an actual list of the elements, then removes our “prefab” element from the list of items to be deleted, finally – deletes every item.

public virtual void ClearPanel()
{
    RefreshChildren();
    if (_children.Count == 0)
        return;
    _children.RemoveAt(0);
    _children.ForEach(c => Destroy(c.gameObject));
}

protected void RefreshChildren()
{
    _children.Clear();
    foreach (Transform child in _containerTransform) {
        _children.Add(child.gameObject);
    }
}

Last but not least – the AddElement method. It basically duplicates the sample element and invokes the SetData method from the ScrolledListElement class that we created earlier. This way the element gets attached to the container an can perform a proper initialization using provided data.

protected virtual void AddElement(TDataType element)
{
    var button = Instantiate(_listElementCache.gameObject);
    button.SetActive(true);
    var elemScript = button.GetComponent<ScrolledListElement<TDataType>>();
    elemScript.SetData(element, _containerTransform);
    _children.Add(button);
}

And that’s it! You can see the complete example here. To use this ScrolledList component, for example to display a list of users, you should create UserList script that inherits from ScrolledList together with the UserListElement that inherits from the ScrolledListElement. Of course you also need a user data model. The LoadView method is virtual, so if you need to prepare your data primarily to displaying it, overriding it would be the way to go (e.g. if you wanted to sort or filter the data).

Pros and cons

Using this list is really convenient, it may seem like a lot work at first, but then creating new lists becomes much simplified.

There is still much room for improvements. At Tabasco we create and destroy such repetitive items using our ObjectPoolManager, but this is beyond the scope of this post. There could be also a mechanism implemented that would actually check for changes during the reload of the view instead of just simply deleting all items and adding listed ones.

The major drawback however is the performance. All of the game objects get created on LoadView, and all of them are present on the scene (although not visible). This causes visual imperfection while sliding though the list when there are many elements. For the simple usage it is acceptable though.

In one of the future posts I will present much more complicated and efficient solution – ScrolledListPooled, so stay tuned!

Disclaimer

I created this scripts for the Kickerinho World implementation. They are provided under the MIT license and the original copyright belongs to TabascoInteractive.

This post is part of a series about scrolled lists:

Creating the GUI elements

ScrolledList – the basic implementation (this one)

Editor script that adds this component to the context menu

Scrolled List Dynamic - final version

Object pooling and Scrolled List Pooled

Performance comparison

As always - like my fan page or follow me on Twitter to get notified when future posts appear. :) I also encourge you to sign up for the Kickerinho World beta!

comments powered by Disqus