RecyclerView та java.lang.IndexOutOfBoundsException: виявлена ​​невідповідність. Недійсне положення адаптера утримувача переглядуViewHolder на пристроях Samsung


253

Я переглядаю переробник, який ідеально працює на всіх пристроях, окрім Samsung. На Samsung я розумію

java.lang.IndexOutOfBoundsException: виявлена ​​невідповідність. Недійсне положення адаптера утримувача переглядуViewHolder

коли я повернуся до фрагмента з переглядом рециркулятора з іншої діяльності.

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

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)

Як я можу це виправити?


коли ви повернулися, чи ваші дані такі, як і коли ви залишаєте сторінку?
khusrav

Я збираюсь те саме питання, як ти вирішуєш ....
Ашвін соланкі

@ Владимир Ви знайшли остаточну відповідь?
Аліреза Нооралі

У моєму випадку це було тому, що я приступив до завдання асинхронізації, і коли одна з них завершується перед іншою, а користувач прокручується вниз, а тим часом інший завершує та оновлює користувач адаптера, може отримати такий виняток, оскільки друге завдання повернуло менший обсяг даних
Васиф

Відповіді:


196

Ця проблема викликана 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("TAG", "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Можливо, у внутрішньому стані неправильно.


1
куди саме ти це кладеш? про адаптер або діяльність?
Стів Камау

розширити LinearLayoutManagerі скасувати це. Я буду доповненням у своїй відповіді.
sakiM

14
code.google.com/p/android/isissue/detail?id=158046 відповідь №12 сказала, що цього не робити.
Роберт

гмм, ви праві. Здається, важко розрядити всі потенційні зміни, що не стосуються інтерфейсу користувача, у моєму додатку, я зберігатиму це лише як спосіб вирішення.
sakiM

1
Для мого випадку я роблю на одній темі. mDataHolder.get (). removeAll (mHiddenGenre); mAdapter.notifyItemRangeRemoved (mExpandButtonPosition, mHiddenGenre.size ());
JehandadK

73

Це приклад оновлення даних із абсолютно новим вмістом. Ви можете легко змінити його відповідно до ваших потреб. Я вирішив це у своєму випадку, зателефонувавши:

notifyItemRangeRemoved(0, previousContentSize);

перед:

notifyItemRangeInserted(0, newContentSize);

Це правильне рішення і також згадується в цій публікації членом проекту AOSP.


2
Це рішення працює для мене. Я спробував тут багато відповідей, але вони не спрацьовують (я не перевіряв перше рішення розморожування)
AndroLife

Проблема у використанні цих методів створює таку непослідовність, навіть коли це робиться на одній нитці.
JehandadK

Я не користуюся notifyItemRangeInsertedцією проблемою і не маю цієї проблеми на деяких пристроях Samsung
користувач25

І тут зовсім поза темою. Автор не користувавсяnotifyItemRangeInserted
користувач25

1
Дякую! Це мені допомогло.
ДмитроКанунніков,

35

Я стикався з цим питанням один раз, і я вирішив це, обернувши його 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);

це, здається, працює для мене, але чи можете ви сказати, чому це працює?
Денніс Андерсон

Виправлено і для мене. Як ви передбачили, що це може бути причиною цієї аварії.
Рахул Растогі

1
Базовий клас методу LinearLayoutManager supportsPredictiveAnimations () повертає значення false за замовчуванням. Що ми отримуємо, замінивши тут метод? public boolean supportsPredictiveItemAnimations() { return false; }
М.

