RecyclerView и java.lang.IndexOutOfBoundsException: обнаружено несоответствие. Неверный вид держателя держателя положения ViewHolder в устройствах Samsung

У меня есть вид переработчика, который отлично работает на всех устройствах, кроме Samsung. На самсунг у меня получается

java.lang.IndexOutOfBoundsException: обнаружено несоответствие. Неправильный вид держателя адаптера positionViewHolder

когда я вернусь к фрагменту с видом переработчика из другого действия.

Код адаптера:

public class FeedRecyclerAdapter extends RecyclerView.Adapter<FeedRecyclerAdapter.MovieViewHolder> {
    public static final String getUserPhoto = APIConstants.BASE_URL + APIConstants.PICTURE_PATH_SMALL;
    Movie[] mMovies = null;
    Context mContext = null;
    Activity mActivity = null;
    LinearLayoutManager mManager = null;
    private Bus uiBus = null;
    int mCountOfLikes = 0;

    //Constructor
    public FeedRecyclerAdapter(Movie[] movies, Context context, Activity activity,
                               LinearLayoutManager manager) {
        mContext = context;
        mActivity = activity;
        mMovies = movies;
        mManager = manager;
        uiBus = BusProvider.getUIBusInstance();
    }

    public void setMoviesAndNotify(Movie[] movies, boolean movieIgnored) {
        mMovies = movies;
        int firstItem = mManager.findFirstVisibleItemPosition();
        View firstItemView = mManager.findViewByPosition(firstItem);
        int topOffset = firstItemView.getTop();
        notifyDataSetChanged();
        if(movieIgnored) {
            mManager.scrollToPositionWithOffset(firstItem - 1, topOffset);
        } else {
            mManager.scrollToPositionWithOffset(firstItem, topOffset);
        }
    }

