2015-01-19 21 views
18

Ich habe eine RecyclerView, die zwei Arten von View s zeigt, stellt eine Benutzerveröffentlichung dar und eine andere, die eine Ereignispublikation darstellt. Beide haben Elemente gemeinsam, zum Beispiel eine TextView, die einen Zeitstempel zeigt. Also habe ich einen PublicationViewHolder erstellt, der diesen TextView Zeitstempel in eine Variable aufnimmt und lädt. Mein Problem ist, dass der Adapter zunächst die richtigen Werte lädt, aber wenn ich nach unten scrolle und wieder nach oben scrolle, werden die Werte in den Positionen durch Werte von anderen Positionen geändert. Hier ist der Code:RecyclerView-Adapter falsche Werte

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

} 

Jetzt werde ich nur meine UserPublicationViewHolder für Benutzer Publikationen machen.

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. 
    } 
} 

Jetzt werde ich das Gleiche tun, sondern für die Event-Publikationen

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

Hier ist mein RecyclerView Adapter:

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

} 

Ich weiß nicht, was falsch sein kann, UserPublication und EventPublication erstreckt sich von der Veröffentlichung. Ich mache keine Anfrage oder lade den Adapter neu. Ich lade nur den Adapter einmal.

Update:

BTW verwende ich diese RecyclerView in einem Fragment wich in einem Pageadapter Wich geladen ist, wird in einem ViewPager geladen, die in einem Fragment ist, vielleicht ist dies das Problem?

Aktualisierung: Dies ist der andere Bindungscode.

Dies ist die Lastmethode des 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); 
     } 
    } 
} 

Und das ist die Load-Methode des 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()); 
} 

bemerkte ich einige Code, nur weil ich Tests war, aber wie man sehen kann, kann ich nur setTexts und einige Bilder assing.

Und so stelle ich den Adapter, LinearLayoutManager, etc. In der onViewCreated-Methode des Fragments.

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 ist der Adapter mit dem Datensatz in einer benutzerdefinierten Methode geladen, die ich habe, genannt onHttpClientReady, aber dies nicht scheint das Problem zu sein.

Hier sind einige Screenshots:

oben auf der Liste, wenn ich in der App für das erste Mal eingeben:

enter image description here

Dann, wenn ich zurückkomme: enter image description here

BTW die Like, Abneigung und Lieblings-Tasten, wenn jemand sie mehr als einmal geklickt hat, wird ein numerischer Wert angezeigt, diese Werte sind auch falsch platziert, wenn sie sind.

UPDATE: Jetzt weiß ich, dass war nicht, weil die verschachtelten Fragmente. Ich habe meinen Code so geändert, dass sich jetzt jedes Tab-Fragment im PageStateAdapter befindet, der sich im ViewPager innerhalb einer Aktivität befindet. Aber das Problem ist immer noch da.

UPDATE: ich, dass die GetItemID Methode gefunden wird nie ausgeführt wird, IDK, warum noch.

+0

Überprüfen Sie 'public void laden (Veröffentlichung Veröffentlichung, Int i)' - Sie nie verwenden i –

+0

Ich glaube nicht. Es stimmt, der Parameter int i ist nutzlos, mein Fehler. Die Veröffentlichungsinstanzen sind jedoch die richtigen, wie Sie in der onBindViewHolder-Methode sehen können. Ich habe meine Antwort aktualisiert, ich denke, dass das Thema von dieser Seite kommt. Ich habe sogar versucht, alle diese Parameter ohne Glück als Finale zu setzen. – 4gus71n

+0

wenn Benutzer klicken auf wie erhalten Antwort mit aktualisierten Likes zählen, während die Ansicht auf diese Position ist nicht auf die richtige Ansicht aktualisiert Android – Harsha

Antwort

3

Ich würde vorschlagen, Ihre Klassenhierarchie und Verwendung zu überprüfen. Wenn Sie eine type == type Art von Operation in einer Basisklasse durchführen, dann vereiteln Sie im Allgemeinen den Zweck der Abstraktion und Vererbung. So etwas wie dies würde für Sie arbeiten:

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

jetzt Ihr „Ereignis“ oder „user Publikationen“ einfach von dieser Klasse ableiten und den Konstruktor und bindViews() Methode implementieren. Achten Sie darauf, in beiden Fällen zur übergeordneten Klasse zu wechseln. Vergewissern Sie sich auch, dass Sie alle Ansicht im Layout für die spezifische Veröffentlichung in Ihrer bindViews() Methoden festlegen.an dieser Position in Ihrem Datensatz

In Ihrem Adapter, müssen Sie nur auf der Grundlage der Publikation Art den richtigen Halter erstellen:

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

Danke, ich weiß, dass mein Code ziemlich unordentlich ist, der Refactor, den du vorschlägst, ist wirklich gut, aber das Hauptproblem ist, dass aus irgendeinem Grund, wenn ich mit dem 'RecyclerView' nach unten scrolle, und die Daten erneut angezeigt werden in jedem RecyclerView-Element ändert sich für einige andere Daten von einem anderen Element einer anderen Position. Ich kann nicht sehen, wie dieser Refactor mir mit meinem Problem helfen kann. Wie ich in meinem Update gesagt habe, denke ich, dass dieses Problem an den verschachtelten Fragmenten liegt. Sind Sie einverstanden? – 4gus71n

+0

Nein, es sind nicht die verschachtelten Fragmente. Es ist höchstwahrscheinlich die Art, wie die Bindung gemacht wird. Wenn Sie dies sehen, liegt das normalerweise daran, dass die Ansichten wiederverwendet werden und die Bindeoperation nicht alle Ansichten in der Hierarchie auf einen Zustand setzt, der den Daten entspricht. –

