2015-03-27 16 views
11

Aktualizacja # 1RecyclerView z GridLayoutManager i Picasso pokazując niewłaściwe zdjęcie

Dodany hasStableIds (prawda) i zaktualizowane do wersji 2.5.2 Picasso. To nie rozwiązuje problemu.

reprodukcji:

RecyclerView z GridLayoutManager (spanCount = 3). Elementy listy to CardView z obrazem w środku.

Gdy wszystkie elementy nie pasują do ekranu wywoływanie notifyItemChanged na jednym elemencie powoduje więcej niż jedno wywołanie onBindViewHolder(). Jedno połączenie dotyczy pozycji z notifyItemChanged dla elementów niewidocznych na ekranie.

Problem:

Czasami pozycja w pozycji przekazywane do notifyItemChanged jest załadowany obraz należący do przedmiotu, który nie znajduje się na ekranie (najprawdopodobniej z powodu recyklingiem posiadaczowi widzenia - choć chciałbym załóżmy, że jeśli przedmiot pozostanie na miejscu, przekazany obiekt będzie taki sam).

Znalazłem komentarz Jake'a w innym wydaniu o wywołaniu load(), nawet jeśli plik/uri ma wartość null. Obraz jest ładowany na wszystkie tuBindViewHolder tutaj.

Prosta aplikacja próbka:

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

Stuknij w połączeniach elementów notifyItemChanged z parametru równej pozycji tego elementu.

Kod:

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

znalazłeś rozwiązanie? Mam ten sam problem. Czy dzieje się tak tylko z RecyclerView? Czy próbowałeś z ListView? –

+0

Jeszcze nie. Ponieważ nie jestem pewien, czy jest to problem z Picasso lub GridLayoutManager, opublikowaliśmy problemy na obu stronach projektu PIITASS GITLUB (https://github.com/square/picasso/issues/954) i AOSP Google Code (https: // code.google.com/p/android/issues/detail?id=162699). Nie sądzę, że ten problem istnieje na ListView. – gswierczynski

+0

Czy rozwiązałeś ten problem, nadal widzę, że ten problem istnieje w com.squareup.picasso: picasso: 2.5.2 setSupportsChangeAnimations (false) i setHasStableIds (true) wydają się temu zapobiegać – Harkish

Odpowiedz

4

spędziłem więcej czasu niż chciałbym dopuścić do obejścia osobliwości z RecyclerView i nowej karty, które mu towarzyszy. Jedyną rzeczą, która ostatecznie pracował dla mnie pod względem prawidłowych aktualizacjach i upewniając notifyDataSetChanges i wszystkie jego rodzeństwa nie powodować dziwne zachowanie było to:

Na moim adaptera ustawić

setHasStableIds(true); 

W Konstruktor. Następnie przejąłem tę metodę:

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

Upewniłem się, że wszystkie moje produkty zwróciły niepowtarzalny identyfikator.

Sposób, w jaki to osiągasz, należy do Ciebie. Dla mnie dane zostały dostarczone z mojego serwisu WWW w postaci UUID i oszukany przez konwersję części UUID tęsknić za pomocą tego:

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

Oczywiście nie jest to bardzo bezpieczne podejście, ale są szanse, że działa na moich listach, które będą zawierać < 1000 przedmiotów. Do tej pory nie miałem z tym żadnego problemu.

Zalecam wypróbowanie tego podejścia i sprawdzenie, czy to działa.Ponieważ macie tablicę, uzyskanie unikalnego numeru powinno być proste. Może spróbuj powrocie pozycji rzeczywistej pozycji (a nie pozycji, która jest przekazywana w getItemId()) lub stworzyć niepowtarzalny długo dla każdego z rekordów i przekazać, że w.

+0

Prezentowany kod jest po prostu pokaż problem. Moja implementacja jest bardziej skomplikowana (z różnymi sposobami sortowania i filtrowania). Mam UUID do (może hashcode wykona zadanie). Widziałem tę optymalizację, ale jeszcze jej nie wypróbowałem. – gswierczynski

+0

To samo dla mnie. Mój kod adaptera jest znacznie bardziej złożony, ale miałem wiele problemów z uzyskiwaniem obrazów, które zachowują się w widokach recyklerów (ładowanie przez UniversalImageLoader asynchronicznie). Jedyne, co zadziałało, to to, co napisałem. – kha

+1

Dziękujemy za Twój wkład @kha. Niestety to nie rozwiązuje problemu. Popełniłem zmiany, które zaproponowałeś. – gswierczynski

0

tutaj rozwiązanie działa, ale ma usterki graficzne Dzwoniąc 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); 
      }); 

to działa, ponieważ w tym momencie obraz ma szerokość, niestety kiedy trzeba aktualizować wszystkie pola te w viewholder (jak select wszystkie działania), i wzywam notifyDataSetChanged() a efekt jest bardzo brzydki

wciąż szukając lepszego rozwiązania

edit: to rozwiązanie działa dla mnie:

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

Rozwiązanie działa, ale przynosi nowe błędy, więc nie polecam tego. –

Powiązane problemy