    // Create new views (called by layout manager)
    @Override
    public MovieViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.feed_one_recommended_movie_layout, parent, false);

        return new MovieViewHolder(view);
    }

    // Replaced contend of each view (called by layout manager)
    @Override
    public void onBindViewHolder(MovieViewHolder holder, int position) {
        setLikes(holder, position);
        setAddToCollection(holder, position);
        setTitle(holder, position);
        setIgnoreMovieInfo(holder, position);
        setMovieInfo(holder, position);
        setPosterAndTrailer(holder, position);
        setDescription(holder, position);
        setTags(holder, position);
    }

    // returns item count (called by layout manager)
    @Override
    public int getItemCount() {
        return mMovies != null ? mMovies.length : 0;
    }

    private void setLikes(final MovieViewHolder holder, final int position) {
        List<Reason> likes = new ArrayList<>();
        for(Reason reason : mMovies[position].reasons) {
            if(reason.title.equals("Liked this movie")) {
                likes.add(reason);
            }
        }
        mCountOfLikes = likes.size();
        holder.likeButton.setText(mContext.getString(R.string.like)
            + Html.fromHtml(getCountOfLikesString(mCountOfLikes)));
        final MovieRepo repo = MovieRepo.getInstance();
        final int pos = position;
        final MovieViewHolder viewHolder = holder;
        holder.likeButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(mMovies[pos].isLiked) {
                    repo.unlikeMovie(AuthStore.getInstance()
                        .getAuthToken(), mMovies[pos].id, new Callback<Movie>() {
                        @Override
                        public void success(Movie movie, Response response) {
                            Drawable img = mContext.getResources().getDrawable(R.drawable.ic_like);
                            viewHolder.likeButton
                                .setCompoundDrawablesWithIntrinsicBounds(img, null, null, null);
                            if (--mCountOfLikes <= 0) {
                                viewHolder.likeButton.setText(mContext.getString(R.string.like));
                            } else {
                                viewHolder.likeButton
                                    .setText(Html.fromHtml(mContext.getString(R.string.like)
                                        + getCountOfLikesString(mCountOfLikes)));
                            }
                            mMovies[pos].isLiked = false;
                        }

                        @Override
                        public void failure(RetrofitError error) {
                            Toast.makeText(mContext.getApplicationContext(),
                                mContext.getString(R.string.cannot_like), Toast.LENGTH_LONG)
                                .show();
                        }
                    });
                } else {
                    repo.likeMovie(AuthStore.getInstance()
                        .getAuthToken(), mMovies[pos].id, new Callback<Movie>() {
                        @Override
                        public void success(Movie movie, Response response) {
                            Drawable img = mContext.getResources().getDrawable(R.drawable.ic_liked_green);
                            viewHolder.likeButton
                                .setCompoundDrawablesWithIntrinsicBounds(img, null, null, null);
                            viewHolder.likeButton
                                .setText(Html.fromHtml(mContext.getString(R.string.like)
                                    + getCountOfLikesString(++mCountOfLikes)));
                            mMovies[pos].isLiked = true;
                            setComments(holder, position);
                        }

                        @Override
                        public void failure(RetrofitError error) {
                            Toast.makeText(mContext,
                                mContext.getString(R.string.cannot_like), Toast.LENGTH_LONG).show();
                        }
                    });
                }
            }
        });
    }

    private void setComments(final MovieViewHolder holder, final int position) {
        holder.likeAndSaveButtonLayout.setVisibility(View.GONE);
        holder.commentsLayout.setVisibility(View.VISIBLE);
        holder.sendCommentButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (holder.commentsInputEdit.getText().length() > 0) {
                    CommentRepo repo = CommentRepo.getInstance();
                  repo.sendUserComment(AuthStore.getInstance().getAuthToken(), mMovies[position].id,
                        holder.commentsInputEdit.getText().toString(), new Callback<Void>() {
                            @Override
                            public void success(Void aVoid, Response response) {
                                Toast.makeText(mContext, mContext.getString(R.string.thanks_for_your_comment),
                                    Toast.LENGTH_SHORT).show();
                                hideCommentsLayout(holder);
                            }

                            @Override
                            public void failure(RetrofitError error) {
                                Toast.makeText(mContext, mContext.getString(R.string.cannot_add_comment),
                                    Toast.LENGTH_LONG).show();
                            }
                        });
                } else {
                    hideCommentsLayout(holder);
                }
            }
        });
    }

    private void hideCommentsLayout(MovieViewHolder holder) {
        holder.commentsLayout.setVisibility(View.GONE);
        holder.likeAndSaveButtonLayout.setVisibility(View.VISIBLE);
    }

    private void setAddToCollection(final MovieViewHolder holder, int position) {
        final int pos = position;
        if(mMovies[position].isInWatchlist) {
            holder.saveButton
              .setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_check_green, 0, 0, 0);
        }
        final CollectionRepo repo = CollectionRepo.getInstance();
        holder.saveButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(!mMovies[pos].isInWatchlist) {
                   repo.addMovieToCollection(AuthStore.getInstance().getAuthToken(), 0, mMovies[pos].id, new Callback<MovieCollection[]>() {
                            @Override
                            public void success(MovieCollection[] movieCollections, Response response) {
                                holder.saveButton
                                    .setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_check_green, 0, 0, 0);

                                mMovies[pos].isInWatchlist = true;
                            }

                            @Override
                            public void failure(RetrofitError error) {
                                Toast.makeText(mContext, mContext.getString(R.string.movie_not_added_to_collection),
                                    Toast.LENGTH_LONG).show();
                            }
                        });
                } else {
                 repo.removeMovieFromCollection(AuthStore.getInstance().getAuthToken(), 0,
                        mMovies[pos].id, new Callback<MovieCollection[]>() {
                        @Override
                        public void success(MovieCollection[] movieCollections, Response response) {
                            holder.saveButton
                                .setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_plus, 0, 0, 0);

                            mMovies[pos].isInWatchlist = false;
                        }

                        @Override
                        public void failure(RetrofitError error) {
                            Toast.makeText(mContext,
                                mContext.getString(R.string.cannot_delete_movie_from_watchlist),
                                Toast.LENGTH_LONG).show();
                        }
                    });
                }
            }
        });
    }

    private String getCountOfLikesString(int countOfLikes) {
        String countOfLikesStr;
        if(countOfLikes == 0) {
            countOfLikesStr = "";
        } else if(countOfLikes > 999) {
            countOfLikesStr = " " + (countOfLikes/1000) + "K";
        } else if (countOfLikes > 999999){
            countOfLikesStr = " " + (countOfLikes/1000000) + "M";
        } else {
            countOfLikesStr = " " + String.valueOf(countOfLikes);
        }
        return "<small>" + countOfLikesStr + "</small>";
    }

    private void setTitle(MovieViewHolder holder, final int position) {
        holder.movieTitleTextView.setText(mMovies[position].title);
        holder.movieTitleTextView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                MovieDetailActivity.openView(mContext, mMovies[position].id, true, false);
            }
        });
    }

    private void setIgnoreMovieInfo(MovieViewHolder holder, final int position) {
        holder.ignoreMovie.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                MovieRepo repo = MovieRepo.getInstance();
                repo.hideMovie(AuthStore.getInstance().getAuthToken(), mMovies[position].id,
                    new Callback<Void>() {
                        @Override
                        public void success(Void aVoid, Response response) {
                            Movie[] newMovies = new Movie[mMovies.length - 1];
                            for (int i = 0, j = 0; j < mMovies.length; i++, j++) {
                                if (i != position) {
                                    newMovies[i] = mMovies[j];
                                } else {
                                    if (++j < mMovies.length) {
                                        newMovies[i] = mMovies[j];
                                    }
                                }
                            }
                            uiBus.post(new MoviesChangedEvent(newMovies));
                            setMoviesAndNotify(newMovies, true);
                            Toast.makeText(mContext, mContext.getString(R.string.movie_ignored),
                                Toast.LENGTH_SHORT).show();
                        }

                        @Override
                        public void failure(RetrofitError error) {
                            Toast.makeText(mContext, mContext.getString(R.string.movie_ignored_failed),
                                Toast.LENGTH_LONG).show();
                        }
                    });
            }
        });
    }

    private void setMovieInfo(MovieViewHolder holder, int position) {
        String imdp = "IMDB: ";
        String sources = "", date;
        if(mMovies[position].showtimes != null && mMovies[position].showtimes.length > 0) {
            int countOfSources = mMovies[position].showtimes.length;
            for(int i = 0; i < countOfSources; i++) {
                sources += mMovies[position].showtimes[i].name + ", ";
            }
            sources = sources.trim();
            if(sources.charAt(sources.length() - 1) == ',') {
                if(sources.length() > 1) {
                    sources = sources.substring(0, sources.length() - 2);
                } else {
                    sources = "";
                }
            }
        } else {
            sources = "";
        }
        imdp += mMovies[position].imdbRating + " | ";
        if(sources.isEmpty()) {
            date = mMovies[position].releaseYear;
        } else {
            date = mMovies[position].releaseYear + " | ";
        }

        holder.movieInfoTextView.setText(imdp + date + sources);
    }

    private void setPosterAndTrailer(final MovieViewHolder holder, final int position) {
        if (mMovies[position] != null && mMovies[position].posterPath != null
            && !mMovies[position].posterPath.isEmpty()) {
            Picasso.with(mContext)
                .load(mMovies[position].posterPath)
             .error(mContext.getResources().getDrawable(R.drawable.noposter))
                .into(holder.posterImageView);
        } else {
            holder.posterImageView.setImageResource(R.drawable.noposter);
        }
        holder.posterImageView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                MovieDetailActivity.openView(mActivity, mMovies[position].id, false, false);
            }
        });
        if(mMovies[position] != null && mMovies[position].trailerLink  != null
            && !mMovies[position].trailerLink.isEmpty()) {
            holder.playTrailer.setVisibility(View.VISIBLE);
            holder.playTrailer.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    MovieDetailActivity.openView(mActivity, mMovies[position].id, false, true);
                }
            });
        }
    }

    private void setDescription(MovieViewHolder holder, int position) {
        String text = mMovies[position].overview;
        if(text == null || text.isEmpty()) {
       holder.descriptionText.setText(mContext.getString(R.string.no_description));
        } else if(text.length() > 200) {
            text = text.substring(0, 196) + "...";
            holder.descriptionText.setText(text);
        } else {
            holder.descriptionText.setText(text);
        }
        final int pos = position;
        holder.descriptionText.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                MovieDetailActivity.openView(mActivity, mMovies[pos].id, false, false);
            }
        });
    }

    private void setTags(MovieViewHolder holder, int position) {
        List<String> tags = Arrays.asList(mMovies[position].tags);
        if(tags.size() > 0) {
            CastAndTagsFeedAdapter adapter = new CastAndTagsFeedAdapter(tags,
                mContext, ((FragmentActivity) mActivity).getSupportFragmentManager());
            holder.tags.setItemMargin(10);
            holder.tags.setAdapter(adapter);
        } else {
            holder.tags.setVisibility(View.GONE);
        }
    }

    // class view holder that provide us a link for each element of list
    public static class MovieViewHolder extends RecyclerView.ViewHolder {
        TextView movieTitleTextView, movieInfoTextView, descriptionText, reasonsCountText;
        TextView reasonText1, reasonAuthor1, reasonText2, reasonAuthor2;
        EditText commentsInputEdit;
        Button likeButton, saveButton, playTrailer, sendCommentButton;
        ImageButton ignoreMovie;
        ImageView posterImageView, userPicture1, userPicture2;
        TwoWayView tags;
        RelativeLayout mainReasonsLayout, firstReasonLayout, secondReasonLayout, reasonsListLayout;
        RelativeLayout commentsLayout;
        LinearLayout likeAndSaveButtonLayout;
        ProgressBar progressBar;

        public MovieViewHolder(View view) {
            super(view);
            movieTitleTextView = (TextView)view.findViewById(R.id.movie_title_text);
            movieInfoTextView = (TextView)view.findViewById(R.id.movie_info_text);
            descriptionText = (TextView)view.findViewById(R.id.text_description);
            reasonsCountText = (TextView)view.findViewById(R.id.reason_count);
            reasonText1 = (TextView)view.findViewById(R.id.reason_text_1);
            reasonAuthor1 = (TextView)view.findViewById(R.id.author_1);
            reasonText2 = (TextView)view.findViewById(R.id.reason_text_2);
            reasonAuthor2 = (TextView)view.findViewById(R.id.author_2);
            commentsInputEdit = (EditText)view.findViewById(R.id.comment_input);
            likeButton = (Button)view.findViewById(R.id.like_button);
            saveButton = (Button)view.findViewById(R.id.save_button);
            playTrailer = (Button)view.findViewById(R.id.play_trailer_button);
            sendCommentButton = (Button)view.findViewById(R.id.send_button);
            ignoreMovie = (ImageButton)view.findViewById(R.id.ignore_movie_imagebutton);
            posterImageView = (ImageView)view.findViewById(R.id.poster_image);
            userPicture1 = (ImageView)view.findViewById(R.id.user_picture_1);
            userPicture2 = (ImageView)view.findViewById(R.id.user_picture_2);
            tags = (TwoWayView)view.findViewById(R.id.list_view_feed_tags);
            mainReasonsLayout = (RelativeLayout)view.findViewById(R.id.reasons_main_layout);
            firstReasonLayout = (RelativeLayout)view.findViewById(R.id.first_reason);
            secondReasonLayout = (RelativeLayout)view.findViewById(R.id.second_reason);
            reasonsListLayout = (RelativeLayout)view.findViewById(R.id.reasons_list);
            commentsLayout = (RelativeLayout)view.findViewById(R.id.comments_layout);
            likeAndSaveButtonLayout = (LinearLayout)view
                .findViewById(R.id.like_and_save_buttons_layout);
            progressBar = (ProgressBar)view.findViewById(R.id.centered_progress_bar);
        }
    }
}

