2015-01-19 14 views
18

Ho un RecyclerView che mostra due tipi di View s uno rappresenta una pubblicazione Utente e un altro che rappresenta una pubblicazione di evento. Entrambi hanno elementi in comune, ad esempio uno TextView che mostra un timestamp. Così ho creato un PublicationViewHolder che prende questo timestamp TextView in una variabile e lo carica. Il mio problema è che l'adattatore, inizialmente, carica i valori corretti, ma quando si scorre verso il basso e si scorre di nuovo, i valori nelle posizioni vengono modificati dai valori di altre posizioni. Ecco il codice:Adattatore RecyclerView che assume valori errati

public class PublicationViewHolder extends RecyclerView.ViewHolder { 

    private TextView vTimeStamp; 

    public PublicationViewHolder(View itemView) { 
     super(itemView); 
     this.vTimeStamp = (TextView) itemView.findViewById(R.id.txt_view_publication_timestamp); 
    } 

    public void load(Publication publication, int i) { 
     load(publication); 
     try { 
      if (Publication.TYPE_USER_PUBLICATION == publication.getType()) { 
       load((UserPublication) publication); 
      } else if (Publication.TYPE_EVENT_PUBLICATION == publication.getType()) { 
       load((EventPublication) publication); 
      } 
     } catch (ClassCastException e) { 
      throw new RuntimeException("Publication type cast fail. See PublicationViewHolder."); 
     } 
    } 

    public void load(Publication publication) { 
     vTimeStamp.setText(DateFormatter.getTimeAgo(publication.getTimeStamp())); 
    } 

    public void load(UserPublication publication) { 
     //This method is override by UserPublicationViewHolder 
    }; 

    public void load(EventPublication publication) { 
     //This method is override by EventPublicationViewHolder 
    }; 

} 

Ora farò la mia UserPublicationViewHolder solo per le pubblicazioni degli utenti.

public class UserPublicationViewHolder extends PublicationViewHolder { 
    private ImageView vImageView, vLikeButton, vDislikeButton, vFavButton, vEditPost, vDeletePost; 
    private TextView vText, vUsername, vLikeCount, vDislikeCount, vFavCount; 
    private PostImagesLayout vImagesContainer; 
    private TagCloudLocationFriends tagsView; 

    public UserPublicationViewHolder(View itemView) { 
     super(itemView); 
     vImageView = (ImageView) itemView.findViewById(R.id.img_view_publication_user); 
     vText = (TextView) itemView.findViewById(R.id.txt_view_publication_text); 

     vLikeCount = (TextView) itemView.findViewById(R.id.txt_view_like_count); 
     vFavCount = (TextView) itemView.findViewById(R.id.txt_view_fav_count); 
     vDislikeCount = (TextView) itemView.findViewById(R.id.txt_view_dislike_count); 

     vUsername = (TextView) itemView.findViewById(R.id.txt_view_publication_user_name); 
     vLikeButton = (ImageView) itemView.findViewById(R.id.img_view_like); 
     vDislikeButton = (ImageView) itemView.findViewById(R.id.img_view_dislike); 
     vFavButton = (ImageView) itemView.findViewById(R.id.img_view_fav); 
     vImagesContainer = (PostImagesLayout) itemView.findViewById(R.id.container_post_images); 

     tagsView = (TagCloudLocationFriends) itemView.findViewById(R.id.location_friends_tag); 

     // edit - remove icons 
     vDeletePost = (ImageView) itemView.findViewById(R.id.img_view_delete_post); 
     vEditPost = (ImageView) itemView.findViewById(R.id.img_view_edit_post); 
    } 


    @Override 
    public void load(UserPublication publication) { 
     //Load the UserPublicationViewHolder specific views. 
    } 
} 

Ora io farò lo stesso, ma per le pubblicazioni di eventi

public class EventPublicationViewHolder extends PublicationViewHolder { 

    private TextView vTextViewTitle; 
    private TextView vTextViewText; 

