RecyclerView мигает после notifyDatasetChanged()

У меня есть RecyclerView, который загружает некоторые данные из API, включает URL-адрес изображения и некоторые данные, и я использую networkImageView для ленивого изображения загрузки.

@Override
public void onResponse(List<Item> response) {
   mItems.clear();
   for (Item item : response) {
      mItems.add(item);
   }
   mAdapter.notifyDataSetChanged();
   mSwipeRefreshLayout.setRefreshing(false);
}

Вот реализация для адаптера:

public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, final int position) {
        if (isHeader(position)) {
            return;
        }
        // - get element from your dataset at this position
        // - replace the contents of the view with that element
        MyViewHolder holder = (MyViewHolder) viewHolder;
        final Item item = mItems.get(position - 1); // Subtract 1 for header
        holder.title.setText(item.getTitle());
        holder.image.setImageUrl(item.getImg_url(), VolleyClient.getInstance(mCtx).getImageLoader());
        holder.image.setErrorImageResId(android.R.drawable.ic_dialog_alert);
        holder.origin.setText(item.getOrigin());
    }

Проблема заключается в том, когда мы обновляемся в recyclerView, она близируется очень короткое время в начале, которое выглядит странно.

Я просто использовал GridView/ListView, и он работал так, как я ожидал. Не было блик.

для RecycleView в onViewCreated of my Fragment:

mRecyclerView = (RecyclerView) view.findViewById(R.id.recyclerView);
        // use this setting to improve performance if you know that changes
        // in content do not change the layout size of the RecyclerView
        mRecyclerView.setHasFixedSize(true);

        mGridLayoutManager = (GridLayoutManager) mRecyclerView.getLayoutManager();
        mGridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
            @Override
            public int getSpanSize(int position) {
                return mAdapter.isHeader(position) ? mGridLayoutManager.getSpanCount() : 1;
            }
        });

        mRecyclerView.setAdapter(mAdapter);

Кто-нибудь сталкивался с такой проблемой? что может быть причиной?

Ответ 1

Попробуйте использовать стабильные идентификаторы в вашем RecyclerView.Adapter

setHasStableIds(true) и переопределить getItemId(int position).

Без стабильных идентификаторов, после notifyDataSetChanged() ViewHolders обычно присваивается не тем же позициям. Это была причина моргания в моем случае.

Вы можете найти хорошее объяснение здесь.

Ответ 2

В соответствии с этой issue страницей.... это анимация изменения элемента по умолчанию recycleview... Вы можете отключить ее. Попробуйте это

recyclerView.getItemAnimator().setSupportsChangeAnimations(false);

Изменение последней версии

Цитата из Блог разработчиков Android:

Обратите внимание, что этот новый API не поддерживает обратную совместимость. Если вы ранее реализован ItemAnimator, вы можете расширить SimpleItemAnimator, который предоставляет старый API, обертывая новый API. Вы также заметите, что некоторые методы были полностью удалены из ItemAnimator. Например, если вы звонили recyclerView.getItemAnimator(). setSupportsChangeAnimations (ложь), этот код больше не будет компилироваться. Вы можете заменить его на:

ItemAnimator animator = recyclerView.getItemAnimator();
if (animator instanceof SimpleItemAnimator) {
  ((SimpleItemAnimator) animator).setSupportsChangeAnimations(false);
}

Ответ 3

Это просто сработало:

recyclerView.getItemAnimator().setChangeDuration(0);

Ответ 4

У меня та же проблема при загрузке изображения с некоторых URL, а затем мигает imageView. Решено с помощью

notifyItemRangeInserted()    

вместо

notifyDataSetChanged()

что позволяет перезагружать эти неизмененные старые данные.

Ответ 5

попробуйте отключить анимацию по умолчанию

ItemAnimator animator = recyclerView.getItemAnimator();

if (animator instanceof SimpleItemAnimator) {
  ((SimpleItemAnimator) animator).setSupportsChangeAnimations(false);
}

это новый способ отключить анимацию с поддержкой Android 23

этот старый способ будет работать для старой версии библиотеки поддержки

recyclerView.getItemAnimator().setSupportsChangeAnimations(false)

Ответ 6

