RecyclerView con GridLayoutManager y Picasso mostrando imagen incorrecta

Actualización # 1

Añadido hasStableIds (true) y Picasso actualizado a la versión 2.5.2. No resuelve el problema.

Reproducción:

RecyclerView con GridLayoutManager (spanCount = 3). Los elementos de lista son CardViews con ImageView dentro.

Cuando todos los elementos no se ajustan a la llamada de la pantalla notifyItemChanged en un elemento causa más de una llamada a onBindViewHolder (). Una llamada es para la posición de notifyItemChanged otros para los elementos no visibles en la pantalla.

Problema:

A veces el elemento en la posición pasada a la notifyItemChanged se carga con una imagen que pertenece a un elemento que no está en la pantalla (muy probablemente debido al reciclado del titular de la vista – aunque yo asumiría que si el elemento permanece en su lugar, Sería el mismo).

He encontrado el comentario de Jake sobre otro tema aquí sobre la llamada load () incluso si el archivo / uri es nulo. La imagen se carga en cada onBindViewHolder aquí.

Ejemplo de aplicación simple:

git clone https://github.com/gswierczynski/recycler-view-grid-layout-with-picasso.git 

Toque en un elemento llama a notifyItemChanged con un parámetro igual a la posición de ese elemento.

Código:

 public class MainActivity extends ActionBarActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); if (savedInstanceState == null) { getSupportFragmentManager().beginTransaction() .add(R.id.container, new PlaceholderFragment()) .commit(); } } public static class PlaceholderFragment extends Fragment { public PlaceholderFragment() { } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View rootView = inflater.inflate(R.layout.fragment_main, container, false); RecyclerView rv = (RecyclerView) rootView.findViewById(R.id.rv); rv.setLayoutManager(new GridLayoutManager(getActivity(), 3)); rv.setItemAnimator(new DefaultItemAnimator()); rv.setAdapter(new ImageAdapter()); return rootView; } } private static class ImageAdapter extends RecyclerView.Adapter<ImageViewHolder> implements ClickableViewHolder.OnClickListener { public static final String TAG = "ImageAdapter"; List<Integer> resourceIds = Arrays.asList( R.drawable.a0, R.drawable.a1, R.drawable.a2, R.drawable.a3, R.drawable.a4, R.drawable.a5, R.drawable.a6, R.drawable.a7, R.drawable.a8, R.drawable.a9, R.drawable.a10, R.drawable.a11, R.drawable.a12, R.drawable.a13, R.drawable.a14, R.drawable.a15, R.drawable.a16, R.drawable.a17, R.drawable.a18, R.drawable.a19, R.drawable.a20); @Override public ImageViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item, parent, false); return new ImageViewHolder(v, this); } @Override public void onBindViewHolder(ImageViewHolder holder, int position) { Log.d(TAG, "onBindViewHolder position: " + position + " | holder obj:" + holder.toString()); Picasso.with(holder.iv.getContext()) .load(resourceIds.get(position)) .fit() .centerInside() .into(holder.iv); } @Override public int getItemCount() { return resourceIds.size(); } @Override public void onClick(View view, int position) { Log.d(TAG, "onClick position: " + position); notifyItemChanged(position); } @Override public boolean onLongClick(View view, int position) { return false; } } private static class ImageViewHolder extends ClickableViewHolder { public ImageView iv; public ImageViewHolder(View itemView, OnClickListener onClickListener) { super(itemView, onClickListener); iv = (ImageView) itemView.findViewById(R.id.iv); } } } public class ClickableViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, View.OnLongClickListener { OnClickListener onClickListener; public ClickableViewHolder(View itemView, OnClickListener onClickListener) { super(itemView); this.onClickListener = onClickListener; itemView.setOnClickListener(this); itemView.setOnLongClickListener(this); } @Override public void onClick(View view) { onClickListener.onClick(view, getPosition()); } @Override public boolean onLongClick(View view) { return onClickListener.onLongClick(view, getPosition()); } public static interface OnClickListener { void onClick(View view, int position); boolean onLongClick(View view, int position); } } 

Pasé más tiempo del que me gustaría admitir para trabajar con rarezas con RecyclerView y el nuevo adaptador que viene con él. Lo único que finalmente funcionó para mí en términos de actualizaciones correctas y asegurarse de que notifyDataSetChanges y todos sus otros hermanos no causaron un comportamiento extraño fue el siguiente:

En mi adaptador,

 setHasStableIds(true); 

En el constructor. Entonces anulé este método:

 @Override public long getItemId(int position) { // return a unique id here } 

Y se aseguró de que todos mis artículos devolvieran un identificador único.

Cómo lograrlo depende de usted. Para mí, los datos fueron suministrados desde mi servicio web en la forma de un UUID y engañé convirtiendo partes del UUID a largo usando esto:

 SomeContent content = _data.get(position); Long code = Math.abs(content.getContentId().getLeastSignificantBits()); 

Obviamente esto no es un enfoque muy seguro, pero es probable que funcione para mis listas que contendrán <1000 elementos. Hasta ahora no he tenido ningún problema con él.

Lo que recomiendo es probar este enfoque y ver si funciona para usted. Puesto que usted tiene una matriz, obtener un número único para usted debe ser simple. Tal vez trate de devolver la posición del elemento real ( y no la posición que se pasa en el getItemId() ) o crear un único largo para cada uno de sus registros y pasar que en.

¿Ha intentado llamar al método mutate mutate() en el Drawable? Véase aquí , por ejemplo.

Aquí una solución de trabajo pero tiene problemas gráficos al llamar a notifyDataSetChanged()

 holder.iv.post(new Runnable() { @Override public void run() { Picasso.with(holder.iv.getContext()) .load(resourceIds.get(position)) .resize(holder.iv.getWidth(), 0) .into(holder.iv); }); 

Funciona porque en este punto la imagen tiene un ancho, por desgracia cuando necesito actualizar todas las casillas de verificación en el visor (como una acción select all), y llamo notifyDataSetChanged() y el efecto es muy feo

Sigue buscando una mejor solución

Editar: esta solución funciona para mí:

  holder.iv.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) holder.iv.getViewTreeObserver().removeOnGlobalLayoutListener(this); else holder.iv.getViewTreeObserver().removeGlobalOnLayoutListener(this); Picasso.with(holder.iv.getContext()) .load(resourceIds.get(position)) .resize(holder.iv.getMeasuredWidth(), 0) .into(holder.iv); } }); 
  • RecyclerView - elementos de pantalla que saltan el primer pase de diseño
  • Recyclerview vincula todas las vistas al mismo tiempo
  • Firebase UI RecyclerView onClick
  • Adición de elementos al reciclado sin fin con RecyclerView con un reciclado ReverseLayout
  • Contenido dinámico dentro de una lista en Android
  • El uso del ejemplo de RecyclerView no compila
  • Extraño comportamiento de las imágenes en RecyclerView
  • Cómo filtrar un RecyclerView con un SearchView
  • Android Studio 2.2.2 error "No se puede localizar el modo 0" en la visualización de renderizado después de la actualización
  • RecyclerView no se desplaza como se esperaba
  • Fade out / fade en TextView basado en desplazamiento
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.