9

ArrayAdapter を拡張するカスタム アダプターがあります。ビュー ホルダー パターンを実装して、Web サービスからのデータ (テキスト + 画像) を表示します。

画像の遅延読み込みには、Android 開発者向けサイトの高度なトレーニングからの非同期タスク パターンを使用します。

また、ディスク + RAM ビットマップ キャッシュも使用します。

取得する追加データがある場合は、クリックすると Web サービスから追加データが取得され、アダプターに追加されるフッター ビューを追加します。

問題は、この新しいデータが追加されているときに、表示されている画像の一部が変更され、すぐに元に戻るため、奇妙なちらつきが発生することです。

それ以外はすべて正常に機能しており、スクロールはスムーズです。

私が理解している限り、これらの画像の変更は、新しいデータが追加されたときに表示されるビューが更新されているときに発生しています。

この望ましくない動作を回避する方法はありますか?

これは、非同期タスクのダウンロードと管理を行うクラスです

public class ImageDownloader {
private ImageCache mCache;
private int reqWidth;
private int reqHeight;

public void download(String url, ImageView imageView, ImageCache imageCache, int reqHeight, int reqWidth) {
    mCache = imageCache;
    this.reqHeight = reqHeight;
    this.reqWidth = reqWidth;

    if (cancelPotentialDownload(url, imageView)) {
        BitmapDownloaderTask task = new BitmapDownloaderTask(imageView);

        DownloadedDrawable downloadedDrawable = new DownloadedDrawable(task);
        imageView.setImageDrawable(downloadedDrawable);
        task.execute(url);
    }
}

private class BitmapDownloaderTask extends AsyncTask<String, Void, Bitmap> {
    private String url;
    private WeakReference<ImageView> imageViewReference;

    public BitmapDownloaderTask(ImageView imageView) {
        imageViewReference = new WeakReference<ImageView>(imageView);
    }

    @Override
    protected Bitmap doInBackground(String... strings) {
        Bitmap bitmap = null;
        try {
            bitmap =  mCache.getBitmapFromURL(strings[0], reqWidth, reqHeight);
        } catch (IOException e) {
        }

        return bitmap;
    }

    @Override
    protected void onPostExecute(Bitmap bitmap) {
        if (isCancelled()) bitmap = null;

        if (imageViewReference != null) {
            ImageView imageView = imageViewReference.get();
            BitmapDownloaderTask bitmapDownloaderTask = getBitmapDownloaderTask(imageView);

            if (imageView != null) {
                imageView.setImageBitmap(bitmap);
            }
        }
    }
}

private static class DownloadedDrawable extends ColorDrawable {
    private WeakReference<BitmapDownloaderTask> bitmapDownloaderTaskReference;

    public DownloadedDrawable(BitmapDownloaderTask bitmapDownloaderTask) {
        bitmapDownloaderTaskReference = new WeakReference<BitmapDownloaderTask>(bitmapDownloaderTask);
    }

    public BitmapDownloaderTask getBitmapDownloaderTask() {
        return bitmapDownloaderTaskReference.get();
    }
}

private static BitmapDownloaderTask getBitmapDownloaderTask(ImageView imageView) {
    if (imageView != null) {
        Drawable drawable = imageView.getDrawable();
        if (drawable instanceof DownloadedDrawable) {
            DownloadedDrawable downloadedDrawable = (DownloadedDrawable)drawable;
            return downloadedDrawable.getBitmapDownloaderTask();
        }
    }
    return null;
}

private static boolean cancelPotentialDownload(String url, ImageView imageView) {
    BitmapDownloaderTask bitmapDownloaderTask = getBitmapDownloaderTask(imageView);

    if (bitmapDownloaderTask != null) {
        String bitmapUrl = bitmapDownloaderTask.url;
        if ((bitmapUrl == null) || (!bitmapUrl.equals(url))) {
            bitmapDownloaderTask.cancel(true);
        } else {
            return false;
        }
    }
    return true;
}

}

これはアダプターです:

プライベート クラス PlaceAdapter extends ArrayAdapter { final int viewResourceId;

    public PlaceAdapter(Context context, int textViewResourceId, List<PlaceModel> objects) {
        super(context, textViewResourceId, objects);
        viewResourceId = textViewResourceId;
    }

    @Override
    public View getView(final int position, View convertView, ViewGroup parent) {
        ViewHolder holder;

        if (convertView == null) {
            LayoutInflater inflater = getLayoutInflater();
            convertView = inflater.inflate(viewResourceId, null);

            holder = new ViewHolder(convertView);
            convertView.setTag(holder);
        } else {
            holder = (ViewHolder) convertView.getTag();
        }

        PlaceModel place = getItem(position);

        holder.name.setText(place.getName());
        holder.address.setText(place.getVicinity());
        holder.position = position;

        if (place.getIcon() != null) {
            String url = mImageViewUrls.get(holder.image);
            if (url == null || (url != null && !url.equals(place.getIcon()))) {
                mDownloader.download(place.getIcon(), holder.image, mCache, 100, 100);
                mImageViewUrls.put(holder.image, place.getIcon());
            }
        } else {
            holder.image.setImageBitmap(null);
            mImageViewUrls.remove(holder.image);
        }

        return convertView;
    }
}