Исключение:

java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid view holder adapter positionViewHolder{42319ed8 position=1 id=-1, oldPos=0, pLpos:0 scrap tmpDetached no parent}
 at android.support.v7.widget.RecyclerView$Recycler.validateViewHolderForOffsetPosition(RecyclerView.java:4166)
 at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:4297)
 at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:4278)
 at android.support.v7.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:1947)
 at android.support.v7.widget.GridLayoutManager.layoutChunk(GridLayoutManager.java:434)
 at android.support.v7.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1322)
 at android.support.v7.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:556)
 at android.support.v7.widget.GridLayoutManager.onLayoutChildren(GridLayoutManager.java:171)
 at android.support.v7.widget.RecyclerView.dispatchLayout(RecyclerView.java:2627)
 at android.support.v7.widget.RecyclerView.onLayout(RecyclerView.java:2971)
 at android.view.View.layout(View.java:15746)
 at android.view.ViewGroup.layout(ViewGroup.java:4867)
 at android.support.v4.widget.SwipeRefreshLayout.onLayout(SwipeRefreshLayout.java:562)
 at android.view.View.layout(View.java:15746)
 at android.view.ViewGroup.layout(ViewGroup.java:4867)
 at android.widget.FrameLayout.layoutChildren(FrameLayout.java:453)
 at android.widget.FrameLayout.onLayout(FrameLayout.java:388)
 at android.view.View.layout(View.java:15746)
 at android.view.ViewGroup.layout(ViewGroup.java:4867)
 at android.support.v4.view.ViewPager.onLayout(ViewPager.java:1626)
 at android.view.View.layout(View.java:15746)
 at android.view.ViewGroup.layout(ViewGroup.java:4867)
 at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1677)
 at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1531)
 at android.widget.LinearLayout.onLayout(LinearLayout.java:1440)
 at android.view.View.layout(View.java:15746)
 at android.view.ViewGroup.layout(ViewGroup.java:4867)
 at android.support.v4.view.ViewPager.onLayout(ViewPager.java:1626)
 at android.view.View.layout(View.java:15746)
 at android.view.ViewGroup.layout(ViewGroup.java:4867)
 at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1677)
 at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1531)
 at android.widget.LinearLayout.onLayout(LinearLayout.java:1440)
 at android.view.View.layout(View.java:15746)
 at android.view.ViewGroup.layout(ViewGroup.java:4867)
 at android.widget.FrameLayout.layoutChildren(FrameLayout.java:453)
 at android.widget.FrameLayout.onLayout(FrameLayout.java:388)
 at android.view.View.layout(View.java:15746)
 at android.view.ViewGroup.layout(ViewGroup.java:4867)
 at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1677)
 at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1531)
 at android.widget.LinearLayout.onLayout(LinearLayout.java:1440)
 at android.view.View.layout(View.java:15746)
 at android.view.ViewGroup.layout(ViewGroup.java:4867)
 at android.widget.FrameLayout.layoutChildren(FrameLayout.java:453)
 at android.widget.FrameLayout.onLayout(FrameLayout.java:388)
