web-dev-qa-db-ja.com

RecyclerView:非同期画像読み込み

RecyclerViewを含むリストを表示するためにimageViewを使用しています。 UIをより流makeにするには、SDカードに保存された58dpのサムネイルをasyncTaskでこれらのimageViewsにロードします。

問題は、childViewが視覚的に表示されると、別のデータの古いイメージが再利用され、AsyncTaskが終了すると置き換えられることです。 imageViewonPreExecuteビットマップをnullに設定することでシャッフルを停止できます。

古い画像を本当に再利用する方法はありますか、それとも新しいViewが配置されるたびにSDカードから画像をロードする必要がありますか?これは、最初に間違った画像があるか、画像が真っ白であるため、ビューを非常に見苦しくします。

21
Paul Woitaschek

ビューを再利用するため、コンテンツが既に含まれているビューを取得するため、ListViewsパターンを使用している場合は、これもViewHolderで問題になります。

ここには2つの解決策があります。良い習慣と悪いハックです。

  • bindViewHolder(VH holder, int position)などを使用して、setDrawable(null)の先頭に何も表示しないようにImageViewを設定することをお勧めします。

  • 不正なハックでは、ViewHolderパターンを強制せずにビューをリサイクル/再利用せず、毎回それを膨張させますが、それはListViewおよび他の古いコンポーネントでのみ許可されます。

32

ユニバーサルイメージローダー を確認する必要があります。メモリキャッシュ、ディスクキャッシュがあり、画像を非同期に読み込むため、UIをブロックしません。デフォルトの画像を設定したり、画像の取得に失敗したりできます。画像をサンプリングして、ビットマップのメモリフットプリントを減らすことができます。画像に使用することをお勧めします。

無意味であるため、ケースでリサイクル可能を無効にしないでください。適切にサンプリングされない場合、ビットマップドロウアブルは非常に高いメモリオーバーロードを生成するため、画像はリサイクルする必要があります。

RecyclerViewAdapterの使用例:

@Override
public void onBindViewHolder(CustomViewHolder viewHolder, int position) {
    String imageUri = "";//local or remote image uri address
    //viewHolder.imgView: reference to your imageview
    //before you call the displayImage you have to 
    //initialize imageloader in anywhere in your code for once.   
    //(Generally done in the Application class extender.)
    ImageLoader.getInstance().displayImage(imageUri, viewHolder.imgView);
}

編集:最近、私はGlideをメインの画像読み込みおよびキャッシュライブラリとして考えています。次のように使用できます。

Glide.with(context)
    .load(imageUri)
    .placeholder(R.drawable.myplaceholder)
    .into(imageView);
8
Gunhan

新しいリクエストを開始する前に古いリクエストをキャンセルしてくださいスクロールと小さな画像)。

解決策は次のとおりです。

  1. OnBindViewHolder中にビューホルダーに一意の識別子を保存します(これは同期的に行われるため、VHがリサイクルされると上書きされます)。
  2. 次に、イメージを非同期的に(AsynchTask、RxJavaなどを使用して)ロードし、参照のために非同期呼び出しでこの一意のIDを渡します
  3. 最後に、後処理用のイメージロードメソッド(AsyncTasksのonPostExecute)で、非同期要求で渡されたIDがビューホルダーに存在する現在のIDと同じであることを確認します。

RxJavaを使用してバックグラウンドでアプリからアイコンを読み込む例:

 public void loadIcon(final ImageView appIconView, final ApplicationInfo appInfo, final String uniqueAppID) {
    Single.fromCallable(() -> {
            return appIconView.getContext().getPackageManager().getApplicationIcon(appInfo);
        })
          .subscribeOn(Schedulers.computation())
          .observeOn(AndroidSchedulers.mainThread())
          .subscribe( drawable -> {
                 if (uniqueAppID.equals(mUniqueAppID)) { // Show always the correct app icon
                    appIconView.setImageDrawable(drawable);
                 }
             }
         );
}

ここで、mUniqueAppIDは、onBindViewHolderによって変更されたビューホルダーのフィールドです。

5
Sebas LG

「onBindViewHolder」メソッドで古いリクエストをキャンセルする必要があります。

try{
        ((SpecialOfferViewHolder)viewHolder).imageContainer.cancelRequest();
}catch(Exception e) {

}

viewHolderに画像コンテナを保存することを忘れないでください:

public void onResponse(ImageContainer response, boolean arg1) {
                ((SpecialOfferViewHolder)viewHolder).imageContainer=response;

}

私はグッドプラクティスに追加します:

良い習慣では、setDrawable(null)などを使用して、bindViewHolder(VHホルダー、int位置)の先頭に何も表示しないようにImageViewを設定します。

何も表示するのではなく、ローダーの画像を表示して、そのビューで処理が行われ、しばらくして結果が表示されるというフィードバックをユーザーに提供します。空白のビューのみを表示するのは良い習慣ではありません。ユーザーにフィードバックを提供する必要があります。

0
Dieguinho