Предполагая, что mItems - это коллекция, которая поддерживает ваш Adapter, почему вы удаляете все и повторно добавляете? В основном вы говорите, что все изменилось, поэтому RecyclerView перепроверяет все представления, чем я предполагаю, что библиотека изображений не обрабатывает ее должным образом, где она по-прежнему сбрасывает представление, даже если это тот же URL-адрес изображения. Возможно, у них было какое-то испеченное решение для AdapterView, так что он отлично работает в GridView.

Вместо вызова notifyDataSetChanged, который вызовет повторное связывание всех представлений, вызовите подробные уведомления о событиях (уведомите добавленные/удаленные/перемещенные/обновленные), чтобы RecyclerView перескакивал только нужные виды, и ничто не будет мерцать.

Ответ 7

В Kotlin вы можете использовать "расширение класса" для RecyclerView:

fun RecyclerView.disableItemAnimator() {
    (itemAnimator as? SimpleItemAnimator)?.supportsChangeAnimations = false
}

// sample of using in Activity:
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                          savedInstanceState: Bundle?): View? {
    // ...
    myRecyclerView.disableItemAnimator()
    // ...
}

Ответ 8

Recyclerview использует DefaultItemAnimator в качестве его аниматора по умолчанию. Как вы можете видеть из приведенного ниже кода, они меняют альфу владельца изображения при изменении позиции:

@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;
}

Я хотел сохранить оставшуюся часть анимации, но удалить "мерцание", чтобы я клонировал DefaultItemAnimator и удалил 3 альфа-строки выше.

Чтобы использовать новый аниматор, просто вызовите setItemAnimator() в RecyclerView:

mRecyclerView.setItemAnimator(new MyItemAnimator());

Ответ 9

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

Создается класс LruBitmapCache.java, чтобы получить размер кеша изображения

import android.graphics.Bitmap;
import android.support.v4.util.LruCache;
import com.android.volley.toolbox.ImageLoader.ImageCache;

public class LruBitmapCache extends LruCache<String, Bitmap> implements
        ImageCache {
    public static int getDefaultLruCacheSize() {
        final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
        final int cacheSize = maxMemory / 8;

        return cacheSize;
    }

    public LruBitmapCache() {
        this(getDefaultLruCacheSize());
    }

    public LruBitmapCache(int sizeInKiloBytes) {
        super(sizeInKiloBytes);
    }

    @Override
    protected int sizeOf(String key, Bitmap value) {
        return value.getRowBytes() * value.getHeight() / 1024;
    }

    @Override
    public Bitmap getBitmap(String url) {
        return get(url);
    }

    @Override
    public void putBitmap(String url, Bitmap bitmap) {
        put(url, bitmap);
    }
}

VolleyClient.java singleton class [extends Application] добавлен ниже кода

в конструкторе класса Singleton класса VolleyClient добавьте ниже фрагмент, чтобы инициализировать ImageLoader

private VolleyClient(Context context)
    {
     mCtx = context;
     mRequestQueue = getRequestQueue();
     mImageLoader = new ImageLoader(mRequestQueue,getLruBitmapCache());
}

Я создал метод getLruBitmapCache() для возврата LruBitmapCache

public LruBitmapCache getLruBitmapCache() {
        if (mLruBitmapCache == null)
            mLruBitmapCache = new LruBitmapCache();
        return this.mLruBitmapCache;
}

Надеюсь, он поможет вам.

Ответ 11

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

private int getCacheSize(Context context) {

    final DisplayMetrics displayMetrics = context.getResources().
            getDisplayMetrics();
    final int screenWidth = displayMetrics.widthPixels;
    final int screenHeight = displayMetrics.heightPixels;
    // 4 bytes per pixel
    final int screenBytes = screenWidth * screenHeight * 4;

    return screenBytes * 3;
}

Ответ 12

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

Я решил это, только уменьшив старое представление на 0,5 альфа и запустив newview alpha на 0,5. Это создало переход более мягкого замирания без полного исчезновения представления.

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

в animateChange:

ViewCompat.setAlpha(newHolder.itemView, 0);  //change 0 to 0.5f

в animateChangeImpl:

oldViewAnim.alpha(0).setListener(new VpaListenerAdapter() { //change 0 to 0.5f

Ответ 13

для меня recyclerView.setHasFixedSize(true); работал

Ответ 14

Использование соответствующих методов recyclerview для обновления представлений позволит решить эту проблему.

Во-первых, внесите изменения в список

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

Затем сообщите с помощью

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

Надеюсь, это поможет!