1
@ M.Hig Документація для LinearLayoutManagerговорить про те, що за замовчуванням є false, але це твердження хибне :-( Декомпільований код для LinearLayoutManagerцього має: public boolean supportsPredictiveItemAnimations () {return this.mPendingSavedState == null && this.mLastStackFromEnd == this.mStackFromEnd ;}
Клайд

Я використовую різні утиліти для оновлення адаптера перегляду рециркулятора, і ця відповідь усунула збій. Велике спасибі, шановний авторе!
Євген П.

29

Нова відповідь: Використовуйте DiffUtil для всіх оновлень RecyclerView. Це допоможе як у продуктивності, так і в помилках вище. Дивіться тут

Попередня відповідь: Це працювало для мене. Головне - не використовувати 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
Це найбільш ретельне рішення з хорошим поясненням. Дякую!
Сакібой

то яка мета використання notifyitemrangeinserted замість notifydatasetchanged (), @Bolling.
Ankur_009

@FilipLuch Ви можете пояснити, чому?
Sreekanth Karumanaghat

3
@SreekanthKarumanaghat впевнений, не знаю, чому я не пояснив причину. В основному він очищає, а потім відтворює всі предмети в списку. Як і в результатах пошуку, дуже часто ви отримуєте одні й ті ж предмети, або коли виконується оновлення, ви отримуєте ті самі предмети, і тоді ви в кінцевому підсумку відтворюєте все, що є марною продуктивністю. Використовуйте DiffUtils замість цього та оновлюйте лише зміни, а не всі елементи. Це як щоразу переходити від А до Я, але ти там змінив лише F.
Філіп Лукіяненко

2
DiffUtil - це прихований скарб. Дякую, що поділились!
Силерія

22

Причини, які викликали цю проблему:

  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 вам потрібно перевірити доступ усіх даних до списку переробників та переконатися, що в іншому потоці немає змін.

це працювало за моїм сценарієм, я не можу використовувати DiffUtil, оскільки у мене є спеціальні компоненти для переробників та адаптерів, і помилка трапляється саме в конкретних сценаріях, які відомі, мені просто потрібно було проклеїти її БЕЗ вдаючись до видалення аніматорів елементів, тому я просто
завернув

17

У мене була подібна проблема.

Проблема в коді помилки нижче:

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

Це чудово працювало для мене! Не впевнений, чому ми не можемо просто використовувати newList.size() - 1.
waseefakhtar

15

Відповідно до цього питання , проблема була вирішена і, ймовірно, була випущена деякий час приблизно на початку 2015 року . Цитата з тієї ж теми :

Це спеціально пов'язане з викликом notifyDataSetChanged. [...]

До речі, я настійно раджу не використовувати notifyDataSetChanged, оскільки це вбиває анімацію та продуктивність. Також у цьому випадку використання конкретних подій сповіщення допоможе вирішити проблему.

Якщо у вас все ще виникають проблеми з останньою версією бібліотеки підтримки, я б запропонував переглянути ваші дзвінки notifyXXX(зокрема, ваше використання notifyDataSetChanged) всередині адаптера, щоб переконатися, що ви дотримуєтесь (делікатного / незрозумілого) RecyclerView.Adapterдоговору. Також не забудьте опублікувати ці сповіщення в основному потоці.


16
насправді я не погоджуюся з вашою частиною щодо продуктивності, але notifyDataSetChanged () не вбиває анімацію, анімувати за допомогою notifyDataSetChanged (), a) виклик setHasStableIds (true) на вашому об'єкті RecyclerView.Adapter і b) замінює getItemId всередині адаптера, щоб повернути посилання унікальне довге значення для кожного ряду і перевірити це, анімації спрацьовують
PirateApp

@PirateApp. Ви можете розглянути коментар як відповідь. Я спробував це, і він працює чудово.
mr5

Неправда! Все ж отримуйте з консолі Google звіти про цю проблему. І пристрій звичайно - Samsung -Samsung Galaxy J3(2017) (j3y17lte), Android 8.0
користувач25

10

У мене була така ж проблема. Це було викликано тим, що я затримав повідомлення про адаптер про вставку елемента.

Але ViewHolderспробували перемалювати деякі дані на своєму огляді, і він розпочав RecyclerViewвимірювання та перерахунок кількості дітей - на той момент він вийшов з ладу (список елементів і його розмір уже оновлено, але адаптер ще не був повідомлений).


8

Це відбувається, коли ви вказали неправильне положення для 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 
 }

1
Чому notifyItemRangeInserted(initialSize, mChannelItemList.size()-1);і ні notifyItemRangeInserted(initialSize, list.size());?
CoolMind

