RecyclerView blinking after notifyDatasetChanged()

前端 未结 18 1666
悲&欢浪女
悲&欢浪女 2020-12-07 16:20

I have a RecyclerView which loads some data from API, includes an image url and some data, and I use networkImageView to lazy load image.

@Override
public vo         


        
相关标签:
18条回答
  • 2020-12-07 16:38

    Recyclerview uses DefaultItemAnimator as it's default animator. As you can see from the code below, they change the alpha of the view holder upon item change:

    @Override
    public boolean animateChange(RecyclerView.ViewHolder oldHolder, RecyclerView.ViewHolder newHolder, int fromX, int fromY, int toX, int toY) {
        ...
        final float prevAlpha = ViewCompat.getAlpha(oldHolder.itemView);
        ...
        ViewCompat.setAlpha(oldHolder.itemView, prevAlpha);
        if (newHolder != null) {
            ....
            ViewCompat.setAlpha(newHolder.itemView, 0);
        }
        ...
        return true;
    }
    

    I wanted to retain the rest of the animations but remove the "flicker" so I cloned DefaultItemAnimator and removed the 3 alpha lines above.

    To use the new animator just call setItemAnimator() on your RecyclerView:

    mRecyclerView.setItemAnimator(new MyItemAnimator());
    
    0 讨论(0)
  • 2020-12-07 16:40

    I have the same issue loading image from some urls and then imageView blinks. Solved by using

    notifyItemRangeInserted()    
    

    instead of

    notifyDataSetChanged()
    

    which avoids to reload those unchanged old datas.

    0 讨论(0)
  • 2020-12-07 16:42

    Using appropriate recyclerview methods to update views will solve this issue

    First, make changes in the list

    mList.add(item);
    or mList.addAll(itemList);
    or mList.remove(index);
    

    Then notify using

    notifyItemInserted(addedItemIndex);
    or
    notifyItemRemoved(removedItemIndex);
    or
    notifyItemRangeChanged(fromIndex, newUpdatedItemCount);
    

    Hope this will help!!

    0 讨论(0)
  • 2020-12-07 16:43

    In my case I used SwipeRefresh and RecycleView with viewmodel binding and faced with blinking. Solved with -> Use submitList() to keep the list updated because DiffUtils have done the job, otherwise the list reloading entirely refer to CodeLab https://developer.android.com/codelabs/kotlin-android-training-diffutil-databinding#4

    0 讨论(0)
  • 2020-12-07 16:44

    This simply worked:

    recyclerView.getItemAnimator().setChangeDuration(0);
    
    0 讨论(0)
  • 2020-12-07 16:45

    In my case, neither any of above nor the answers from other stackoverflow questions having same problems worked.

    Well, I was using custom animation each time the item gets clicked, for which I was calling notifyItemChanged(int position, Object Payload) to pass payload to my CustomAnimator class.

    Notice, there are 2 onBindViewHolder(...) methods available in RecyclerView Adapter. onBindViewHolder(...) method having 3 parameters will always be called before onBindViewHolder(...) method having 2 parameters.

    Generally, we always override the onBindViewHolder(...) method having 2 parameters and the main root of problem was I was doing the same, as each time notifyItemChanged(...) gets called, our onBindViewHolder(...) method will be called, in which I was loading my image in ImageView using Picasso, and this was the reason it was loading again regardless of its from memory or from internet. Until loaded, it was showing me the placeholder image, which was the reason of blinking for 1 sec whenever I click on the itemview.

    Later, I also override another onBindViewHolder(...) method having 3 parameters. Here I check if the list of payloads is empty, then I return the super class implementation of this method, else if there are payloads, I am just setting the alpha value of the itemView of holder to 1.

    And yay I got the solution to my problem after wasting a one full day sadly!

    Here's my code for onBindViewHolder(...) methods:

    onBindViewHolder(...) with 2 params:

    @Override
    public void onBindViewHolder(@NonNull RecyclerAdapter.ViewHolder viewHolder, int position) {
                Movie movie = movies.get(position);
    
                Picasso.with(context)
                        .load(movie.getImageLink())
                        .into(viewHolder.itemView.posterImageView);
        }
    

    onBindViewHolder(...) with 3 params:

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position, @NonNull List<Object> payloads) {
            if (payloads.isEmpty()) {
                super.onBindViewHolder(holder, position, payloads);
            } else {
                holder.itemView.setAlpha(1);
            }
        }
    

    Here's the code of method I was calling in onClickListener of viewHolder's itemView in onCreateViewHolder(...):

    private void onMovieClick(int position, Movie movie) {
            Bundle data = new Bundle();
            data.putParcelable("movie", movie);
    
            // This data(bundle) will be passed as payload for ItemHolderInfo in our animator class
            notifyItemChanged(position, data);
        }
    

    Note: You can get this position by calling getAdapterPosition() method of your viewHolder from onCreateViewHolder(...).

    I have also overridden getItemId(int position) method as follows:

    @Override
    public long getItemId(int position) {
        Movie movie = movies.get(position);
        return movie.getId();
    }
    

    and called setHasStableIds(true); on my adapter object in activity.

    Hope this helps if none of the answers above work!

    0 讨论(0)
提交回复
热议问题