android - make - Adaptador RecyclerView tomando valores erróneos
recyclerview android studio (5)
Esto suele suceder cuando tienes algo como "if (field! = Null) holder.setField (field)", sin otra cosa. El soporte se recicla, esto significa que tendrá valores allí, por lo que debe limpiar o reemplazar CADA valor, si es nulo debe anularse, si no lo es, debe escribirlo SIEMPRE. Es tarde, pero, como respuesta para otros.
Tengo un RecyclerView
que muestra dos tipos de View
una representa una publicación de Usuario y otra que representa una publicación de Evento. Ambos tienen elementos en común, por ejemplo, un TextView
que muestra una marca de tiempo. Así que creé un PublicationViewHolder
que toma esta TextView
tiempo de TextView
en una variable y la carga. Mi problema es que el adaptador, inicialmente, carga los valores correctos, pero cuando me desplazo hacia abajo y vuelvo hacia arriba, los valores en las posiciones se cambian por valores de otras posiciones. Aquí está el código:
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
};
}
Ahora haré mi UserPublicationViewHolder
para publicaciones de usuarios.
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.
}
}
Ahora haré lo mismo pero para las publicaciones del Evento.
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
}
}
Ahora aquí está mi adaptador 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();
}
}
No sé qué puede estar mal, UserPublication y EventPublication se extienden desde Publication. No estoy haciendo alguna solicitud o recargando el adaptador. Solo carga el adaptador una vez.
Actualizar:
Por cierto, estoy usando este RecyclerView dentro de un Fragmento que se carga en un PageAdapter que se carga en un ViewPager que está dentro de un Fragmento, ¿tal vez este sea el problema?
Actualización: Este es el otro código de enlace.
Este es el método de carga de 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);
}
}
}
Y este es el método de carga de 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());
}
Comenté algún código solo porque estaba probando, pero como puede ver, solo hago setTexts y cago algunas imágenes.
Y así es como configuro el adaptador, LinearLayoutManager, etc. En el método onViewCreated del fragmento.
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);
Por cierto, el adaptador está cargado con el conjunto de datos en un método personalizado que tengo, llamado onClpClientReady, pero este no parece ser el problema.
Aquí hay algunas capturas de pantalla:
Principio de la lista cuando entro en la aplicación por primera vez:
Entonces cuando vuelva
Por cierto, los botones "Me gusta", "no me gusta" y "favoritos", si alguien hizo clic en ellos más de una vez, mostrará un valor numérico, estos valores también se colocarán mal si están.
ACTUALIZACIÓN: Ahora sé que no fue porque los fragmentos anidados. Cambié mi código de la forma en que, ahora, cada fragmento de pestaña está en el PageStateAdapter que está dentro del ViewPager que está dentro de una actividad. Pero el problema sigue ahí.
ACTUALIZACIÓN: encontré que el método getItemId nunca se está ejecutando, IDK por qué todavía.
La única gran variable en su código de enlace está en su formato de fecha: DateFormatter.getTimeAgo(publication.getTimeStamp())
Sin ver esa clase directamente, es difícil decirlo con certeza, pero parece que, si la marca de tiempo es inmutable, pero el formateador se basa en la hora actual, eso sería consistente con el cambio de texto cuando la vista rebote.
Creo que un problema mayor (y un tanto aparte) es la legibilidad del código, lo que dificulta la detección visual del problema. El patrón de herencia y las sobrecargas aquí hacen que sea difícil razonar sobre el código y decidir qué ruta se está tomando y si está haciendo lo correcto. Aquí hay un código de servilleta (no lo construí ni lo ejecuté) usando un enfoque más compositivo que podría ser una organización más clara y facilitar la depuración de problemas:
Nueva clase auxiliar para el código de titular de vista común, reemplaza 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
como ejemplo (haga lo mismo para 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
}
}
Observe que no hay una clase base ahora en su adaptador, y tampoco hay necesidad de verificar los tipos, así que hay mucho menos código.
Ahora el adaptador sigue siendo el mismo con la excepción del tipo genérico y 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.
}
}
...
}
Tenga en cuenta que mantiene un patrón muy similar a su onCreateViewHolder
, por lo que no solo hay menos código general sino también más consistencia interna. Esta no es la única forma de hacerlo, solo una sugerencia basada en su caso de uso particular.
Para mí, configurar setHasStableIds(false)
resolvió el problema.
Tuvo el mismo problema con las imágenes cargadas asíncronas, que tenían diferentes alturas . Entonces, con el depurador, las posiciones para reciclar dependen del tamaño real de las vistas.
Una solución simple para mí fue especificar diferentes tamaños, por lo que el sistema conoce el tamaño exacto de todos los artículos. https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter.html#getItemViewType(int)
Por ejemplo paisaje, retrato y plaza.
Así que creé vistas separadas y las usé como: (simplificado)
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);
}
}
Finalmente, RecycleView no se confunde con los diferentes tamaños de elementos dinámicos.
Yo sugeriría revisar su jerarquía y uso de clase. En general, si está realizando una operación de type == type
en una clase base, está rechazando el propósito de la abstracción y la herencia. Algo como esto te funcionaría:
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()));
}
}
Ahora su "evento" o "publicaciones de usuario" simplemente derivan de esta clase e implementan el método constructor y bindViews()
. Asegúrese de llamar a la superclase en ambos casos. Además, asegúrese de configurar cada vista en el diseño para la publicación específica en sus métodos bindViews()
.
En su adaptador, solo necesita crear el titular correcto basado en el tipo de publicación en esa posición en su conjunto de datos:
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));
}
}