07-30 12:48:22.688    9590-9590/com.Filmgrail.android.debug W/System.err? at android.view.View.layout(View.java:15746)
 at android.view.ViewGroup.layout(ViewGroup.java:4867)
 at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1677)
 at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1531)
 at android.widget.LinearLayout.onLayout(LinearLayout.java:1440)
 at android.view.View.layout(View.java:15746)
 at android.view.ViewGroup.layout(ViewGroup.java:4867)
 at android.widget.FrameLayout.layoutChildren(FrameLayout.java:453)
 at android.widget.FrameLayout.onLayout(FrameLayout.java:388)
 at android.view.View.layout(View.java:15746)
 at android.view.ViewGroup.layout(ViewGroup.java:4867)
 at android.view.ViewRootImpl.performLayout(ViewRootImpl.java:2356)
 at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2069)
 at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1254)
 at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:6630)
 at android.view.Choreographer$CallbackRecord.run(Choreographer.java:803)
 at android.view.Choreographer.doCallbacks(Choreographer.java:603)
 at android.view.Choreographer.doFrame(Choreographer.java:573)
 at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:789)
 at android.os.Handler.handleCallback(Handler.java:733)
 at android.os.Handler.dispatchMessage(Handler.java:95)
 at android.os.Looper.loop(Looper.java:136)
 at android.app.ActivityThread.main(ActivityThread.java:5479)
 at java.lang.reflect.Method.invokeNative(Native Method)
 at java.lang.reflect.Method.invoke(Method.java:515)
 at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1283)
 at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1099)
 at dalvik.system.NativeStart.main(Native Method)

