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

アダプターの実装は次のとおりです。

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()で何をオーバーライドする必要がありますか?おかげで
バラズズ・オルバン

3
どのようにIDを生成する必要があるかについてのアイデアはありますか?
Mauker

あなたは素晴らしいです!Googleは違います。グーグルはこれについて知らないか、彼らが知っていて私たちに知られたくないのです!THANK YOU MAN
MBH

1
アイテムの位置をめちゃくちゃにします。アイテムは正しい順序でFirebaseデータベースから取得されます。
MuhammadAliJr

1
@Maukerオブジェクトに一意の番号がある場合はそれを使用でき、一意の文字列がある場合はobject.hashCode()を使用できます。それは私にとって完全にうまく機能します
Simon Schubert

106

この問題のページによると、これはデフォルトのリサイクルビューアイテム変更アニメーションです...オフにすることができます。

recyclerView.getItemAnimator().setSupportsChangeAnimations(false);

最新バージョンでの変更

Android開発者ブログからの引用:

この新しいAPIには下位互換性がないことに注意してください。以前にItemAnimatorを実装した場合は、代わりにSimpleItemAnimatorを拡張できます。これは、新しいAPIをラップすることで古いAPIを提供します。また、一部のメソッドがItemAnimatorから完全に削除されていることにも気づくでしょう。たとえば、recyclerView.getItemAnimator()。setSupportsChangeAnimations(false)を呼び出していた場合、このコードはコンパイルできなくなります。あなたはそれを次のように置き換えることができます:

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

5
@sabeerはまだ同じ問題です。問題は解決していません。
Shreyash Mahajan

3
@deliveこれに対する解決策を見つけた場合はお知らせください
Shreyash Mahajan

私はピカソを使っています。
2016年

8
Kotlin:(recyclerView.itemAnimator as?SimpleItemAnimator)?. supportsChangeAnimations = false
Pedro Paulo Amorim

それは機能していますが、他の問題が発生しています(NPE)java.lang.NullPointerException:nullオブジェクト参照でフィールド 'int android.support.v7.widget.RecyclerView $ ItemAnimator $ ItemHolderInfo.left'から読み取ろうとしていますこれを解決するには?
Navas pk 2017年

46

これは単に機能しました:

recyclerView.getItemAnimator().setChangeDuration(0);

これは、codeItemAnimatorに代わる優れた方法ですanimator = recyclerView.getItemAnimator(); if(animator instanceof SimpleItemAnimator){((SimpleItemAnimator)animator).setSupportsChangeAnimations(false); }code
ziniestro 2016

1
すべてのアニメーションの追加と削除を停止します
MBH 2017

11

同じ問題をいくつかのURLからロードすると、imageViewが点滅します。を使用して解決

notifyItemRangeInserted()    

の代わりに

notifyDataSetChanged()

これにより、変更されていない古いデータの再読み込みが回避されます。


9

デフォルトのアニメーションを無効にするには、これを試してください

ItemAnimator animator = recyclerView.getItemAnimator();

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

これは、Androidサポート以降、アニメーションを無効にする新しい方法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で正確に行ったことです。ご覧
Ali

LruCache <String、Bitmap>クラスを使用して一度チェックするだけで、問題が解決すると思います。見てみましょうLruCache
Androidの学習者

コメントで共有したコードを一度確認しましたか?私がそこで見逃したクラスには何がありますか?
Ali

LruCache <String、Bitmap>クラスの拡張と、私が行った方法のようなsizeOf()メソッドのオーバーライドが欠落しています。残りはすべて問題ないようです。
Android学習者

わかりました、すぐに試してみますが、魔法を使って問題を解決するために何をしたのですか?sourcecode私にとっては、オーバーライドされたsizeOfメソッドがソースコードに含まれているはずです。
Ali

1

私のためにrecyclerView.setHasFixedSize(true);働いた


OPのアイテムのサイズが固定されているとは思いません
Irfandi D. Vendy

これは私の場合、私を助けました
bst91

1

私の場合、上記のいずれも、同じ問題を持つ他のスタックオーバーフローの質問からの回答も機能しませんでした。

ええと、アイテムがクリックされるたびにカスタムアニメーションを使用していました。そのために、notifyItemChanged(int position、Object Payload)を呼び出して、CustomAnimatorクラスにペイロードを渡しました。

RecyclerViewアダプターでは、2つのonBindViewHolder(...)メソッドを使用できます。3つのパラメーターを持つonBindViewHolder(...)メソッドは、常に2つのパラメーターを持つonBindViewHolder(...)メソッドの前に呼び出されます。

一般に、2つのパラメーターを持つonBindViewHolder(...)メソッドを常にオーバーライドします。問題の主な根本は同じことでした。notifyItemChanged(...)が呼び出されるたびに、onBindViewHolder(...)メソッドがピカソを使用してImageViewに自分の画像を読み込んでいたため、メモリやインターネットからの読み込みに関係なく、再び読み込まれました。ロードされるまでは、プレースホルダーの画像が表示されていました。これが、アイテムビューをクリックするたびに1秒間点滅する理由でした。

その後、3つのパラメーターを持つ別のonBindViewHolder(...)メソッドもオーバーライドします。ここでは、ペイロードのリストが空かどうかを確認し、このメソッドのスーパークラス実装を返します。それ以外の場合は、ペイロードがある場合は、ホルダーのitemViewのアルファ値を1に設定します。

そして、悲しいことに丸一日を浪費した後、問題の解決策を得ました!

次に、onBindViewHolder(...)メソッドのコードを示します。

onBindViewHolder(...)2つのパラメーター:

@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(...)と3つのパラメータ:

@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

私の場合、はるかに単純な問題がありましたが、上記の問題と非常によく似ています。ExpandableListViewをGroupieで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 alphaだけフェーディングし、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

0

適切なrecyclerviewメソッドを使用してビューを更新すると、この問題が解決します

まず、リストに変更を加えます

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

次に、使用して通知します

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

これが役立つことを願っています!!


絶対違う。秒単位でいくつかの更新を行っている場合(私の場合はBLEスキャン)、まったく機能しません。私はこのたわごとをRecyclerAdapterに更新するために1日を費やしました... ArrayAdapterを保持する方が良いです。MVCパターンを使用していないのは残念ですが、少なくともほぼ使用可能です。
Gojir4


0

Kotlinソリューション:

(recyclerViewIdFromXML.itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.