private class ViewHolder {
    public final ImageView image;
    public final TextView name;
    public final TextView address;
    public int position;

    public ViewHolder(View row) {
        image = (ImageView) row.findViewById(R.id.placeRow_imageView);
        name = (TextView) row.findViewById(R.id.placeRow_placeName);
        address = (TextView) row.findViewById(R.id.placeRow_placeAddress);
    }
}

mImageViewUrlsはと URLWeakHashMap<ImageView, String>の間でマッピングされるため、必要な画像がすでに表示ImageViewされているかどうかを確認することで、冗長な非同期タスクの呼び出しを減らすことができます。ImageViewこの実装を行わないと、データの変更時に表示されるすべての画像でちらつきが発生します。これにより、一部の画像でのみ発生します。

編集: この問題の考えられる原因を排除しようとしました。まず、キャッシュの実装を完全にバイパスして、ネットワークから各ビットマップをダウンロードしようとしました。次に、アダプターを CommonsWare の Endless アダプターでラップしようとしましたが、両方で同じ結果が得られました..これにより、考えられる原因として ImageDownloader クラスとアダプターのみが残ります..私はこれについて完全に迷っています。

4

4 に答える 4

4

このちらつきの理由は、リストビューでリスト項目が再利用されるためです。再利用すると、リスト項目のイメージビューは最初に表示される古いイメージ参照を保持します。後で新しい画像がダウンロードされると、表示が開始されます。これにより、ちらつき動作が発生します。このちらつきの問題を回避するには、古いイメージ参照を再利用するときは常にイメージビューからクリアしてください。

あなたの場合、holder.image.setImageBitmap(null);を追加します。ホルダーの後 = (ViewHolder) convertView.getTag();

したがって、 getView() メソッドは次のようになります。

@Override
public View getView(final int position, View convertView, ViewGroup parent) {

    ...

    if (convertView == null) {
        LayoutInflater inflater = getLayoutInflater();
        convertView = inflater.inflate(viewResourceId, null);

        holder = new ViewHolder(convertView);
        convertView.setTag(holder);
    } else {
        holder = (ViewHolder) convertView.getTag();
        holder.image.setImageBitmap(null)
    }

    ...

    return convertView;
}
于 2014-05-22T10:55:00.160 に答える
1

編集:私は同様の問題を抱えていました.画像は急速に変化していました. これは次の理由で発生します。

List アダプターの getView() メソッドが複数回呼び出されます。getView() が呼び出されるたびに、行に画像をダウンロードして設定しようとします。画像がネットワークからダウンロードされるまでに、その画像を要求したリストの行が画面の表示部分から移動している可能性がありますが、アダプターはその行を再利用しようとします。これにより、以前に要求された画像がその位置の新しい行に設定されます(以前に要求された行位置)。

アダプターでこのアプローチを試し、 setTag を使用して ImageView に要求された URL を設定します。

if(item.get_referenceImage().length()!=0){
     //with view, hold the url. This is used to verify that right image is loaded on download completion  
     holder.refImageView.setTag(item.get_referenceImage());
     imageManager.loadImage(new MetaItem(item.get_referenceImage(), holder.refImageView));
 }else{
     //no image, set default
     Log.d(TAG, "No images found for the view setting default image");
     holder.refImageView.setTag(null); //this is req, otherwise image cache will retain previous image
     holder.refImageView.setImageResource(R.drawable.blank_96_1382x);
}

キャッシュで、ビットマップをロードする前に、正しい画像がロードされているかどうかを確認します。

 String url = (String) m_imageViewRef.get().getTag();
  if(url != null && url.compareTo(m_metaItem.m_url) == 0 ){ 
        m_metaItem.m_imageViewRef.get().setImageBitmap(result);
  }

PS: MetaItem は、imageView と画像の URL への参照を保持する単なる POJO です。

于 2012-11-21T20:30:07.243 に答える
0

リストアイテムをリロードするadapter.notifyDatasetChanged()原因となる呼び出しを行っている可能性があります。listviewリストビューを呼び出さないようadapter.notifyDatasetChanged() にしてください。スクロールすると自動的に更新されます。ただし、これによりArrayIndexOutOfBoundsスクロール中に例外が発生する可能性があります。

于 2015-09-24T07:13:47.913 に答える
0

解決策は、画像が変更されていないときに画像をリロードしないことです。

アダプターで getView() を実行します。

// schedule rendering:
final String path = ... (set path here);
if (holder.lastImageUrl == null || !holder.lastImageUrl.equals(path)
                || holder.headerImageView.getDrawable() == null) {
    // refresh image
    imageLoader.displayImage(imageUri, imageAware);
} else {
    // do nothing, image did not change and does not need to be updated
}

成功した場合 (ImageLoadingListener を追加)、holder.lastImageUrl = パスを設定し、失敗してキャンセルした場合は、次回リロードされるように holder.lastImageUrl を null に設定します。

于 2014-09-17T12:47:52.217 に答える