A evolução dos adaptadores no Android
Conteudo
Visão geral
Se você já está desenvolvendo Android há algum tempo, pode muito bem ter que implementar uma lista de visualizações em vários pontos de sua carreira.
Primeiro, você provavelmente usou ListView e GridView para mostrar uma lista de itens vertical e horizontalmente – em uma estrutura de grade. Então veio o Recyclerview, que trouxe muitos aprimoramentos aos padrões existentes e novas implementações como viewtypes – este foi um aprimoramento revolucionário nos adaptadores Android. Recentemente, ListAdapter surgiu, concentrando-se principalmente em aprimorar o desempenho para fornecer uma experiência suave, mesmo com milhares de itens na lista.
É importante saber o que usamos hoje. É igualmente importante saber por que usamos o que usamos hoje. Então, vamos dar uma olhada na história dos adaptadores no Android.
Base Adapter
Este foi o início dos adaptadores no Android. Nos adaptadores básicos, não temos nenhum tipo de visualização ou decorador de itens como no desenvolvimento Android moderno. Vamos dar uma olhada em um adaptador de base simples:
class EmailsAdapter extends BaseAdapter { private LayoutInflater inflater = null; int selectedpos = -1; @Override public int getCount() { return email_accnts.size(); } @Override public Object getItem(int position) { return position; } @Override public long getItemId(int position) { return position; } class ViewHolder { TextView tvPersonName; TextView tvPersonEmmail; } @Override public View getView(final int position, View convertView, ViewGroup parent) { View view = convertView; ViewHolder holder = null; LayoutInflater inflater = (LayoutInflater) getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE); view = inflater.inflate(R.layout.adapter_email, null); holder = new ViewHolder(); holder.tvPersonName = (TextView) view.findViewById(R.id.tv_person_name); holder.tvPersonEmmail = (TextView) view.findViewById(R.id.tv_person_email); view.setTag(holder); if (convertView != null) { holder = (ViewHolder) view.getTag(); } holder.tvPersonName.setText(emailList.get(position).name); holder.tvPersonEmmail.setText(emailList.get(position).email); view.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { } }); return view; } }
Aqui, getView é a função inteiramente responsável por criar e aumentar cada visualização. Temos que checar manualmente se a view foi criada ou não, então usar as views no layout, que é a melhor maneira naquele momento. Pelo que sei, para inflar diferentes tipos de visualização, precisamos projetar todas as visualizações no mesmo layout e temos que alternar a visibilidade com base no tipo. Isso dá muito trabalho e as visualizações não são recicladas corretamente. Essas coisas limitam os desenvolvedores Android de criar listas sofisticadas como as que vemos no Instagram e no Twitter atualmente.
A outra grande desvantagem é que temos que usar o Grid-Adapter para mostrar visualizações em grades. Pessoalmente, uso BaseAdapter e GridAdapter por um tempo no início da minha carreira no final de 2015 – acredite, eles são muito ruins!
Não é que eles sejam ruins no desempenho, mas temos que fazer cada pedacinho de trabalho o tempo todo e, mesmo assim, haverá problemas como exceções OOM (OutOfMemory). Isso foi um pesadelo naquela época.
RecyclerView Adapter
Seguindo em frente com o miserável adaptador básico por meses, um dia conheci o milagroso RecyclerviewAdapter. Isso foi uma virada de jogo para mim. Ele não apenas resolveu problemas no baseAdapter, mas também abriu as portas para novas possibilidades, como viewTypes, itens de decoração, combinação de listas verticais, horizontais e de grade com gerenciador de layout e muito mais.
Vamos ver um adaptador simples de reciclagem:
public class EmailsAdapter extends RecyclerView.Adapter<EmailsAdapter.EmailsItemViewHolder> { private List<CommonListUtils> itemsList; private Context mContext; public EmailsAdapter(Context context, List<CommonListUtils> itemsList) { this.itemsList = itemsList; this.mContext = context; } @Override public CommonListItemRowHolder onCreateViewHolder(ViewGroup viewGroup, int i) { View view = LayoutInflater.from(mContext).inflate(R.layout.adapter_emails, viewGroup, false); return new CommonListItemRowHolder(view); } @Override public void onBindViewHolder(final CommonListItemRowHolder holder, final int position) { holder.getBindings().tvPersonName.setText(itemsList.get(position).name); holder.getBindings().tvPersonEmmail.setText(itemsList.get(position).email); } @Override public int getItemCount() { return (null != itemsList ? itemsList.size() : 0); } public class EmailsItemViewHolder extends RecyclerView.ViewHolder { private EmailListItemBinding bindings_item; public EmailsItemViewHolder(View itemView) { super(itemView); bindings_item = DataBindingUtil.bind(itemView); } public EmailListItemBinding getBindings() { return bindings_item; } } }
Neste adaptador de recyclerview, temos duas funções diferentes: onCreateViewHolder e onBindViewHolder. Eles criam a visualização apenas uma vez e a aumentam sempre que necessário. Isso aumenta o desempenho porque o recyclerview apenas cria e usa visualizações quando necessário. Se você tiver cem itens na lista, ele não carregará todos eles – em vez disso, carregará apenas o número de visualizações que podem ser mostradas ao usuário no momento. Durante a rolagem, o adaptador recyclerview armazena apenas as visualizações que são visíveis para o usuário na memória, o que o torna mais eficiente e direto.
ViewTypes
Além das vantagens acima, recyclerview também abriu as portas para uma nova implementação, por meio de viewTypes, para construir listas aninhadas e layouts diferentes no mesmo adaptador.
No adaptador acima, implementamos a função getItemViewType () e o código conforme mostrado a seguir nesse método. Esta é a lógica viewType que diferencia as visualizações para aumentar.
@Override public int getItemViewType(int position) { if (employees.get(position).isEmail) { return TYPE_EMAIL; } else { return TYPE_CALL; } }
O viewType que passamos aqui está disponível em onCreateViewHolder. Ao usá-lo, aumentamos diferentes visões.
Suporta grades e listas
Há um novo conceito chamado gerenciador de layout na visão recicladora, que nos permite integrar grade e listas sem nenhuma complexidade.
- LinearLayoutManager: Suporta listas verticais e horizontais.
- StaggeredLayoutManager: oferece suporte a listas escalonadas como as que vemos no aplicativo Pinterest.
- GridLayoutManager: Suporta grades de exibição como uma galeria.
Veja como podemos alterar rapidamente a estrutura da lista com o gerenciador de layout:
//Vertical lists val layoutManager: RecyclerView.LayoutManager = LinearLayoutManager( this, LinearLayoutManager.VERTICAL, false ) //Horizontal lists val layoutManager: RecyclerView.LayoutManager = LinearLayoutManager( this, LinearLayoutManager.HORIZONTAL, false ) //Grid llists val layoutManager: RecyclerView.LayoutManager = GridLayoutManager( this, rowCount, LinearLayoutManager.VERTICAL, false ) //Assign to recyclerview recyclerview?.layoutManager = layoutManager
Animations
Animações em listas é outro recurso interessante que veio junto com o RecyclerView. O RecyclerView executará uma animação relevante se qualquer uma das funções de notificação for acionada, exceto para notificarDataSetChanged. Isso inclui notificarItemChanged, notificarItemInserted, notificarItemMoved e notificarItemRemoved.
ViewHolder-Pattern
O padrão ViewHolder é usado para acelerar a renderização de suas listas para que funcione suavemente. findViewById é caro quando usado cada vez que um item de lista é renderizado – ele deve penetrar profundamente em sua hierarquia de layout e também instanciar objetos. Como as listas podem redesenhar seus itens com bastante frequência durante a rolagem, essa sobrecarga pode afetar o desempenho.
O RecyclerView é sem dúvida um widget revolucionário no Android, mas há o próximo passo para tudo. Assim é para RecyclerView e a próxima etapa é chamada de ListAdapter.
ListAdapter
O adaptador RecyclerView não é uma extensão do BaseAdapter. ListAdapter, por outro lado, é a próxima geração do adaptador RecyclerView (ele estende o adaptador recyclerview). ListAdapter se concentra principalmente em melhorar o desempenho – mesmo com grandes blocos de dados, como feeds intermináveis do Instagram e do Twitter com vídeos de reprodução automática.
Ao trabalhar com o RecyclerView, o próprio adaptador é responsável por validar a lista se novos itens forem adicionados ou removidos ou se algo tiver sido alterado nos itens existentes. Com ListAdapter, este trabalho agora é feito por AsyncListDiffer, um auxiliar para calcular a diferença entre duas listas via DiffUtil em um thread de segundo plano. Dê uma olhada em seu uso básico:
public static final DiffUtil.ItemCallback<User> DIFF_CALLBACK = new DiffUtil.ItemCallback<User>() { @Override public boolean areItemsTheSame( @NonNull User oldUser, @NonNull User newUser) { // User properties may have changed if reloaded from the DB, but ID is fixed return oldUser.getId() == newUser.getId(); } @Override public boolean areContentsTheSame( @NonNull User oldUser, @NonNull User newUser) { // NOTE: if you use equals, your object must properly override Object#equals() // Incorrectly returning false here will result in too many animations. return oldUser.equals(newUser); } }
Possui duas funções principais:
areItemsTheSame
Esta função decide se deve aumentar a visualização atual ou criar uma nova. Conforme mostrado acima, precisamos comparar o valor da chave primária aqui como um id, que é único para cada item da lista.
areContentsTheSame
O adaptador de lista apenas atualiza a visualização se esta função retornar verdadeiro. Isso ocorre porque podemos ter muitas variáveis nos itens que podem não afetar a IU, portanto, as alterações nessas variáveis não devem afetar a IU. Para trabalhar isso de forma eficaz, precisamos comparar as variáveis de item que usamos na IU.
Desta forma, podemos controlar quando um novo item é rende rizado na lista e também quando os dados do item são atualizados. Isso é legal, certo!