Не зрозуміла. Ви змішали initialSizeі listрозмір. Отже, обидва ваші варіанти неправильні.
CoolMind

Для мене це працює, notifyItemRangeInserted(initialSize, list.size()-1);але я не розумію. Чому я повинен зменшити розмір вставленого на одиницю для itemCount?
сплетіння

7

Ще одна причина цієї проблеми - це виклик цих методів з неправильними індексами (індекси, яких НЕ сталося, вставляйте або видаляйте в них)

-оповістиItemRangeRemoved

-оповістиItemRemoved

-оповістиItemRangeInserted

-оповістиItemInserted

перевірити параметри індексів цих методів і переконатися, що вони точні та правильні.


2
Це була моя проблема. Виняток виникає при додаванні нічого до списку.
Разель

6

Ця помилка все ще не виправлена ​​в 23.1.1, але загальним рішенням було б вирішити виняток.


18
Ловити його де саме? Єдиний код у стеці стека - це власний код Android.
howettl

1
Ловіть це так само, як відповідь @saki_M.
Ренан Бандейра

Це насправді працює, хоча і для Ренана? Ви випробували виправлення деякий час? Помилка виникає лише зрідка, тому я побачу лише, чи працює це з часом.
Simon Simon

Це фактично працює, але деякі дитячі погляди в моєму непомітно залишаються
Девід

@david Послідовно залишається, що є наслідком цього?
Sreekanth Karumanaghat

4

Ця проблема викликана 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]
    }
});

Я перебуваю на головній темі під час виклику DiffUtil.calculateDiff (diffUtilForecastItemChangesAnlayser (this.mWeatherForecatsItemWithMainAndWeathers, weatherForecastItems)). DispatchUpdatesTo (this); журнал чистий на Thread: Thread [main, 5, main]
Mathias Seguy Android2ee

4

У моєму випадку кожного разу, коли я дзвоню notifyItemRemoved (0), він вийшов з ладу. Виявилося, що я встановив, setHasStableIds(true)і getItemIdя просто повернув позицію. Я закінчив її оновленням, щоб повернути hashCode()унікальний ідентифікатор елемента або самостійно визначений ідентифікатор, який вирішив проблему.


4

У моєму випадку я отримав цю проблему через отримання оновлень даних із сервера (я використовую Firebase Firestore), і коли перший набір даних обробляється DiffUtil у фоновому режимі, приходить інший набір оновлення даних і викликає проблему одночасності запустивши ще один DiffUtil.

Якщо коротко, якщо ви використовуєте DiffUtil у фоновому потоці, який потім повертається до головної нитки для відправки результатів до RecylerView, то ви маєте шанс отримати цю помилку, коли за короткий час надійде кілька оновлень даних.

Я вирішив це, дотримуючись порад у цьому чудовому поясненні: https://medium.com/@jonfhancock/get-threading-right-with-diffutil-423378e126d2

Просто для пояснення рішення полягає в натисканні оновлень, коли поточне працює на Deque. Потім deque може запустити оновлені оновлення після завершення поточного оновлення, отже, обробка всіх наступних оновлень, але також уникнення помилок неузгодженості!

Сподіваюся, це допомагає, тому що ця змусила мене почухати голову!


Дякуємо за посилання!
CoolMind

3

Проблема у мене виникла лише тоді, коли:

Я створив Адаптер із порожнім списком . Потім я вставив предмети і подзвонив notifyItemRangeInserted.

Рішення:

Я вирішив це, створивши Адаптер лише після того, як у мене з’явився перший фрагмент даних і відразу його ініціалізую. Наступний фрагмент можна було б вставити і notifyItemRangeInsertedвикликати без проблем.


Я не думаю, що це причина. У мене багато адаптерів із порожніми списками, потім додані елементи notifyItemRangeInserted, але ніколи цього не було.
CoolMind

3

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


або просто повідомити, якщо товар замість цього видалено
Ремаріо

моя модель використовує посилання контейнера ось чому
Ремаріо

3

