Desenvolvimento

23 nov, 2016

Lista de rolagem: o código

Publicidade

Olá. Recentemente, falei sobre como criar um componente de lista de rolagem, mas nós somente fixamos os elementos. Hoje, vou mostrar minha proposta de implementação de lista, que pode ser abastecida pela lista de elementos do script.

O script do elemento

Em primeiro lugar, nós criaremos uma classe abstrata de elementos – ScrolledListElement. Ela será uma classe genérica, o que significa que nós teremos que providenciar um tipo adicional quando herdarmos dela. Esse tipo será, automaticamente, nosso tipo de dados. Essa classe terá apenas dois campos.

  • DataCache: irá armazenar nossos dados atuais.
  • _transform: irá armazenar o cache de mudança por motivos de performance.

Todos os métodos serão virtuais. No método Awake, nós somente colocaremos a mudança em cache. O método SetData permite anexar esse elemento a seu novo pai e também armazenar seus dados. A classe completa ficará assim:

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();
}

A utilização desejada seria a criação de uma nova classe que herde de ScrolledListElement e sobrescreva ao menos o método SetData – para preencher todos os componentes visuais (imagens e textos) representando os elementos.

Carregando os dados

O componente ScrolledList também será uma classe genérica, e será necessário informar dois tipos – um é o tipo de dados e o outro é um tipo elemento. Nós também podemos adicionar uma restrição para que o tipo elemento deva herdar da classe Component. A assinatura fica assim:

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

Enquanto criava várias listas, eu percebi que é realmente inconveniente criar um elemento pronto, apagar e finalmente começar a lista vazia, e então adicionar os elementos novamente quando eu queria fazer alguma melhoria no visual. Por isso eu decidi manter o elemento de amostra como filho da lista e então tratá-la como uma lista pronta. Isso envolve olhar para o elemento durante a fase de inicialização, salvando-o e marcando-o como inativo. A única coisa a fazer nessa fase é observar o container do objeto no caso de ele não ser configurado no inspetor. Nós também inicializamos a lista filha:

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;
}

O método mais crucial é o LoadView. Ele leva uma lista de elementos como argumento e cuida de atualizar as filhas, então se autoconfigura como ativo e envia um evento OnViewLanded – nesse caso, algo deveria acontecer quando todos os elementos forem criados.

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

O próximo método que nós temos que cuidar é o UpdateList. Ele chama o método de limpeza e o método AddElement para cada item da lista.

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

ClearPanel (limpar painéis) faz exatamente o que o nome diz – limpa os elementos presentes no objeto de container. Primeiramente, ele cria uma lista atual dos elementos, depois ele remove nossos elementos prontos da lista de itens a serem deletados e, finalmente, deleta todos os itens.

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);
    }
}

Por último, mas não menos importante – o método AddElement. Ele basicamente duplica o elemento de exemplo e chama o método SetData da classe ScrolledElementList que nós criamos mais cedo. Dessa forma, o elemento é adicionado ao container e pode realizar a inicialização correta utilizando os dados providenciados.

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);
}

E é isso! Para utilizar esse componente ScrolledList, por exemplo para apresentar uma lista de usuários, você deve criar um script UserList que herda de ScrolledList junto com o UserListElement que herda de ScrolledListElement. É claro que você também precisa utilizar um modelo de dados de usuário. O método LoadView é virtual, sendo assim, se você precisa  preparar seus dados antes de exibi-los, sobrescrevê-los seria um caminho (ex.: se você quiser filtrar ou organizar os dados).

Prós e contras

Utilizar essa lista é realmente conveniente, pode parecer muito trabalho a princípio, mas criar novas listas acaba se tornando muito simples.

Ainda há muito a ser melhorado. Na Tabasco, nós criamos e destruímos itens repetitivos utilizando nosso ObjectPoolManager (repositório de objetos), mas isso foge do objetivo deste artigo. Poderia também haver um mecanismo implementado que verificasse as mudanças durante o carregamento da View, em vez de somente deletar todos os itens e adicionar os itens listados.

A maior desvantagem, no entanto, é a performance. Todos os objetos são criados no LoadView, e todos são apresentados na cena (mesmo que não visíveis). Isso causa uma imperfeição visual enquanto a lista é apresentada quando existem muitos elementos. Para uma utilização simples, entretanto, é aceitável.

Em um artigo futura, apresentarei soluções mais eficientes e complexas – ScrooledListPooled, então, fique ligado.

Disclaimer

Criei esses scripts para a implementação do Kickerinho World. Eles são divulgados sob a licença do MIT e os direitos pertencem à TabascoInterative.

***

Emilia Szymanska faz parte do time de colunistas internacionais do iMasters. A tradução do artigo é feita pela redação iMasters, com autorização do autor, e você pode acompanhar o artigo em inglês no link: http://www.gamasutra.com/blogs/EmiliaSzymanska/20161020/283748/Scrolled_List__the_code.php.