    public EventPublicationViewHolder(View itemView) { 
     super(itemView); 
     vTextViewTitle = (TextView) itemView.findViewById(R.id.txt_view_publication_event_title); 
     vTextViewText = (TextView) itemView.findViewById(R.id.txt_view_publication_event_text); 
    } 

    @Override 
    public void load(EventPublication publication) { 
     //Load the EventPublicationViewHolder specifics views 
    } 
} 

Ora qui è il mio adattatore RecyclerView:

public class PublicationAdapter extends RecyclerView.Adapter<PublicationViewHolder> { 

    public static final int USER_PUBLICATION_TYPE = 1; 
    public static final int EVENT_PUBLICATION_TYPE = 2; 
    private List<Publication> publications = new ArrayList<Publication>(); 

    public List<Publication> getPublications() { 
     return publications; 
    } 

    public void setPublications(List<Publication> publications) { 
     this.publications = publications; 
    } 

    @Override 
    public int getItemViewType(int position) { 
     if (publications.get(position) instanceof UserPublication) { 
      return USER_PUBLICATION_TYPE; 
     } 
     if (publications.get(position) instanceof EventPublication) { 
      return EVENT_PUBLICATION_TYPE; 
     } 
     throw new RuntimeException("Unknown view type in PublicationAdapter"); 
    } 

    @Override 
    public PublicationViewHolder onCreateViewHolder(ViewGroup viewGroup, int type) { 
     View v; 
     switch (type) { 
      case USER_PUBLICATION_TYPE: 
       v = LayoutInflater.from(getActivity()).inflate(R.layout.view_holder_user_publication, viewGroup, false); 
       return new UserPublicationViewHolder(v); 
      case EVENT_PUBLICATION_TYPE: 
       v = LayoutInflater.from(getActivity()).inflate(R.layout.view_holder_event_publication, viewGroup, false); 
       return new EventPublicationViewHolder(v); 
     } 
     return null; 
    } 

    @Override 
    public void onBindViewHolder(PublicationViewHolder aPublicationHolder, int i) { 
     aPublicationHolder.load(publications.get(i), i); 
    } 

    @Override 
    public long getItemId(int position) { 
     //Here I tried returning only position or 0 without luck. 
     //The id is unique BTW 
     return publications.get(position).getId(); 
    } 

    @Override 
    public int getItemCount() { 
     return publications.size(); 
    } 

} 

Non so cosa può essere sbagliato, UserPublication e EventPublication si estendono entrambi dalla pubblicazione. Non sto facendo alcuna richiesta o ricarico l'adattatore. Carico l'adattatore solo una volta.

Aggiornamento:

BTW sto usando questo RecyclerView all'interno di un frammento wich viene caricato in un wich PageAdapter viene caricato in una ViewPager che si trova all'interno di un frammento, forse è questo il problema?

Aggiornamento: Questo è l'altro codice vincolante.

Questo è il metodo di caricamento di UserPublicationViewHolder.