Как я могу это исправить?

38 ответов

Эта проблема вызвана RecyclerView Данные изменены в другой ветке. Лучший способ - это проверка доступа к данным. И обходной путь оборачивает LinearLayoutManager,

Предыдущий ответ

На самом деле была ошибка в RecyclerView, и поддержка 23.1.1 все еще не исправлена.

Для обходного пути, обратите внимание, что стеки трассировки, если мы можем поймать это Exception в одном из классов он может пропустить этот сбой. Для меня я создаю LinearLayoutManagerWrapper и переопределить onLayoutChildren:

public class WrapContentLinearLayoutManager extends LinearLayoutManager {
    //... constructor
    @Override
    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
        try {
            super.onLayoutChildren(recycler, state);
        } catch (IndexOutOfBoundsException e) {
            Log.e("probe", "meet a IOOBE in RecyclerView");
        }
    }
}

Затем установите его RecyclerView:

RecyclerView recyclerView = (RecyclerView)findViewById(R.id.recycler_view);

recyclerView.setLayoutManager(new WrapContentLinearLayoutManager(activity, LinearLayoutManager.HORIZONTAL, false));

На самом деле поймать это исключение, и, похоже, никаких побочных эффектов еще нет.

Кроме того, если вы используете GridLayoutManager или же StaggeredGridLayoutManager Вы должны создать обертку для него.

Обратите внимание: RecyclerView может быть в неправильном внутреннем состоянии.

Это пример обновления данных с совершенно новым контентом. Вы можете легко изменить его в соответствии с вашими потребностями. Я решил это в моем случае, позвонив:

notifyItemRangeRemoved(0, previousContentSize);

до:

notifyItemRangeInserted(0, newContentSize);

Это правильное решение и также упоминается в этом посте участником проекта AOSP.

Я столкнулся с этой проблемой однажды, и я решил это, оборачивая LayoutManager и отключение прогностической анимации.

Вот пример:

public class LinearLayoutManagerWrapper extends LinearLayoutManager {

  public LinearLayoutManagerWrapper(Context context) {
    super(context);
  }

