findViewById vs View Holder Pattern en el adaptador ListView

Siempre uso LayoutInflater y findViewById para crear nuevos elementos en el método getView de un Adapter .

Pero en muchos artículos, las personas escriben que findViewById es muy lento y se recomienda encarecidamente utilizar el patrón del titular de la vista.

¿Alguien puede explicar por qué findViewById es tan lento? ¿Y por qué el patrón del titular de la vista es más rápido?

¿Y qué debería hacer si fuera necesario agregar diferentes elementos a un ListView ? ¿Debo crear clases para cada tipo?

 static class ViewHolderItem1 { TextView textViewItem; } static class ViewHolderItem2 { Button btnViewItem; } static class ViewHolderItem3 { Button btnViewItem; ImageView imgViewItem; } 

¿Alguien puede explicar por qué findViewById es tan lento? ¿Y por qué View Holder Pattern es más rápido?

Cuando no está utilizando Holder, el método getView() llamará a findViewById() tantas veces como su fila (s) estará fuera de la vista. Entonces, si tiene 1000 filas en la lista y 990 filas estarán fuera de la vista, entonces 990 veces se llamará findViewById() nuevamente.

El patrón de diseño del titular se usa para el almacenamiento en caché de View. El objeto Holder (arbitrario) contiene widgets secundarios de cada fila y cuando row está fuera de View, no se llamará a findViewById (), pero se reciclará View y se obtendrán widgets de Holder.

 if (convertView == null) { convertView = inflater.inflate(layout, null, false); holder = new Holder(convertView); convertView.setTag(holder); // setting Holder as arbitrary object for row } else { // view recycling // row already contains Holder object holder = (Holder) convertView.getTag(); } // set up row data from holder titleText.setText(holder.getTitle().getText().toString()); 

Donde la clase Holder puede verse así:

 public class Holder { private View row; private TextView title; public Holder(View row) { this.row = row; } public TextView getTitle() { if (title == null) { title = (TextView) row.findViewById(R.id.title); } return title; } } 

Como @meredrica señaló que si desea obtener un mejor rendimiento, puede usar campos públicos (pero destruye la encapsulación).

Actualizar:

Aquí hay un segundo enfoque sobre cómo usar el patrón ViewHolder :

 ViewHolder holder; // view is creating if (convertView == null) { convertView = LayoutInflater.from(mContext).inflate(R.layout.row, parent, false); holder = new ViewHolder(); holder.title = (TextView) convertView.findViewById(R.id.title); holder.icon = (ImageView) convertView.findViewById(R.id.icon); convertView.setTag(holder); } // view is recycling else { holder = (ViewHolder) convertView.getTag(); } // set-up row final MyItem item = mItems.get(position); holder.title.setText(item.getTitle()); ... private static class ViewHolder { public TextView title; public ImageView icon; } 

Actualización # 2:

Como todos saben, Google y AppCompat v7 como biblioteca de soporte lanzaron el nuevo ViewGroup llamado RecyclerView, que está diseñado para representar vistas basadas en adaptadores. Como dice @antonioleiva en la publicación : “Se supone que es el sucesor de ListView y GridView” .

Para poder usar este elemento básicamente necesitas uno de los más importantes y es un tipo especial de Adapter que está incluido en el ViewGroup mencionado – RecyclerView.Adapter donde ViewHolder es de lo que estamos hablando aquí 🙂 Simplemente, este nuevo elemento ViewGroup tiene su propio patrón ViewHolder implementado. Todo lo que debe hacer es crear una clase de ViewHolder personalizada que tenga que extenderse desde RecyclerView.ViewHolder y no necesita preocuparse por comprobar si la fila actual en el adaptador es nula o no.

El adaptador lo hará por usted y puede estar seguro de que la fila se inflará solo en caso de que deba inflarse (diría). Aquí hay una simple imlementación:

 public static class ViewHolder extends RecyclerView.ViewHolder { private TextView title; public ViewHolder(View root) { super(root); title = root.findViewById(R.id.title); } } 

Dos cosas importantes aquí:

  • Tienes que llamar al constructor super () en el que necesitas pasar tu vista raíz de la fila
  • Puede obtener una posición específica de la fila directamente desde ViewHolder a través del método getPosition () . Esto es útil cuando desea realizar alguna acción después de tocar 1 en el widget de fila.

Y un uso de ViewHolder en Adapter. El adaptador tiene tres métodos que debe implementar:

  • onCreateViewHolder () – donde se crea ViewHolder
  • onBindViewHolder () – donde está actualizando su fila. Podemos decir que es un pedazo de código en el que está reciclando la fila
  • getItemCount () – diría que es el mismo que el método típico getCount () en BaseAdapter

Entonces un pequeño ejemplo:

 @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View root = LayoutInflater.from(mContext).inflate(myLayout, parent, false); return new ViewHolder(root); } @Override public void onBindViewHolder(ViewHolder holder, int position) { Item item = mItems.get(position); holder.title.setText(item.getTitle()); } @Override public int getItemCount() { return mItems != null ? mItems.size() : 0; } 

1 Es bueno mencionar que RecyclerView no proporciona una interfaz directa para poder escuchar eventos de clics de elementos. Esto puede ser curioso para alguien, pero aquí hay una buena explicación de por qué no es tan curioso como parece.

Lo resolví creando mi propia interfaz que se usa para manejar los eventos de clics en las filas (y cualquier tipo de widget que quiera en la fila):

 public interface RecyclerViewCallback { public void onItemClick(T item, int position); } 

Lo estoy vinculando en Adapter a través del constructor y luego llamo a esa callback en ViewHolder:

 root.setOnClickListener(new View.OnClickListener { @Override public void onClick(View v) { int position = getPosition(); mCallback.onItemClick(mItems.get(position), position); } }); 

Este es un ejemplo básico, así que no lo tomes solo de una manera posible. Las posibilidades son infinitas.

El patrón ViewHolder creará una instancia estática de ViewHolder y la adjuntará al elemento de la vista la primera vez que se cargue, y luego se recuperará de esa etiqueta de vista en las futuras llamadas. ya que sabíamos que el método getView () se llama con mucha frecuencia, especialmente cuando hay muchos elementos en la vista de lista para desplazarse, de hecho se llama cada vez que un elemento de vista de lista se vuelve visible en desplazamiento.

ViewHolder Pattern evitará que findViewById() muchas veces como inútil, manteniendo las vistas en una referencia estática, es un buen patrón para guardar algunos recursos (especialmente cuando necesita hacer referencia a muchas vistas en los elementos de la vista de lista).

muy bien dicho por @RomainGuy

El ViewHolder puede, y debería, también ser utilizado para almacenar estructuras temporales de datos para evitar asignaciones de memoria en getView (). El ViewHolder contiene un búfer char para evitar asignaciones al obtener datos del Cursor.