notifyDatasetChanged () 후 RecyclerView 깜박임


113

API에서 일부 데이터를로드하고 이미지 URL과 일부 데이터를 포함하는 RecyclerView가 있으며 networkImageView를 사용하여 이미지를 지연로드합니다.

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

다음은 Adapter 구현입니다.

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

그런 문제에 직면 한 사람이 있습니까? 이유가 무엇일까요?


답변:


126

RecyclerView.Adapter에서 안정적인 ID를 사용해보십시오.

setHasStableIds(true) 및 재정의 getItemId(int position) .

안정적인 ID없이 notifyDataSetChanged() 일반적으로 ViewHolders가 동일한 위치에 할당되지 않습니다. 그것이 제 경우에 깜박이는 이유였습니다.

여기 에서 좋은 설명을 찾을 수 있습니다 .


1
깜박임을 해결 한 setHasStableIds (true)를 추가했지만 GridLayout에서 항목은 스크롤 할 때 여전히 위치가 변경됩니다. getItemId ()에서 무엇을 재정의해야합니까? 감사합니다
Balázs Orbán

3
ID를 생성하는 방법에 대한 아이디어가 있습니까?
Mauker

당신은 대단합니다! Google은 아닙니다. Google은 이것에 대해 모르거나 알고 있고 우리가 알기를 원하지 않습니다! THANK YOU MAN
MBH

1
항목 위치를 엉망으로 만들면 항목이 올바른 순서로 firebase 데이터베이스에서 제공됩니다.
MuhammadAliJr

1
@Mauker 객체에 고유 번호가있는 경우 해당 번호를 사용하거나 고유 한 문자열이있는 경우 object.hashCode ()를 사용할 수 있습니다. 그것은 나를 위해 잘 완벽하게 작동
사이먼 슈베르트에게

106

문제 페이지 에 따르면 .... 기본 recycleview 항목 변경 애니메이션입니다 ... 해제 할 수 있습니다 .. 시도해보세요.

recyclerView.getItemAnimator().setSupportsChangeAnimations(false);

최신 버전 변경

Android 개발자 블로그 에서 인용 :

이 새로운 API는 이전 버전과 호환되지 않습니다. 이전에 ItemAnimator를 구현 한 경우 대신 새 API를 래핑하여 이전 API를 제공하는 SimpleItemAnimator를 확장 할 수 있습니다. 또한 일부 메서드가 ItemAnimator에서 완전히 제거되었음을 알 수 있습니다. 예를 들어, recyclerView.getItemAnimator (). setSupportsChangeAnimations (false)를 호출했다면이 코드는 더 이상 컴파일되지 않습니다. 다음으로 바꿀 수 있습니다.

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

5
@sabeer는 여전히 같은 문제입니다. 문제가 해결되지 않습니다.
Shreyash 마하 잔

3
@delive이 어떤 해결책을 찾을 경우 알려
Shreyash 마하 잔을

나는 피카소를 사용하고 있는데, 그 무언가는 깜박 거리지 않습니다.

8
코 틀린 (같은 SimpleItemAnimator recyclerView.itemAnimator?)? supportsChangeAnimations = 거짓
페드로 파올로 아모

작동하고 있지만 다른 문제가 발생합니다 (NPE) java.lang.NullPointerException : null 개체 참조의 'int android.support.v7.widget.RecyclerView $ ItemAnimator $ ItemHolderInfo.left'필드에서 읽기 시도 방법이 있습니까? 이 문제를 해결하려면?
Navas pk

46

이것은 단순히 작동했습니다.

recyclerView.getItemAnimator().setChangeDuration(0);

codeItemAnimator animator = recyclerView.getItemAnimator (); 의 좋은 대안입니다 . if (animator instanceof SimpleItemAnimator) {((SimpleItemAnimator) animator) .setSupportsChangeAnimations (false); }code
ziniestro

1
모든 애니메이션 ADD 및 REMOVE 중지
MBH

11

일부 URL에서 이미지를로드하는 데 동일한 문제가 발생한 다음 imageView가 깜박입니다. 사용하여 해결

notifyItemRangeInserted()    

대신에

notifyDataSetChanged()

변경되지 않은 이전 데이터를 다시로드하지 않습니다.


9

기본 애니메이션을 비활성화하려면 이것을 시도하십시오.

ItemAnimator animator = recyclerView.getItemAnimator();

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

이것은 안드로이드 지원 23 이후 애니메이션을 비활성화하는 새로운 방법입니다.

이 이전 방식은 이전 버전의 지원 라이브러리에서 작동합니다.