@Override 
    public void load(UserPublication publication) { 
     PicassoHelper.publicationUser(getActivity(), publication.getUser().getAvatarUrl(), 
       vImageView); 
     vText.setText(publication.getText()); 
     vUsername.setText(publication.getUser().getName()); 
     boolean hasLocation = false; 
     if (publication.getImages().length > 0) { 
      vImagesContainer.setImages(publication.getImages()); 
     } else { 
      vImagesContainer.setVisibility(View.GONE); 
     } 
     tagsView.setTags(new ArrayList<MinikastTag>()); 
     tagsView.drawTags(); 

     if(publication.getLocation() != null || publication.getTaggedFriends().size() > 0){ 
      if(publication.getLocation() != null){ 
       hasLocation = true; 
       tagsView.add(new MinikastTag(1,"Post from ",1)); 
       tagsView.add(new MinikastTag(2, publication.getLocation().getName(), 2)); 
      } 
      if(publication.getTaggedFriends().size() > 0){ 
       if(hasLocation) 
        tagsView.add(new MinikastTag(3," with ",1)); 
       else 
        tagsView.add(new MinikastTag(3,"With ",1)); 

       int i = 0; 
       for(User aUser: publication.getTaggedFriends()){ 
        MinikastTag aTag; 
        if(i == publication.getTaggedFriends().size() - 1) { 
         aTag = new MinikastTag(4, aUser.getName(), 3); 
         aTag.setUserID(aUser.getId()); 
         aTag.setUserName(aUser.getName()); 
         tagsView.add(aTag); 
        } else { 
         aTag = new MinikastTag(4, aUser.getName() + ", ", 3); 
         aTag.setUserID(aUser.getId()); 
         aTag.setUserName(aUser.getName()); 
         tagsView.add(aTag); 
        } 
        i = i+1; 
       } 
      } 
     } 
     tagsView.drawTags(); 

     // likes, dislikes, favs 
     if(publication.getLikesAmount() > 0) 
      vLikeCount.setText(String.valueOf(publication.getLikesAmount())); 

     if(publication.getDislikesAmount() > 0) 
      vDislikeCount.setText(String.valueOf(publication.getDislikesAmount())); 

     if(publication.getLovesAmount() > 0) 
      vFavCount.setText(String.valueOf(publication.getLovesAmount())); 

     // reset buttons 
     vFavButton.setPressed(false); 
     vDislikeButton.setPressed(false); 
     vLikeButton.setPressed(false); 

     if(publication.getRelationship().equals("LOVE")) 
      vFavButton.setPressed(true); 
     else if (publication.getRelationship().equals("LIKE")) 
      vLikeButton.setPressed(true); 
     else if (publication.getRelationship().equals("DISLIKE")) 
      vDislikeButton.setPressed(true); 

     // edit - remove icons 

     if(String.valueOf(publication.getUser().getId()).equals(StartupSharedPreferences.getProfileId())){ 
      vEditPost.setVisibility(View.VISIBLE); 
      vDeletePost.setVisibility(View.VISIBLE); 
     }else{ 
      vEditPost.setVisibility(View.INVISIBLE); 
      vDeletePost.setVisibility(View.INVISIBLE); 
     } 
    } 
} 

E questo è il metodo di carico del EventPublicationViewHolder:

@Override 
public void load(EventPublication publication) { 
    vTimeStamp.setVisibility(View.GONE); 
    itemView.setOnClickListener(new View.OnClickListener() { 
     @Override 
     public void onClick(View view) { 
      //GoTo.eventDetail(getActivity(), publication); 
     } 
    }); 
    vTextViewTitle.setText(publication.getTitle()); 
    vTextViewText.setText(publication.getText()); 
} 

ho commentato qualche codice solo perché ero di prova, ma come si può vedere, faccio solo setTexts e assing alcune immagini.

E così ho impostato l'adattatore, LinearLayoutManager, ecc. Nel metodo onViewCreated del frammento.

vRecyclerView = (FixedRecyclerView) view.findViewById(R.id.recycler_view_publications); 
     vSwipeRefresh = (SwipeRefreshLayout) view.findViewById(R.id.swipe_container); 
     mFeedCallback.onScrollReady(vRecyclerView); 
     mLayoutManager = buildLayoutManager(); 
     vRecyclerView.setLayoutManager(mLayoutManager); 
     vRecyclerView.addItemDecoration(new DividerItemDecoration(getActivity(), DividerItemDecoration.VERTICAL_LIST)); 
     mAdapter = new PublicationAdapter(); 
     vSwipeRefresh.setOnRefreshListener(this); 
     vSwipeRefresh.setColorSchemeResources(R.color._SWIPER_COLOR_1, R.color._SWIPER_COLOR_2, 
       R.color._SWIPER_COLOR_3, R.color._SWIPER_COLOR_4); 
     vRecyclerView.setAdapter(mAdapter); 

BTW L'adattatore viene caricato con il set di dati in un metodo personalizzato che ho, chiamato onHttpClientReady, ma questo doesnt sembra essere il problema.

Ecco alcuni screenshot:

In cima alla lista quando entro in app per la prima volta:

enter image description here

Poi quando torno: enter image description here

BTW il come, non mi piace e pulsanti preferiti, se qualcuno ha fatto clic su di loro più di una volta, visualizzerà un valore numerico, questi valori sono anche missplaced se lo sono.

UPDATE: Ora so che non era perché i frammenti nidificati. Ho cambiato il mio codice nel modo in cui, ora, ogni frammento di scheda si trova in PageStateAdapter che si trova all'interno di ViewPager all'interno di un'attività. Ma il problema è ancora lì.

AGGIORNAMENTO: Ho trovato che il metodo getItemId non viene mai eseguito, IDK perché ancora.

+0

Controllare 'public void load (Pubblicazione pubblicazione, int i)' - non si usa mai i –

+0

Non credo. È vero, il parametro int i è inutile, il mio male. Ma le istanze di pubblicazione sono effettivamente quelle giuste, come puoi vedere nel metodo onBindViewHolder. Ho aggiornato la mia risposta, penso che il problema arrivi da quella parte. Ho anche provato a mettere tutti quei parametri come definitivi senza fortuna. – 4gus71n

+0

se l'utente fa clic su come sto ricevendo risposta con conteggi di Mi piace aggiornati mentre la vista impostata su quella posizione non viene aggiornata sulla vista corretta android – Harsha

risposta

3

Suggerirei di rivedere la gerarchia delle classi e l'utilizzo. In generale, se si sta facendo un tipo di operazione type == type in una classe base, si sta vanificando lo scopo dell'astrazione e dell'ereditarietà. Qualcosa di simile a questo dovrebbe funzionare per voi:

public abstract class PublicationViewHolder extends RecyclerView.ViewHolder { 
    private TextView mTimeStamp; 

    public PublicationViewHolder(View itemView) { 
     mTimeStamp = (TextView)itemView.findViewById(R.id. txt_view_publication_timestamp); 
    } 

    public void bindViews(Publication publication) { 
     mTimeStamp.setText(DateFormatter.getTimeAgo(publication.getTimeStamp())); 
    } 
} 

Ora il vostro "evento" o "pubblicazioni utente" semplicemente derivano da questa classe e implementare il costruttore e bindViews() metodo. Assicurarsi di chiamare la superclasse in entrambi i casi. Inoltre, assicurarsi di impostare ogni visualizzazione nel layout per la pubblicazione specifica nei metodi bindViews().

l'adattatore, è solo bisogno di creare il supporto corretta in base al tipo di pubblicazione in quella posizione nel set di dati:!

public class PublicationAdapter extends RecyclerView.Adapter { 
    private ArrayList<Publication> mPubs; 

    // Your other code here, like 
    // swapPublications(), getItemCount(), etc. 
    ... 

    public int getItemViewType(int position) { 
     return mPubs.get(position).getType(); 
    } 

    public PublicationViewHolder createViewHolder(ViewGroup parent, int type) { 
     PublicationViewHolder ret; 
     View root; 
     LayoutInflater inflater = LayoutInflater.from(parent.getContext()); 

     if (type == USER_PUBLICATION_TYPE) { 
      root = 
       inflater.inflate(R.layout.view_holder_user_publication, 
        parent, 
        false); 

      ret = new UserPubHolder(root); 
     } else { 
      root = 
       inflater.inflate(R.layout.view_holder_event_publication, 
        parent, 
        false); 

      ret = new EventPubHolder(root); 
     } 

     return ret; 
    } 

    public bindViewHolder(PublicationViewHolder holder, int position) { 
     holder.bindViews(mPubs.get(position)); 
    } 
} 
+0

Grazie, so che il mio codice è abbastanza disordinato, il refactoring che tu proponi è veramente buono, ma il problema principale è che, per qualche ragione, quando scorro verso il basso con 'RecyclerView', e risale, i dati visualizzati in ogni elemento di RecyclerView cambia per alcuni altri dati da un altro elemento di un'altra posizione. Non riesco a vedere come questo refactator possa aiutarmi con il mio problema. Come ho detto nel mio aggiornamento, penso che questo problema sia dovuto ai frammenti nidificati. Sei d'accordo? – 4gus71n

