Desempenho de ListView

Baixar exemplo Baixar o exemplo

Ao escrever aplicativos móveis, o desempenho é importante. Os usuários passaram a esperar rolagem suave e tempos de carregamento rápidos. Não atender às expectativas dos usuários custará classificações no repositório de aplicativos ou, no caso de um aplicativo de linha de negócios, custará tempo e dinheiro à sua organização.

O Xamarin.FormsListView é uma exibição poderosa para exibir dados, mas tem algumas limitações. O desempenho da rolagem pode sofrer ao usar células personalizadas, especialmente quando elas contêm hierarquias de exibição profundamente aninhadas ou usam determinados layouts que exigem medidas complexas. Felizmente, há técnicas que você pode usar para evitar um desempenho ruim.

Estratégia de cache

ListViews geralmente são usados para exibir muito mais dados do que se ajustam na tela. Por exemplo, um aplicativo de música pode ter uma biblioteca de músicas com milhares de entradas. Criar um item para cada entrada desperdiçaria memória valiosa e teria um desempenho ruim. Criar e destruir linhas constantemente exigiria que o aplicativo instanciasse e limpasse objetos constantemente, o que também teria um desempenho ruim.

Para conservar a memória, os equivalentes nativos ListView para cada plataforma têm recursos internos para reutilizar linhas. Somente as células visíveis na tela são carregadas na memória e o conteúdo é carregado em células existentes. Esse padrão impede que o aplicativo instancie milhares de objetos, economizando tempo e memória.

Xamarin.FormsListView permite a reutilização da célula por meio da ListViewCachingStrategy enumeração , que tem os seguintes valores:

public enum ListViewCachingStrategy
{
    RetainElement,   // the default value
    RecycleElement,
    RecycleElementAndDataTemplate
}

Observação

O Plataforma Universal do Windows (UWP) ignora a RetainElement estratégia de cache, pois sempre usa o cache para melhorar o desempenho. Portanto, por padrão, ele se comporta como se a RecycleElement estratégia de cache fosse aplicada.

RetainElement

A RetainElement estratégia de cache especifica que o ListView gerará uma célula para cada item na lista e é o comportamento padrão ListView . Ele deve ser usado nas seguintes circunstâncias:

  • Cada célula tem um grande número de associações (20-30+).
  • O modelo de célula é alterado com frequência.
  • O teste revela que a RecycleElement estratégia de cache resulta em uma velocidade de execução reduzida.

É importante reconhecer as consequências da estratégia de RetainElement cache ao trabalhar com células personalizadas. Qualquer código de inicialização de célula precisará ser executado para cada criação de célula, o que pode ser várias vezes por segundo. Nessa circunstância, as técnicas de layout que estavam bem em uma página, como o uso de várias instâncias aninhadas StackLayout , tornam-se gargalos de desempenho quando são configuradas e destruídas em tempo real à medida que o usuário rola.

RecycleElement

A RecycleElement estratégia de cache especifica que o ListView tentará minimizar o volume de memória e a velocidade de execução reciclando as células da lista. Esse modo nem sempre oferece uma melhoria de desempenho, e os testes devem ser executados para determinar quaisquer melhorias. No entanto, é a escolha preferencial e deve ser usada nas seguintes circunstâncias:

  • Cada célula tem um número pequeno a moderado de associações.
  • Cada célula BindingContext define todos os dados da célula.
  • Cada célula é amplamente semelhante, com o modelo de célula inalterado.

Durante a virtualização, a célula terá seu contexto de associação atualizado e, portanto, se um aplicativo usar esse modo, deverá garantir que as atualizações de contexto de associação sejam tratadas adequadamente. Todos os dados sobre a célula devem vir do contexto de associação ou podem ocorrer erros de consistência. Esse problema pode ser evitado usando a associação de dados para exibir dados de célula. Como alternativa, os OnBindingContextChanged dados da célula devem ser definidos na substituição, em vez de no construtor da célula personalizada, conforme demonstrado no exemplo de código a seguir:

public class CustomCell : ViewCell
{
    Image image = null;
    
    public CustomCell ()
    {
        image = new Image();
        View = image;
    }
    
    protected override void OnBindingContextChanged ()
    {
        base.OnBindingContextChanged ();
        
        var item = BindingContext as ImageItem;
        if (item != null) {
            image.Source = item.ImageUrl;
        }
    }
}

Para obter mais informações, consulte Alterações de contexto de associação.

No iOS e no Android, se as células usarem renderizadores personalizados, elas deverão garantir que a notificação de alteração de propriedade seja implementada corretamente. Quando as células forem reutilizados, seus valores de propriedade serão alterados quando o contexto de associação for atualizado para o de uma célula disponível, com PropertyChanged eventos sendo gerados. Para obter mais informações, consulte Personalizando um ViewCell.

RecycleElement com um DataTemplateSelector

Quando um ListView usa um DataTemplateSelector para selecionar um DataTemplate, a RecycleElement estratégia de cache não armazena em cache DataTemplates. Em vez disso, um DataTemplate é selecionado para cada item de dados na lista.

Observação