recyclerView.getItemAnimator().setSupportsChangeAnimations(false)

4

mItems당신을 뒷받침하는 컬렉션 이라고 가정하면Adapter 모든 것을 제거하고 다시 추가하는 이유는 무엇입니까? 기본적으로 모든 것이 변경되었다고 말하고 있으므로 RecyclerView는 이미지 라이브러리가 동일한 이미지 URL이더라도 뷰를 재설정하는 곳에서 제대로 처리하지 않는다고 가정하는 것보다 모든 뷰를 리 바인드합니다. 아마도 그들은 GridView에서 잘 작동하도록 AdapterView에 대한 솔루션으로 구워진 것입니다.

notifyDataSetChanged모든 뷰를 리 바인딩 할 것을 호출 하는 대신 RecyclerView가 필요한 뷰만 리 바인딩하고 아무것도 깜박이지 않도록 세분화 된 알림 이벤트 (추가 / 제거 / 이동 / 업데이트 알림)를 호출합니다.


3
아니면 RecyclerView의 "버그"일까요? 분명히 AbsListView에서 지난 6 년 동안 잘 작동했지만 지금은 RecyclerView에서는 작동하지 않는다면 RecyclerView에서 뭔가 문제가 있음을 의미합니다. :) 간단히 살펴보면 ListView 및 GridView에서 데이터를 새로 고칠 때 뷰 + 위치를 추적하므로 새로 고칠 때 정확히 동일한 뷰 홀더를 얻게됩니다. RecyclerView가 뷰 홀더를 섞는 동안 깜박임이 발생합니다.
vovkab

ListView에서 작업 한다고해서 RecyclerView에 맞는 것은 아닙니다 . 이러한 구성 요소는 아키텍처가 다릅니다.
yigit

1
동의하지만, 예를 들어 커서를 사용하거나 전체 데이터를 새로 고치는 경우와 같이 어떤 항목이 변경되었는지 알기가 정말 어렵습니다. 따라서 recyclerview는이 경우도 올바르게 처리해야합니다.
vovkab

4

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 개의 알파 라인을 제거했습니다.

새 애니메이터를 사용하려면 RecyclerView에서 setItemAnimator ()를 호출하면됩니다.

mRecyclerView.setItemAnimator(new MyItemAnimator());

깜박임을 제거했지만 어떤 이유로 깜박임 효과가 발생했습니다.
Mohamed Medhat

4

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()
    // ...
}

1

안녕하세요 @Ali 그것은 늦게 재생 될 수 있습니다. 나는 또한이 문제에 직면하고 아래 해결책으로 해결했습니다. 확인하는 데 도움이 될 수 있습니다.

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 싱글 톤 클래스 [애플리케이션 확장] 코드 아래에 추가됨

VolleyClient 싱글 톤 클래스 생성자에서 아래 스 니펫을 추가하여 ImageLoader를 초기화합니다.

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

LruBitmapCache를 반환하기 위해 getLruBitmapCache () 메서드를 만들었습니다.

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

도움이되기를 바랍니다.


답변 해 주셔서 감사합니다. 이것이 VollyClient.java에서 정확히 수행 한 작업입니다. : 한 번 봐 가지고 VolleyClient.java
알리

LruCache <String, Bitmap> 클래스를 사용하여 한 번만 확인하면 문제가 해결 될 것이라고 생각합니다. LruCache
Android 학습자

댓글로 공유 한 코드를 한번 확인 하셨나요? 수업에서 내가 놓친 것이 무엇입니까?
알리

LruCache <String, Bitmap> 클래스를 확장하고 내가 한 것처럼 sizeOf () 메서드를 재정의하지 못하고 나머지는 모두 괜찮아 보입니다.
Android 학습자

좋아, 곧 시도해 볼 수 있지만 거기에서 당신에게 마법을 준 일을 설명하고 문제를 해결해 주시겠습니까? 소스 코드 당신의 오버라이드 (override) SIZEOF 방법은 소스 코드에 있어야처럼 나를 위해, 그것은 소리가 난다.
알리

1

나를 recyclerView.setHasFixedSize(true);위해 일했다


나는 OP의 항목을 수정 한 크기를 생각하지 않는다
Irfandi D. Vendy

이것은 제 경우에 도움이되었습니다
bst91

1

제 경우에는 위의 어느 것도 동일한 문제를 가진 다른 스택 오버플로 질문의 답변이 작동하지 않았습니다.

