J'utilise RecyclerView
pour afficher le nom des éléments. Ma ligne contient single TextView
. Les noms d'éléments sont stockés dans List<String> mItemList
.
Pour changer le contenu de RecyclerView
, je remplace Strings dans mItemList
et appelle notifyDataSetChanged
() à RecyclerViewAdapter
.
Mais si j'essaie de changer le contenu de la mItemList
pendant que RecyclerView défile, cela me donne parfois Java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid item position 157(offset:157).state:588
Cela se produit si la taille de mItemList
est inférieure à celle d’avant. Alors, quelle est la bonne façon de changer le contenu de la RecyclerView
? Est-ce un bug dans RecyclerView
?
Voici la trace complète de la pile de l'exception:
Java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid item position 157(offset:157).state:588
at Android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.Java:3300)
at Android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.Java:3258)
at Android.support.v7.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.Java:1803)
at Android.support.v7.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.Java:1302)
at Android.support.v7.widget.LinearLayoutManager.fill(LinearLayoutManager.Java:1265)
at Android.support.v7.widget.LinearLayoutManager.scrollBy(LinearLayoutManager.Java:1093)
at Android.support.v7.widget.LinearLayoutManager.scrollVerticallyBy(LinearLayoutManager.Java:956)
at Android.support.v7.widget.RecyclerView$ViewFlinger.run(RecyclerView.Java:2715)
at Android.view.Choreographer$CallbackRecord.run(Choreographer.Java:725)
at Android.view.Choreographer.doCallbacks(Choreographer.Java:555)
at Android.view.Choreographer.doFrame(Choreographer.Java:524)
at Android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.Java:711)
at Android.os.Handler.handleCallback(Handler.Java:615)
at Android.os.Handler.dispatchMessage(Handler.Java:92)
at Android.os.Looper.loop(Looper.Java:137)
at Android.app.ActivityThread.main(ActivityThread.Java:4921)
at Java.lang.reflect.Method.invokeNative(Native Method)
at Java.lang.reflect.Method.invoke(Method.Java:511)
at com.Android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.Java:1027)
at com.Android.internal.os.ZygoteInit.main(ZygoteInit.Java:794)
at dalvik.system.NativeStart.main(Native Method)
Code AdapterView:
private static class FileListAdapter extends RecyclerView.Adapter<FileHolder> {
private final Context mContext;
private final SparseBooleanArray mSelectedArray;
private final List<String> mList;
FileListAdapter(Context context, List<String> list, SparseBooleanArray selectedArray) {
mList = list;
mContext = context;
mSelectedArray = selectedArray;
}
@Override
public FileHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
View view = LayoutInflater.from(viewGroup.getContext()).inflate(
R.layout.file_list_item, viewGroup, false);
TextView tv = (TextView) view
.findViewById(R.id.file_name_text);
Typeface font = Typeface.createFromAsset(viewGroup.getContext().getAssets(),
viewGroup.getContext().getString(R.string.roboto_regular));
tv.setTypeface(font);
return new FileHolder(view, tv);
}
@Override
public void onBindViewHolder(FileHolder fileHolder, final int i) {
String name = mList.get(i);
// highlight view if selected
setSelected(fileHolder.itemView, mSelectedArray.get(i));
// Set text
fileHolder.mTextView.setText(name);
}
@Override
public int getItemCount() {
return mList.size();
}
}
private static class FileHolder extends RecyclerView.ViewHolder {
public final TextView mTextView;
public FileHolder(View itemView, TextView tv) {
super(itemView);
mTextView = tv;
}
}
Edit: Le bogue est corrigé. Si vous obtenez toujours la même exception, assurez-vous de mettre à jour votre source de données de l'adaptateur uniquement à partir du thread principal et d'appeler la méthode de notification d'adaptateur appropriée après celle-ci.
Ancienne réponse: Il semble s'agir d'un bogue dans RecyclerView
, il est rapporté ici et ici . Espérons que cela sera corrigé dans la prochaine version.
Aucun problème pour moi. Utilisez NotifyDataSetChanged ();
public class MyFragment extends Fragment{
private MyAdapter adapter;
// Your code
public void addArticle(){
ArrayList<Article> list = new ArrayList<Article>();
//Add one article in this list
adapter.addArticleFirst(list); // or adapter.addArticleLast(list);
}
}
public class ArticleAdapterRecycler extends RecyclerView.Adapter<ArticleAdapterRecycler.ViewHolder> {
private ArrayList<Article> Articles = new ArrayList<Article>();
private Context context;
// Some functions from RecyclerView.Adapter<ArticleAdapterRecycler.ViewHolder>
// Add at the top of the list.
public void addArticleFirst(ArrayList<Article> list) {
Articles.addAll(0, list);
notifyDataSetChanged();
}
// Add at the end of the list.
public void addArticleLast(ArrayList<Article> list) {
Articles.addAll(Articles.size(), list);
notifyDataSetChanged();
}
}
Il suffit d’interdire le défilement de RecyclerView lorsque les données changent.
Comme mon code:
mRecyclerView.setOnTouchListener(
new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (mIsRefreshing) {
return true;
} else {
return false;
}
}
}
);
Plus d'informations sur: http://drakeet.me/recyclerview-bug-indexoutofboundsexception-inconsistency-detected-invalid-itval-item-position-solution
Bien que la réponse acceptée donne quelques liens hypertextes utiles concernant ce problème, il n’est pas vrai que ce comportement de RecyclerView
lors du défilement est un bogue .
Si vous voyez cette exception, vous oubliez probablement d’avertir l’adaptateur une fois que le contenu de RecyclerView
est "modifié". Les gens appellent notifyDataSetChanged()
uniquement après l’ajout d’un élément au jeu de données. Toutefois, l'incohérence se produit non seulement après le réapprovisionnement de l'adaptateur, mais également lorsque vous supprimez un élément ou effacez le jeu de données, vous devez actualiser la vue en informant l'adaptateur de cette modification:
public void refillAdapter(Item item) {
adapter.add(item);
notifyDataSetChanged();
}
public void cleanUpAdapter() {
adapter.clear();
notifyDataSetChanged(); /* Important */
}
Dans mon cas, j'ai essayé de nettoyer l'adaptateur dans onStop()
et de le remplir dans onStart()
. J'ai oublié d'appeler notifyDataSetChanged()
après le nettoyage de l'adaptateur à l'aide de clear()
. Ensuite, chaque fois que j'ai changé l'état de onStop()
à onStart()
et que j'ai rapidement fait défiler la RecyclerView
pendant le rechargement du jeu de données, j'ai constaté cette exception. Si j'attendais la fin du rechargement sans défilement, il n'y aurait aucune exception puisque l'adaptateur peut être rétabli sans à-coups cette fois-ci.
En bref, la variable RecyclerView
n'est pas cohérente lorsqu'elle est modifiée. Si vous essayez de faire défiler la vue pendant le traitement des modifications dans le jeu de données, vous voyez Java.lang.IndexOutOfBoundsException: Inconsistency detected
. Pour éliminer ce problème, vous devez avertir l'adaptateur immédiatement après la modification du jeu de données.
Le problème n’est certainement pas dû au défilement de recyclerview, mais il est lié à notifyDataSetChanged () . J'avais une vue de recycleur dans laquelle je changeais constamment de données, c'est-à-dire en ajoutant et en supprimant des données. J'appelais notifyDataSetChanged () à chaque fois que j'ajoutais des éléments à ma liste, mais ne rafraîchissait pas l'adaptateur à chaque fois que l'élément était supprimé ou la liste effacée.
Donc, pour réparer le:
Java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid item position 2(offset:2).state:12 at Android.support.v7.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline(RecyclerView.Java:5456)
J'ai appelé adapter.notifyDataSetChanged () après list.clear () , partout où cela était nécessaire.
if (!myList.isEmpty()) {
myList.clear();
myListAdapter.notifyDataSetChanged();
}
Depuis lors, je n'ai jamais rencontré l'exception . J'espère que cela fonctionne de la même manière pour les autres. :)
J'ai eu un problème similaire mais avec la suppression du contenu. Je voulais aussi garder les animations aussi. J'ai fini par utiliser le notifyRemove, puis en passant la plage. Cela semble résoudre tous les problèmes ...
public void deleteItem(int index) {
try{
mDataset.remove(index);
notifyItemRemoved(index);
notifyItemRangeRemoved(index,1);
} catch (IndexOutOfBoundsException e){
notifyDataSetChanged();
e.printStackTrace();
}
}
Semble travailler et se débarrasser de l'exception IOB ...
Je réalise que pour moi cette exception survient lorsque deux événements se produisent simultanément, c.-à-d.
1) Défilement de recyclerview
2) jeu de données en cours de modification
Donc, j'ai résolu ce problème en désactivant le défilement jusqu'à ce que le notifydatasetchanged soit appelé.
leaderAdapter.notifyDataSetChanged();
pDialog.hide();
Pour désactiver le défilement, j'ai utilisé une boîte de dialogue de progression, dont la valeur setCancelable est false.
pDialog = new ProgressDialog(getActivity());
pDialog.setMessage("Please wait...");
pDialog.setCancelable(false);
L'astuce consiste à activer le défilement uniquement lorsque le jeu de données a été mis à jour.
Je faisais face au même problème, Java.lang.IndexOutOfBoundsException: incohérence détectée .
Créez Custom LinearLayoutManager .
HPLinearLayoutManager.Java
public class HPLinearLayoutManager extends LinearLayoutManager {
public HPLinearLayoutManager(Context context) {
super(context);
}
public HPLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
super(context, orientation, reverseLayout);
}
public HPLinearLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
/**
* Magic here
*/
@Override
public boolean supportsPredictiveItemAnimations() {
return false;
}
}
créer instance of HPLinearLayoutManager
.
HPLinearLayoutManager hpLinearLayoutManager = new HPLinearLayoutManager(mContext);
recyclerView.setLayoutManager(hpLinearLayoutManager);
J'espère que cela vous aiderait.
Ce problème vient à recyclerview si vous utilisez
adapter.setHasStableIds(true);
si vous le définissez, supprimez-le et mettez à jour votre jeu de données à l'intérieur de l'adaptateur;
si le problème persiste, invalidez toutes les vues une fois que vous avez obtenu de nouvelles données, puis mettez à jour votre jeu de données.
J'ai réussi à utiliser cette suggestion en utilisant la suggestion de Cocorico dans une réponse précédente ( https://stackoverflow.com/a/26927186/3660638 ), mais il y a un problème: depuis que j'utilise une SortedList
, j'utilise notifyDataSetChanged () chaque fois qu'il y a un changement dans les données (ajout, suppression, etc.) vous fait perdre les animations d'éléments que vous obtenez avec notifyItemXXXXX(position)
, donc ce que j'ai fini par faire, c'est de l'utiliser seulement lorsque je modifie les données par lot, comme:
public void addAll(SortedList<Entity> items) {
movieList.beginBatchedUpdates();
for (int i = 0; i < items.size(); i++) {
movieList.add(items.get(i));
}
movieList.endBatchedUpdates();
notifyDataSetChanged();
}
Vous devez utiliser dans votre compte getitem
public int getItemCount() {
if (mList!= null)
return mList.size();
else
return 0;
}
Actualiser également la vue recycleur s'il vous plaît utiliser cette
if (recyclerView.getAdapter() == null) {
recyclerView.setHasFixedSize(true);
mFileListAdapter= new FileListAdapter(this);
recyclerView.setAdapter(mFileListAdapter);
recyclerView.setItemAnimator(new DefaultItemAnimator());
} else {
mFileListAdapter.notifyDataSetChanged();
}
En utilisant cette solution, vous ne pouvez pas résoudre le problème Vous utilisez simplement une condition à l'intérieur de onBindViewHolder pour résoudre le problème Java.lang.IndexOutOfBoundsException
public void onBindViewHolder(FileHolder fileHolder, final int i) {
if(i < mList.size)
{
String name = mList.get(i);
setSelected(fileHolder.itemView, mSelectedArray.get(i));
fileHolder.mTextView.setText(name);
}
}
J'ai reproduit ce problème . Cela s'est produit lorsque nous avons supprimé les éléments du thread d'arrière-plan de mList mais que nous n'avons pas appelé notifyDataSetChanged () Maintenant, si nous faisons défiler Cette exception est à venir.
Java.lang.IndexOutOfBoundsException: incohérence détectée. Position de l'article invalide 86 (offset: 86) .state: 100
Au départ, j'avais 100 éléments et j'ai retiré quelques éléments du fil d'arrière-plan.
On dirait que Recyclerview appelle getItemCount () lui-même pour valider l'état.
créer CustomLinearLayoutManager:
public class CustomLinearLayoutManager extends LinearLayoutManager {
public CustomLinearLayoutManager(Context context) {
super(context);
}
public CustomLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
super(context, orientation, reverseLayout);
}
public CustomLinearLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
public boolean supportsPredictiveItemAnimations() {
return false;
}
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
try {
super.onLayoutChildren(recycler, state);
} catch (IndexOutOfBoundsException e) {
e.printStackTrace();
}
}
@Override
public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
try {
return super.scrollVerticallyBy(dy, recycler, state);
} catch (Exception e) {
e.printStackTrace();
}
return 0;
}
}
Évitez notifyDatasetHasChanged () et procédez comme suit:
public void setItems(ArrayList<Article> newArticles) {
//get the current items
int currentSize = articles.size();
//remove the current items
articles.clear();
//add all the new items
articles.addAll(newArticles);
//tell the recycler view that all the old items are gone
notifyItemRangeRemoved(0, currentSize);
//tell the recycler view how many new items we added
notifyItemRangeInserted(0, articles.size());
}
Je modifie les données pour la variable RecyclerView
en arrière-plan Thread
. J'ai la même Exception
que l'OP. J'ai ajouté ceci après avoir changé les données:
myRecyclerView.post(new Runnable() {
@Override
public void run() {
myRecyclerAdapter.notifyDataSetChanged();
}
});
J'espère que ça aide
J'ai le même problème avec ce problème, je suis très fatigué de chercher et de le résoudre. Mais j'ai trouvé une réponse à résoudre et les exceptions n'ont pas encore été rejetées.
public class MyLinearLayoutManager extends LinearLayoutManager
{
public MyLinearLayoutManager(Context context) {
super(context);
}
public MyLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
super(context, orientation, reverseLayout);
}
public MyLinearLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
public boolean supportsPredictiveItemAnimations() {
return false;
}
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
//override this method and implement code as below
try {
super.onLayoutChildren(recycler, state);
} catch (Exception e) {
e.printStackTrace();
}
}
}
J'espère que cette réponse résoudra votre problème.
J'avais le même problème lorsque je configurais une nouvelle instance d'adaptateur en fonction de certains critères de sélection.
J'ai résolu mon problème en utilisant RecyclerView.swapAdapter(adapter, true)
Lorsque nous définissons un nouvel adaptateur.
il y a une phrase dans le code sonore:
/** * Utilisé lorsque LayoutState est construit dans un état de défilement. Cela devrait * être réglé la quantité de défilement que nous pouvons faire sans créer un nouveau. * vue. Paramètres requis pour un recyclage efficace des vues . */ int mScrollingOffset;
J'ai également eu le même problème et je l'ai corrigé en n'utilisant pas la méthode notifyItemRangeChanged (). Il est bien expliqué à
https://code.google.com/p/Android/issues/detail?id=77846#c10
J'ai eu un problème similaire pendant que j'essayais d'ajouter le premier élément dans recyclerView avec la méthode notifyItemInserted, alors j'ai modifié la fonction addItem sur mon adaptateur comme indiqué ci-dessous et le problème a été résolu.
Problème étrange, espérons qu'il sera bientôt résolu!
public void addItem(int position, TableItem item) {
boolean firstEntry = false;
if (items.size() == 0) {
firstEntry = true;
}
items.add(position, item);
if (firstEntry) {
notifyDataSetChanged();
} else {
notifyItemInserted(position);
}
}
Je ne vois rien de mal avec le code que vous avez posté. La seule chose qui me soit bizarre est cette ligne
setSelected(fileHolder.itemView, mSelectedArray.get(i));
dans votre méthode onBindViewHolder de l'adaptateur .. mettez-vous également à jour ce tableau lorsque vous modifiez la taille de votre liste d'éléments dans le tableau?
faites cela quand vous voulez ajouter une vue (commenotifyDataouaddViewou quelque chose comme ça)
if(isAdded()){
//
// add view like this.
//
// celebrityActionAdapter.notifyItemRangeInserted(pageSize, 10);
//
//
}
essayez d'utiliser un indicateur booléen, initialisez-le à false et, dans la méthode OnRefresh, rendez-le vrai, effacez votre liste de données si indicateur est vrai juste avant d'y ajouter les nouvelles données et ensuite, le rendre faux.
votre code pourrait être comme ça
private boolean pullToRefreshFlag = false ;
private ArrayList<your object> dataList ;
private Adapter adapter ;
public class myClass extend Fragment implements SwipeRefreshLayout.OnRefreshListener{
private void requestUpdateList() {
if (pullToRefresh) {
dataList.clear
pullToRefreshFlag = false;
}
dataList.addAll(your data);
adapter.notifyDataSetChanged;
@Override
OnRefresh() {
PullToRefreshFlag = true
reqUpdateList() ;
}
}
Dans mon cas, cela a été résolu en changeant mRecyclerView.smoothScrollToPosition(0)
en
mRecyclerView.scrollToPosition(0)