  public LinearLayoutManagerWrapper(Context context, int orientation, boolean reverseLayout) {
    super(context, orientation, reverseLayout);
  }

  public LinearLayoutManagerWrapper(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    super(context, attrs, defStyleAttr, defStyleRes);
  }

  @Override
  public boolean supportsPredictiveItemAnimations() {
    return false;
  }
}

И установите его RecyclerView:

RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManagerWrapper(context, LinearLayoutManager.VERTICAL, false);

Это сработало для меня. Ключ не использовать notifyDataSetChanged() и делать правильные вещи в правильном порядке:

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, newArticles.size());
}

Причины вызвали эту проблему:

  1. Внутренняя проблема в Recycler, когда включена анимация элементов
  2. Модификация данных Recycler в другом потоке
  3. Неправильный вызов методов уведомления

РЕШЕНИЕ:

----------------- РЕШЕНИЕ 1---------------

  • Поймать исключение (не рекомендуется, особенно по причине № 3)

Создайте пользовательский LinearLayoutManager следующим образом и установите для него ReyclerView.

    public class CustomLinearLayoutManager extends LinearLayoutManager {

            //Generate constructors

            @Override
            public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {

                try {

                    super.onLayoutChildren(recycler, state);

                } catch (IndexOutOfBoundsException e) {

                    Log.e(TAG, "Inconsistency detected");
                }

            }
        }

Затем установите RecyclerVIew Layout Manager следующим образом:

recyclerView.setLayoutManager(new CustomLinearLayoutManager(activity));

----------------- РЕШЕНИЕ 2---------------

  • Отключить анимацию элементов (исправляет проблему, если она вызвана причиной № 1):

Опять же, создайте собственный менеджер линейного макета следующим образом:

    public class CustomLinearLayoutManager extends LinearLayoutManager {

            //Generate constructors

             @Override
             public boolean supportsPredictiveItemAnimations() {
                 return false;
             }
        }

Затем установите RecyclerVIew Layout Manager следующим образом:

recyclerView.setLayoutManager(new CustomLinearLayoutManager(activity));

----------------- РЕШЕНИЕ 3---------------

  • Это решение устраняет проблему, если она вызвана причиной № 3. Вы должны убедиться, что вы используете методы уведомления правильно. Кроме того, используйте DiffUtil, чтобы обрабатывать изменения разумным, простым и плавным способом. Использование DiffUtil в Android RecyclerView

----------------- РЕШЕНИЕ 4 ---------------

  • По причине №2 вам необходимо проверить все данные доступа к списку переработчика и убедиться, что нет изменений в другом потоке.

У меня была похожая проблема.

Проблема в коде ошибки ниже:

int prevSize = messageListHistory.size();
// some insert
adapter.notifyItemRangeInserted(prevSize - 1, messageListHistory.size() -1);

Решение:

int prevSize = messageListHistory.size();
// some insert
adapter.notifyItemRangeInserted(prevSize, messageListHistory.size() -prevSize);

Согласно этой проблеме, проблема была решена и, вероятно, была выпущена в начале 2015 года . Цитата из той же ветки:

Это конкретно связано с вызовом notifyDataSetChanged. [...]

Кстати, я настоятельно советую не использовать notifyDataSetChanged, потому что это убивает анимацию и производительность. Также для этого случая использование определенных событий уведомления будет работать вокруг проблемы.

Если у вас все еще возникают проблемы с последней версией библиотеки поддержки, я бы посоветовал вам просмотреть ваши звонки notifyXXX (в частности, ваше использование notifyDataSetChanged) внутри адаптера, чтобы убедиться, что вы придерживаетесь (несколько деликатного / неясного) RecyclerView.Adapter контракт. Также обязательно отправляйте эти уведомления в основной поток.

У меня такая же проблема. Это было вызвано тем, что я отложил уведомление адаптера о вставке элемента.

Но ViewHolder попытался перерисовать некоторые данные в его представлении, и он начал RecyclerView измерение и пересчет количества детей - в тот момент он падал (список элементов и его размер уже были обновлены, но адаптер еще не был уведомлен).

В моем случае я столкнулся с этой проблемой из-за получения обновлений данных с сервера (я использую Firebase Firestore), и в то время как первый набор данных обрабатывается DiffUtil в фоновом режиме, другой набор данных обновляется и вызывает проблему параллелизма. запустив еще один DiffUtil.

Короче говоря, если вы используете DiffUtil в фоновом потоке, который затем возвращается в основной поток для отправки результатов в RecylerView, то у вас есть шанс получить эту ошибку, когда несколько обновлений данных происходят в короткие сроки.