글쎄, 나는 항목을 클릭 할 때마다 사용자 정의 애니메이션을 사용하고 있었는데,이를 위해 notifyItemChanged (int position, Object Payload)를 호출하여 페이로드를 CustomAnimator 클래스에 전달했습니다.

RecyclerView Adapter에는 2 개의 onBindViewHolder (...) 메서드가 있습니다. 3 개의 매개 변수가있는 onBindViewHolder (...) 메소드는 항상 2 개의 매개 변수를 갖는 onBindViewHolder (...) 메소드보다 먼저 호출됩니다.

일반적으로 2 개의 매개 변수가있는 onBindViewHolder (...) 메서드를 항상 재정의하고 문제의 주된 원인은 notifyItemChanged (...)가 호출 될 때마다 onBindViewHolder (...) 메서드가 내가 피카소를 사용하여 ImageView에서 내 이미지를로드하고 있었는데 이것이 메모리 나 인터넷에 관계없이 다시로드되는 이유였습니다. 로드 될 때까지 자리 표시 자 이미지가 표시되었는데, 이것이 항목보기를 클릭 할 때마다 1 초 동안 깜박이는 이유였습니다.

나중에 3 개의 매개 변수가있는 다른 onBindViewHolder (...) 메서드도 재정의합니다. 여기서는 페이로드 목록이 비어 있는지 확인한 다음이 메서드의 슈퍼 클래스 구현을 반환합니다. 그렇지 않으면 페이로드가있는 경우 홀더의 itemView 알파 값을 1로 설정합니다.

그리고 yay 하루 종일 슬프게도 낭비 한 후 내 문제에 대한 해결책을 얻었습니다!

다음은 onBindViewHolder (...) 메서드에 대한 코드입니다.

2 개의 매개 변수가있는 onBindViewHolder (...) :

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

3 개의 매개 변수가있는 onBindViewHolder (...) :

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

다음은 onCreateViewHolder (...)에있는 viewHolder의 itemView의 onClickListener에서 호출 한 메서드 코드입니다.

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

참고 : onCreateViewHolder (...)에서 viewHolder의 getAdapterPosition () 메서드를 호출하여이 위치를 가져올 수 있습니다.

또한 다음과 같이 getItemId (int position) 메서드를 재정의했습니다.

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

setHasStableIds(true);활동에서 내 어댑터 개체를 호출 했습니다.

위의 답변 중 어느 것도 작동하지 않으면 도움이되기를 바랍니다.


1

제 경우에는 훨씬 더 간단한 문제가 있었지만 위의 문제와 매우 비슷해 보이거나 느낄 수 있습니다. Groupie를 사용하여 ExpandableListView를 RecylerView로 변환했습니다 (Groupie의 ExpandableGroup 기능 사용). 내 초기 레이아웃에는 다음과 같은 섹션이 있습니다.

<androidx.recyclerview.widget.RecyclerView
  android:id="@+id/hint_list"
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:background="@android:color/white" />

layout_height를 "wrap_content"로 설정하면 확장 된 그룹에서 축소 된 그룹으로의 애니메이션이 깜박이는 것처럼 느껴졌지만 실제로는 "잘못된"위치 (이 스레드에서 대부분의 권장 사항을 시도한 후에도)에서 애니메이션되었습니다.

어쨌든 단순히 layout_height를 match_parent로 변경하면 문제가 해결되었습니다.

<androidx.recyclerview.widget.RecyclerView
  android:id="@+id/hint_list"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:background="@android:color/white" />

0

비슷한 문제가 있었고 이것은 나를 위해 일했습니다.이 메서드를 호출하여 이미지 캐시의 크기를 설정할 수 있습니다.

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

0

내 응용 프로그램의 경우 일부 데이터가 변경되었지만 전체보기가 깜박이는 것을 원하지 않았습니다.

나는 oldview를 0.5 알파 아래로 페이드하고 newview 알파를 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

0

적절한 recyclerview 메서드를 사용하여 뷰를 업데이트하면이 문제가 해결됩니다.

먼저 목록을 변경합니다.

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

그런 다음

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

이것이 도움이되기를 바랍니다 !!


절대적으로하지. 몇 초 단위로 업데이트하는 경우 (제 경우에는 BLE 스캔) 전혀 작동하지 않습니다. 이 똥 RecyclerAdapter로 업데이트하는 데 하루를 보냈습니다. ArrayAdapter를 유지하는 것이 좋습니다. MVC 패턴을 사용하지 않는 것은 부끄러운 일이지만 적어도 거의 사용할 수 있습니다.
Gojir4


0

Kotlin 솔루션 :

(recyclerViewIdFromXML.itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.