2015-03-27 15 views
11

Update # 1RecyclerView mit GridLayoutManager und Picasso falsches Bild

Added hasStableIds (true) und aktualisiert Picasso auf Version 2.5.2 zeigt. Es löst das Problem nicht.

Fortpflanzung:

RecyclerView mit GridLayoutManager (spanCount = 3). Listenelemente sind CardViews mit ImageView.

Wenn alle Elemente nicht passen, ruft der Aufruf von notifyItemChanged für einen Eintrag mehr als einen Aufruf von onBindViewHolder() auf. Ein Aufruf bezieht sich auf die Position von notifyItemChanged für Positionen, die auf dem Bildschirm nicht sichtbar sind.

Ausgabe:

Manchmal wird das Element an der Position der notifyItemChanged weitergegeben geladen mit einem Bild zu einem Element gehören, die nicht auf dem Bildschirm (höchstwahrscheinlich auf das Recycling der Ansicht Halter ist - obwohl ich würde Nehmen wir an, dass der übergebene Viewholder derselbe wäre, wenn der Artikel an seinem Platz bleibt.

Ich habe Jakes Kommentar zu einem anderen Thema hier über den Aufruf von load() gefunden, auch wenn die Datei/uri null ist. Das Bild wird hier auf jeden onBindViewHolder geladen.

einfache Beispielanwendung:

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

Tippen Sie auf ein Element Anrufe notifyItemChanged mit dem Parameter gleich der Position dieses Elements.

Code:

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

hast du eine lösung gefunden? Ich habe das gleiche Problem. Passiert das nur mit RecyclerView? Hast du es mit ListView versucht? –

+0

Noch nicht. Da ich mir nicht sicher bin, ob dies ein Problem mit Picasso oder GridLayoutManager ist, habe ich Probleme sowohl auf der PIcasso github Projektseite (https://github.com/square/picasso/issues/954) als auch im AOSP Google Code (https: // code.google.com/p/android/issues/detail?id=162699). Ich glaube nicht, dass dieses Problem auf ListView existiert. – gswierczynski

+0

Haben Sie dieses Problem behoben, ich sehe immer noch, dass dieses Problem in com.squadeup.picasso existiert: picasso: 2.5.2 setSupportsChangeAnimations (false) und setHasStableIds (true) scheinen dies zu verhindern – Harkish

Antwort

4

ich mehr Zeit damit verbracht, als ich Merkwürdigkeiten zugeben möchte Umgehen mit RecyclerView und dem neuen Adapter, der mit ihm kommt. Das einzige, was schließlich für mich in Bezug auf die richtigen Updates gearbeitet und dafür sorgen, notifyDataSetChanges und alle seine anderen Geschwister nicht merkwürdiges Verhalten verursachen, war dies:

Auf meinem Adapter, habe ich

setHasStableIds(true); 

In der Konstruktor. Ich overrode dann diese Methode:

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

und sorgte dafür, dass alle meine Artikel eine eindeutige ID zurück.

Wie Sie dies erreichen, liegt an Ihnen. Für mich wurde die Daten von meinem Web-Service in Form einer UUID geliefert und ich betrogen durch Teile des UUID zu lange mit dieser Umwandlung:

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

Offensichtlich ist dies nicht ein sehr sicherer Ansatz aber die Chancen ihm geben funktioniert für meine Listen, die < 1000 Elemente enthalten. Bisher habe ich damit keine Probleme bekommen.

Was ich empfehle ist, diesen Ansatz zu versuchen und zu sehen, ob es für Sie funktioniert.Da Sie ein Array haben, sollte eine eindeutige Nummer für Sie einfach sein. Vielleicht versuchen, die Position des tatsächlichen Einzelteil zurückbringen (und nicht die Position, die in der getItemId() abgelaufen ist) oder schaffen eine einzigartige lange für jedes Ihrer Aufzeichnungen und passieren, dass in.

+0

Der vorgestellte Code ist nur zu zeige das Problem an. Meine eigentliche Implementierung ist viel komplizierter (mit verschiedenen Arten der Sortierung und Filterung). Ich habe UUID zu (vielleicht wird Hashcode die Arbeit tun). Ich habe diese Optimierung gesehen, aber noch nicht ausprobiert. – gswierczynski

+0

Das gleiche für mich. Mein Adaptercode ist viel komplexer, aber ich hatte viele Probleme damit, Images in Recycler-Ansichten zu verhalten (asynchrones Laden über UniversalImageLoader). Das Einzige, was für mich funktionierte, war das, was ich geschrieben habe. – kha

+1

Danke für Ihre Eingabe @ kha. Leider löst das das Problem nicht. Ich habe Änderungen vorgenommen, die Sie vorgeschlagen haben. – gswierczynski

0

hier eine funktionierende Lösung hat aber Glitches Grafiken wenn notifyDataSetChanged() Aufruf

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

es, weil an diesem Punkt Bild arbeitet, hat eine Breite, leider, wenn ich alle Kontrollkästchen, die in der viewholder aktualisieren müssen (wie ein select alle Action), und ich rufe notifyDataSetChanged() und die Wirkung ist sehr hässlich

noch für eine bessere Lösung suchen

edit: diese lösung funktioniert für mich:

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

Lösung funktioniert, aber es bringt neue Bugs, also empfehle ich dies nicht. –

Verwandte Themen