+0

No, non sono i frammenti nidificati. È molto probabilmente il modo in cui viene eseguito il binding. Di solito quando si vede questo è perché le viste vengono riciclate e l'operazione di bind non sta impostando tutte le viste nella gerarchia in uno stato che corrisponde ai dati. –

+0

Iniziare il refettatore di piccole dimensioni: fai solo le "pubblicazioni degli utenti" seguendo lo schema che ho descritto sopra. Una volta ottenuto questo, aggiungi l'altro tipo di pubblicazione e verifica che tutto funzioni correttamente. –

0

L'unica grande variabile nel codice vincolante è la data di formattazione: DateFormatter.getTimeAgo(publication.getTimeStamp())

Senza vedere che gran classe è difficile dire con certezza, ma sembra che, se il timestamp è immutabile, ma il formattatore si basa sull'ora corrente, quindi sarebbe coerente con il testo che cambia quando la vista è in rimbalzo.

Penso che un problema più grande (e in parte una parte) sia la leggibilità del codice, che rende difficile individuare il problema visivamente. Il modello di ereditarietà e i sovraccarichi qui rendono difficile ragionare sul codice e decidere quale percorso viene intrapreso e se sta facendo la cosa giusta. Ecco un po 'di codice tovagliolo (non hanno costruito o eseguirlo) utilizzando un approccio più compositivo che potrebbe essere un'organizzazione chiara e rendere più facile per i problemi di debug:

nuova classe di supporto per la vista comune Codice titolare, sostituisce PublicationViewHolder:

public class PublicationViewHolderHelper { 
    private final TextView vTimeStamp; 

    public PublicationViewHolder(View itemView) { 
     super(itemView); 
     this.vTimeStamp = (TextView) itemView.findViewById(R.id.txt_view_publication_timestamp); 
    } 

    /** Binds view data common to publication types. */ 
    public void load(Publication publication) { 
     vTimeStamp.setText(DateFormatter.getTimeAgo(publication.getTimeStamp())); 
    } 
} 

EventPublicationViewHolder come esempio (fare la stessa cosa per UserPublicationViewHolder):

public class EventPublicationViewHolder extends ViewHolder { 
    private final PublicationViewHolderHelper helper; 

    // View fields... 

    public EventPublicationViewHolder(View itemView) { 
     super(itemView); 
     helper = new PublicationViewHolderHelper(itemView); 
     // Populated view fields... 
    } 

    @Override 
    public void load(EventPublication publication) { 
     helper.load(publication); 
     //Load the EventPublicationViewHolder specifics views 
    } 
} 

Avviso non c'è classe di base ora nel tuo adattatore, e anche senza bisogno di controllo di tipo, quindi c'è una lo t meno codice.

Ora l'adattatore rimane lo stesso con l'eccezione del tipo generico e onBindViewHolder:

public class PublicationAdapter extends RecyclerView.Adapter<ViewHolder> { 
    ... 
    @Override 
    public void onBindViewHolder(ViewHolder viewHolder, int position) { 
     final Publication publication = publications.get(position); 
     final int viewType = getItemViewType(position); 
     switch (viewType) { 
      case USER_PUBLICATION_TYPE: 
       ((UserPublicationViewHolder) viewHolder).load((UserPublication) publication); 
       break; 
      case EVENT_PUBLICATION_TYPE: 
       ((EventPublicationViewHolder) viewHolder).load((EventPublication) publication); 
       break; 
      default: 
       // Blow up in whatever way you choose. 
     } 
    } 
    ... 
} 

Avviso mantiene un modello molto simile al vostro onCreateViewHolder, quindi non c'è non solo il codice meno complesso, ma anche consistenza più interna . Questo certamente non è l'unico modo per farlo, solo un suggerimento basato sul tuo particolare caso d'uso.

+0