Я решил это, следуя совету в этом замечательном объяснении: https://medium.com/@jonfhancock/get-threading-right-with-diffutil-423378e126d2

Просто для того, чтобы объяснить, что решение состоит в том, чтобы отправить обновления, пока текущее работает в Deque. Затем deque может запускать ожидающие обновления после завершения текущего, следовательно, обрабатывая все последующие обновления, но также избегая ошибок несогласованности!

Надеюсь, это поможет, потому что этот заставил меня почесать голову!

Еще одна причина, по которой эта проблема возникает, - это когда вы вызываете эти методы с неправильными индексами (индексы, которых там НЕ произошло, вставляют или удаляют в них)

-notifyItemRangeRemoved

-notifyItemRemoved

-notifyItemRangeInserted

-notifyItemInserted

проверьте параметры indexe для этих методов и убедитесь, что они точные и правильные.

Это происходит, когда вы указываете неправильную позицию для notifyItemChanged, notifyItemRangeInserted и т. Д. Для меня:

До: (ошибочно)

public void addData(List<ChannelItem> list) {
  int initialSize = list.size();
  mChannelItemList.addAll(list);
  notifyItemRangeChanged(initialSize - 1, mChannelItemList.size());
 } 

После: (исправить)

 public void addData(List<ChannelItem> list) {
  int initialSize = mChannelItemList.size();
  mChannelItemList.addAll(list);
  notifyItemRangeInserted(initialSize, mChannelItemList.size()-1); //Correct position 
 }

Эта ошибка по-прежнему не исправлена ​​в 23.1.1, но обычным обходным решением было бы перехватить исключение.

В моем случае каждый раз, когда я вызываю notifyItemRemoved(0), происходит сбой. Оказалось, что я установил setHasStableIds(true) И в getItemId Я только что вернул позицию товара. Я закончил обновлять его, чтобы вернуть элемент hashCode() или самостоятельно определенный уникальный идентификатор, который решил проблему.

Я столкнулся с той же проблемой.

Мое приложение использует компоненты навигации с фрагментом, содержащим мой recyclerView. Мой список отлично отображался при первой загрузке фрагмента... но при переходе и возврате возникла эта ошибка.

При переходе жизненный цикл фрагмента проходил только через onDestroyView, а при возврате он запускался с onCreateView. Однако мой адаптер был инициализирован во фрагменте onCreate и не инициализировался повторно при возврате.

Исправление заключалось в инициализации адаптера в onCreateView.

Надеюсь, это может кому-то помочь.

Иногда это происходит из-за пропуска использованияnotifyItemRemovedиnotifyItemInserted. Убедитесь, что для удаления вы используете:

      list.remove(position);
notifyItemRemoved(position);

и для добавления элемента use :

      list.add(position, item);
notifyItemInserted(position);

и в конце не забудьте:

      notifyItemRangeChanged(position, list.size());

Решено для меня обновлением представления ресайклера до последней версии

      implementation "androidx.recyclerview:recyclerview:1.2.1"

Эта проблема вызвана изменением данных RecyclerView в другом потоке

Можно подтвердить многопоточность как одну проблему, и поскольку я столкнулся с проблемой, и RxJava становится все более популярным: убедитесь, что вы используете .observeOn(AndroidSchedulers.mainThread()) всякий раз, когда вы звоните notify[whatever changed]

Пример кода из адаптера:

myAuxDataStructure.getChangeObservable().observeOn(AndroidSchedulers.mainThread()).subscribe(new Observer<AuxDataStructure>() {

    [...]

    @Override
    public void onNext(AuxDataStructure o) {
        [notify here]
    }
});

В моем случае я изменял данные, ранее находившиеся в потоке, с помощью mRecyclerView.post(новый Runnable...), а затем снова изменял данные в потоке пользовательского интерфейса, что вызывало несогласованность.

Ошибка может быть вызвана тем, что ваши изменения не соответствуют тому, что вы уведомляете. В моем случае:

myList.set(position, newItem);
notifyItemInserted(position);

Что я, конечно, должен был сделать:

myList.add(position, newItem);
notifyItemInserted(position);

Проблема возникла у меня только тогда, когда:

Я создал Адаптер с пустым списком. Затем я вставил элементы и позвонил notifyItemRangeInserted,

Решение:

Я решил это, создав адаптер только после того, как у меня есть первый кусок данных и сразу его инициализировал. Затем можно вставить следующий фрагмент и notifyItemRangeInserted позвонил без проблем.