У моєму випадку я раніше міняв дані всередині потоку з mRecyclerView.post (новий Runnable ...), а потім знову пізніше змінив дані в потоці інтерфейсу, що спричинило непослідовність.


1
у мене така ж ситуація, як і у вас, як ви її вирішили? дякую
baderkhane

2

Помилка може бути викликана тим, що ваші зміни не відповідають тому, що ви повідомляєте. У моєму випадку:

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

Що я, звичайно, повинен був зробити:

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

2

У моєму випадку проблема полягала в тому, що я використовував notifyDataSetChanged, коли кількість щойно завантажених даних була меншою, ніж початкові дані. Такий підхід допоміг мені:

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

Чому notifyDataSetChangedзалежить від нових даних? Я думав, що це оновить весь список.
CoolMind

2

Я зіткнувся з тією ж проблемою.

У моїй програмі використовуються компоненти навігації з фрагментом, що містить мій recilerView. Мій список добре відображався під час першого завантаження фрагмента ... але після відходу та повернення ця помилка сталася.

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

Виправлення полягало в ініціалізації адаптера в onCreateView.

Сподіваюся, це може комусь допомогти.


0

Я отримав цю помилку, оскільки помилково викликав метод видалення певного рядка з мого перегляду кілька разів. У мене був такий метод, як:

void removeFriends() {
    final int loc = data.indexOf(friendsView);
    data.remove(friendsView);
    notifyItemRemoved(loc);
}

Я випадково закликав цей метод тричі замість одного разу, тому другий раз locбув -1, і помилка була видана, коли він намагався його видалити. Два виправлення полягали в тому, щоб метод був викликаний лише один раз, а також додати перевірку правильності на зразок цього:

void removeFriends() {
    final int loc = data.indexOf(friendsView);
    if (loc > -1) {
        data.remove(friendsView);
        notifyItemRemoved(loc);
    }
}

0

У мене така ж проблема, і я прочитав, що це сталося лише в телефонах Samsung ... Але реальність показала, що це відбувається у багатьох брендах.

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

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

За допомогою цього рішення ви просто завантажуєте новий адаптер Arraylist у адаптер та новий адаптер для recilerView, а потім закінчуєте діяльність.

Сподіваюся, це комусь допоможе


0

Я отримав цю помилку, оскільки два рази помилково дзвонив "notifyItemInserted".


0

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

Моє рішення було додати "синхронізований (syncObject) {}" до всіх методів, що змінюють список даних. Таким чином, в будь-який момент часу лише один метод може прочитати цей список.


0

У моєму випадку дані адаптера змінилися. І я неправильно використовував notifyItemInserted () для цих змін. Коли я використовую notifyItemChanged, помилка зникла.


0

Я зіткнувся з тією ж проблемою, коли я видалив і оновив елементи у списку ... Після днів розслідування я думаю, що нарешті знайшов рішення.

Що вам потрібно зробити, це спершу зробити весь notifyItemChangedсвій список, а вже потім робити все notifyItemRemoved у порядку зменшення

Сподіваюся, це допоможе людям, які стикаються з тією ж проблемою ...


0

Я використовую курсор, тому я не можу використовувати 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)
}

0

Якщо проблема виникає після мультиконтакту, ви можете вимкнути багатоконтактне використання

android:splitMotionEvents="false" 

у файлі макета.


-1

Якщо ваші дані сильно змінюються, ви можете використовувати

 mAdapter.notifyItemRangeChanged(0, yourData.size());

або деякі окремі елементи вашого набору даних можуть бути використані

 mAdapter.notifyItemChanged(pos);

Для детального використання методів ви можете посилатися на документ , певним чином намагаючись не використовувати безпосередньо mAdapter.notifyDataSetChanged().


2
використання notifyItemRangeChangedтакож створює той самий збій.
lionelmessi

Це підходить до певної ситуації. Можливо, ви оновили свій набір даних як у фоновому потоці, так і у потоці користувальницького інтерфейсу, це також спричинить непослідовність. Якщо ви оновлюєте лише набір даних у потоці інтерфейсу, він буде працювати.
Аррон Чао
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.