Nice refactor, ma il timestamp non è l'unico dato che sto vincolando. Nel metodo 'load' di' UserPublicationViewHolder' lego la pubblicazione a Mi piace di quantità, non mi piace, preferiti, ecc. Tutti questi campi, tutti questi dati, sono completamente confusi quando scorro verso il basso con 'RecyclerView'. Pensi che forse è perché i frammenti nidificati? Quando aggiungo il frammento tutti i dati vanno bene, tutto sembra a posto, poi, quando scorro verso il basso e torno in cima al Recycler, tutti i dati sono incasinati. – 4gus71n

+0

Sarebbe utile quindi vedere l'altro codice vincolante. Dalla domanda iniziale sembrava che fosse impostata solo la vista del testo. Questo potrebbe fornire alcuni suggerimenti. Inoltre, puoi pubblicare lo snippet che stai utilizzando per impostare il gestore di layout e l'adattatore sul camper? – lopar

+0

Ho aggiornato il mio codice. – 4gus71n

56

Questo di solito succede quando si ha qualcosa come "se (campo = null) holder.setField (campo) ", senza altro. Il titolare viene riciclato, questo significa che avrà dei valori lì, quindi è necessario pulire o sostituire ogni valore, se è nullo dovresti annullare, se non lo è, dovresti scriverlo SEMPRE. È tardi, ma, come risposta per gli altri.

+0

Grazie! Hai fatto la mia giornata!) – Yazon2006

+3

Questa dovrebbe essere la risposta accettata.Ero inconsapevole che questa era una cosa e ora che guardo il mio progetto, questo spiega un sacco di piccoli bug che non ero in grado di replicare facilmente. Grazie per il suggerimento Ivan – Silmarilos

+1

Questo è tutto. Fondamentalmente ho avuto un altro blocco. Avevo 2 tipi di vista, entrambi condividono gli stessi layout ed elementi. Il blocco if nasconde un elemento che è impostato su 'visible' di default. Il blocco else semplicemente riempie l'elemento che stavo già mostrando. Secondo questa risposta dovrei impostare l'elemento su 'visible' di nuovo nel blocco else, nonostante sia già visibile in XML. E ha funzionato, tutto caricato bene, anche dopo essere stato riciclato. – SergeantPeauts

1

Aveva lo stesso problema con immagini caricate asincroni, che avevano altezze diverse. Quindi, con il debugger, è possibile notare che le posizioni per il riciclo dipendono dalla dimensione effettiva delle visualizzazioni.

La soluzione semplice per me era specificare dimensioni diverse, in modo che il sistema conosca le dimensioni esatte di tutti gli articoli. https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter.html#getItemViewType(int)

Ad esempio orizzontale, verticale e quadrato.

Così ho creato viste separate e utilizzati come loro: (semplificato)

public class YourAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { 
    // ... 
    public static class ViewHolderLandscape extends RecyclerView.ViewHolder { ... } 
    public static class ViewHolderPortrait extends RecyclerView.ViewHolder { ... } 
    public static class ViewHolderSquare extends RecyclerView.ViewHolder { ... } 

    @Override 
    public int getItemViewType(int position) {  
    return mDataset.get(position).getImageType(); 
    } 

    @Override 
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 
    int mLayoutId = 0; 

    switch (viewType) { 
     case 0: 
      mLayoutId = R.layout.list_item_landscape; 
      break; 
     case 1: 
      mLayoutId = R.layout.list_item_portrait; 
      break; 
     case 2: 
      mLayoutId = R.layout.list_item_square; 
      break; 
    } 

    View v = LayoutInflater.from(parent.getContext()).inflate(mLayoutId, parent, false);   
    ButterKnife.inject(this, v); 

    return new ViewHolder(v); 
    } 
} 

Infine RecycleView non confondersi su diverse dimensioni degli elementi/dinamici.

4

per me impostare setHasStableIds(false) risolto il problema.

+0

setHasStatbleIds (false); , è corretta . –

+0

@sheagorath non funziona per me – Erum

Problemi correlati