Я столкнулся с той же проблемой, когда удалил и обновил элементы в списке... После нескольких дней исследований, я думаю, что наконец нашел решение.

Что вам нужно сделать, это сначала сделать все notifyItemChanged вашего списка и только потом notifyItemRemoved в порядке убывания

Надеюсь, это поможет людям, столкнувшимся с той же проблемой...

Моя проблема заключалась в том, что, хотя я очищал оба списка массивов, содержащих модель данных для представления переработчика, я не уведомлял адаптер об этом изменении, поэтому в нем были устаревшие данные из предыдущей модели. Что вызвало недоразумение относительно позиции держателя вида. Чтобы исправить это, всегда сообщайте адаптеру, что набор данных изменился, прежде чем обновлять снова.

В моем случае проблема заключалась в том, что я использовал notifyDataSetChanged, когда количество вновь загруженных данных было меньше, чем исходные данные. Этот подход помог мне:

adapter.notifyItemRangeChanged(0, newAmountOfData + 1);
adapter.notifyItemRangeRemoved(newAmountOfData + 1, previousAmountOfData);

Благодаря идее @Bolling у меня есть реализация для поддержки, чтобы избежать каких-либо значений NULL из списка

public void setList(ArrayList<ThisIsAdapterListObject> _newList) {
    //get the current items
    if (ThisIsAdapterList != null) {
        int currentSize = ThisIsAdapterList.size();
        ThisIsAdapterList.clear();
        //tell the recycler view that all the old items are gone
        notifyItemRangeRemoved(0, currentSize);
    }

    if (_newList != null) {
        if (ThisIsAdapterList == null) {
            ThisIsAdapterList = new ArrayList<ThisIsAdapterListObject>();
        }
        ThisIsAdapterList.addAll(_newList);
        //tell the recycler view how many new items we added
        notifyItemRangeInserted(0, _newList.size());
    }
}

Я использую Cursor, поэтому я не могу использовать DiffUtils, как предлагается в популярных ответах. Чтобы он работал у меня, я отключаю анимацию, когда список не бездействует. Это расширение, которое устраняет эту проблему:

 fun RecyclerView.executeSafely(func : () -> Unit) {
        if (scrollState != RecyclerView.SCROLL_STATE_IDLE) {
            val animator = itemAnimator
            itemAnimator = null
            func()
            itemAnimator = animator
        } else {
            func()
        }
    }

Затем вы можете обновить свой адаптер вот так

list.executeSafely {
  adapter.updateICursor(newCursor)
}

У меня возникла эта проблема, после 1 часа расследования я не смог найти основную причину, а затем перешел сRecyclerView.Adapter to [ListAdapter][1]с обратным вызовом DiffUtil

В моем случае в списке было более 5000 наименований. Моя проблема заключалась в том, что при прокрутке представления рециркулятора иногда вызывается "onBindViewHolder", а метод "myCustomAddItems" изменяет список.

Моим решением было добавить "synchronized (syncObject){}" во все методы, которые изменяют список данных. Таким образом, в любой момент времени только один метод может прочитать этот список.

Со мной это случилось сObservableList.removeIf.

removeIf(predicate)реализован неправильно: он не отправляет уведомления об удалении. Таким образом, индексы были явно неправильными, так как элементы были удалены без ведома RV.

Правильный методremoveAll(predicate).

Я столкнулся с той же проблемой, когда у меня есть данные обновления во время прокрутки RecyclerView. И я исправил это следующим решением:

  1. Прекратите прокручивать наш RecyclerView перед обновлением данных.
  2. Диспетчеру пользовательского макета нравится предыдущий ответ.
  3. Используйте DiffUtils, чтобы убедиться, что обновляемые данные верны.

У меня возникла та же проблема, и я прочитал, что это произошло только в телефонах Samsung... Но реальность показала, что это происходит во многих брендах.

После тестирования я понял, что это происходит только тогда, когда вы быстро прокручиваете RecyclerView и затем возвращаетесь либо с помощью кнопки "Назад", либо с помощью кнопки "Вверх". Поэтому я вставил внутри кнопку "Вверх" и нажал на кнопку ниже приведенного ниже фрагмента:

someList = new ArrayList<>();
mainRecyclerViewAdapter = new MainRecyclerViewAdapter(this, someList, this);
recyclerViewMain.setAdapter(mainRecyclerViewAdapter);
finish();

С помощью этого решения вы просто загружаете новый Arraylist в адаптер и новый адаптер в recyclerView, а затем завершаете работу.

Надеюсь, это поможет кому-то

Другие вопросы по тегам