+0

Starten Sie Ihren refactor small: Führen Sie einfach die "Benutzerveröffentlichungen" gemäß dem oben beschriebenen Muster durch. Sobald Sie das richtig verstanden haben, fügen Sie den anderen Publikationstyp hinzu und verifizieren, dass alles gut funktioniert. –

0

Die eine große Variable in Ihrem verbindlichen Code ist bei der Datumsformatierung: DateFormatter.getTimeAgo(publication.getTimeStamp())

Ohne diese Klasse direkt zu sehen, es ist schwer, sicher zu sagen, aber es scheint, wie, wenn der Zeitstempel unveränderlich ist, sondern die Forma basiert auf der aktuellen Zeit, dann wäre das konsistent mit dem Text, der sich ändert, wenn die Ansicht wieder zurückspringt.

Ich denke, ein größeres Problem (und etwas daneben) ist die Lesbarkeit des Codes, die es schwierig macht, das Problem visuell leicht zu erkennen. Das Vererbungsmuster und die Überladungen hier machen es schwer, über den Code nachzudenken und zu entscheiden, welcher Pfad genommen wird und ob es das Richtige tut. Hier einige Serviette Code (habe es nicht gebaut oder es laufen) mit einem mehr kompositorischen Ansatz, der eine klarere Organisation sein könnte und machen es leichter zu debuggen Probleme:

Neue Hilfsklasse für die gemeinsame Ansicht Halter Code ersetzt 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 als Beispiel (für UserPublicationViewHolder das gleiche tun):

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

Hinweis gibt es keine Basisklasse in ihrem Adapter, und auch keine Notwendigkeit für die Typprüfung, so a lo es t weniger Code.

nun der Adapter bleibt gleich, mit Ausnahme der generischen Art und 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. 
     } 
    } 
    ... 
} 

Hinweises es ein sehr ähnliches Muster auf Ihre onCreateViewHolder hält, so ist es nicht nur insgesamt weniger Code, sondern auch interne Konsistenz . Dies ist sicherlich nicht die einzige Möglichkeit, dies zu tun, nur ein Vorschlag basierend auf Ihrem speziellen Anwendungsfall.

+0

Nizza refactor, aber der Zeitstempel ist nicht die einzige Daten, die ich bin verbindlich. In der "load" -Methode des 'UserPublicationViewHolder' binde ich die Publikations-Likes-Menge, Abneigungen, Favoriten usw. fest. All diese Felder, all diese Daten, sind völlig durcheinander, wenn ich mit dem' RecyclerView' nach unten scrolle. Glaubst du, dass das vielleicht an den verschachtelten Fragmenten liegt? Wenn ich das Fragment hinzufüge, sind alle Daten in Ordnung, alles sieht gut aus. Wenn ich dann nach unten scrolle und zum Anfang des Recyclers zurückkehre, sind alle Daten durcheinander. – 4gus71n

+0

Es wäre dann hilfreich, den anderen Bindungscode zu sehen. Aus der ursprünglichen Frage sah es so aus, als ob nur die eine Textansicht gesetzt wurde. Das könnte Hinweise geben. Können Sie auch den Snippet posten, den Sie verwenden, um den Layout-Manager und den Adapter auf dem RV festzulegen? – lopar

+0

Ich habe meinen Code aktualisiert. – 4gus71n

56

Dies geschieht in der Regel, wenn Sie so etwas wie „if (Feld haben = null) holder.setField (field) ", ohne sonst. Der Halter wird recycelt, das bedeutet, dass er dort Werte hat, also musst du JEDEN Wert reinigen oder ersetzen, wenn es Null ist solltest du nullit werden, wenn nicht, solltest du es IMMER schreiben. Es ist spät, aber als Antwort für andere.

+0

Danke! Sie haben meinen Tag gemacht!) – Yazon2006

+3

Dies sollte definitiv die akzeptierte Antwort sein.Ich war mir nicht bewusst, dass das überhaupt eine Sache war und jetzt, wo ich mein Projekt ansehe, erklärt dies eine Menge kleiner Fehler, die ich nicht ohne weiteres replizieren konnte. Danke für den Tipp Ivan – Silmarilos

+1

Das ist es. Im Grunde hatte ich einen else Block. Ich hatte zwei Arten von Ansichten, die beide die gleichen Layouts und Elemente haben. Der if-Block blendet ein Element aus, das standardmäßig auf "visible" gesetzt ist. Der Else-Block füllt einfach das Element, das ich bereits gezeigt habe. Nach dieser Antwort sollte ich das Element im else-Block wieder auf "visible" setzen, obwohl es im XML bereits "sichtbar" ist. Und es hat funktioniert, alles hat gut funktioniert, auch nach dem Recycling. – SergeantPeauts

1

Hatte das gleiche Problem mit async geladenen Bildern, die unterschiedliche Höhen hatten. Mit dem Debugger können Sie also sehen, dass Positionen für das Recycling von der tatsächlichen Größe der Ansichten abhängen.

Einfache Lösung für mich war, verschiedene Größen anzugeben, so dass System die genaue Größe aller Elemente kennt. https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter.html#getItemViewType(int)

Zum Beispiel Landschaft, Porträt und Quadrat. So

Ich habe verschiedene Ansichten und verwendet sie mögen: (vereinfacht)

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

Schließlich RecycleView nicht verwirrt über die verschiedenen/dynamische Element Größen.

4

für mich Einstellung setHasStableIds(false) löste das Problem.

+0

setHasStatbleIds (falsch); , ist richtig . –

+0

@Sheagorath es funktioniert nicht für mich – Erum