A RecycleElement estratégia de cache tem um pré-requisito, introduzido na Xamarin.Forms versão 2.4, que quando um DataTemplateSelector é solicitado a selecionar um que cada DataTemplate um DataTemplate deve retornar o mesmo ViewCell tipo. Por exemplo, dado um ListView com um DataTemplateSelector que pode retornar MyDataTemplateA (em MyDataTemplateA que retorna um ViewCell do tipo MyViewCellA), ou MyDataTemplateB (em que MyDataTemplateB retorna um ViewCell do tipo MyViewCellB), quando MyDataTemplateA é retornado, ele deve retornar MyViewCellA ou uma exceção será gerada.

RecycleElementAndDataTemplate

A RecycleElementAndDataTemplate estratégia de cache baseia-se na RecycleElement estratégia de cache, garantindo também que quando um ListView usa um DataTemplateSelector para selecionar um DataTemplate, DataTemplates sejam armazenados em cache pelo tipo de item na lista. Portanto, DataTemplates são selecionados uma vez por tipo de item, em vez de uma vez por instância de item.

Observação

A RecycleElementAndDataTemplate estratégia de cache tem um pré-requisito de que os DataTemplates retornados pelo DataTemplateSelector devem usar o DataTemplate construtor que usa um Type.

Definir a estratégia de cache

O ListViewCachingStrategy valor de enumeração é especificado com uma ListView sobrecarga de construtor, conforme mostrado no exemplo de código a seguir:

var listView = new ListView(ListViewCachingStrategy.RecycleElement);

Em XAML, defina o CachingStrategy atributo conforme mostrado no XAML abaixo:

<ListView CachingStrategy="RecycleElement">
    <ListView.ItemTemplate>
        <DataTemplate>
            <ViewCell>
              ...
            </ViewCell>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

Esse método tem o mesmo efeito que definir o argumento de estratégia de cache no construtor em C#.

Definir a estratégia de cache em um ListView subclasse

Definir o CachingStrategy atributo de XAML em uma subclasse ListView não produzirá o comportamento desejado, pois não há nenhuma CachingStrategy propriedade em ListView. Além disso, se o XAMLC estiver habilitado, a seguinte mensagem de erro será produzida: Nenhuma propriedade, propriedade associável ou evento encontrado para 'CachingStrategy'

A solução para esse problema é especificar um construtor na subclasse ListView que aceita um ListViewCachingStrategy parâmetro e o passa para a classe base:

public class CustomListView : ListView
{
    public CustomListView (ListViewCachingStrategy strategy) : base (strategy)
    {
    }
    ...
}

Em seguida, o ListViewCachingStrategy valor de enumeração pode ser especificado de XAML usando a x:Arguments sintaxe :

<local:CustomListView>
    <x:Arguments>
        <ListViewCachingStrategy>RecycleElement</ListViewCachingStrategy>
    </x:Arguments>
</local:CustomListView>

Sugestões de desempenho de ListView

Há muitas técnicas para melhorar o desempenho de um ListView. As sugestões a seguir podem melhorar o desempenho de seu ListView

  • Associe a ItemsSource propriedade a uma IList<T> coleção em vez de uma IEnumerable<T> coleção, pois IEnumerable<T> as coleções não dão suporte a acesso aleatório.
  • Use as células internas (como TextCell / SwitchCell ) em vez de ViewCell sempre que possível.
  • Use menos elementos. Por exemplo, considere usar um único FormattedString rótulo em vez de vários rótulos.
  • Substitua o ListView por um TableView ao exibir dados não homogêneos, ou seja, dados de tipos diferentes.
  • Limite o uso do Cell.ForceUpdateSize método . Se for superutilizado, isso prejudicará o desempenho.
  • No Android, evite definir a visibilidade ou a cor de um ListViewseparador de linha após a instanciação, pois isso resulta em uma grande penalidade de desempenho.
  • Evite alterar o layout da célula com base no BindingContext. A alteração do layout gera grandes custos de medição e inicialização.
  • Evite hierarquias de layout profundamente aninhadas. Use AbsoluteLayout ou Grid para ajudar a reduzir o aninhamento.
  • Evite específico LayoutOptions diferente de Fill (Fill é o mais barato de calcular).
  • Evite colocar um ListView dentro de um ScrollView pelos seguintes motivos:
    • O ListView implementa sua própria rolagem.
    • O ListView não receberá nenhum gesto, pois eles serão manipulados pelo pai ScrollView.
    • O ListView pode apresentar um cabeçalho e rodapé personalizados que rolam com os elementos da lista, potencialmente oferecendo a funcionalidade para a qual o ScrollView foi usado. Para obter mais informações, consulte Cabeçalhos e rodapés.
  • Considere um renderizador personalizado se você precisar de um design específico e complexo apresentado em suas células.

AbsoluteLayout tem o potencial de executar layouts sem uma única chamada de medida, tornando-o altamente eficaz. Se AbsoluteLayout não puder ser usado, considere RelativeLayout. Se estiver usando RelativeLayout, passar restrições diretamente será consideravelmente mais rápido do que usar a API de expressão. Esse método é mais rápido porque a API de expressão usa JIT e, no iOS, a árvore precisa ser interpretada, o que é mais lento. A API de expressão é adequada para layouts de página em que ela só é necessária no layout inicial e na rotação, mas em , em ListViewque é executada constantemente durante a rolagem, prejudica o desempenho.

Criar um renderizador personalizado para um ListView ou suas células é uma abordagem para reduzir o efeito dos cálculos de layout no desempenho de rolagem. Para obter mais informações, consulte Personalizando um ListView e Personalizando um ViewCell.