23

RecyclerView用来显示一个包含imageView. 为了使 UI 更流畅,我将保存在 sd 卡上的 58dp 缩略图加载到这些带有asyncTask.

问题是,一旦childView出现视觉显示,来自另一个数据的旧图像将被重复使用,然后在AsyncTask完成后替换。imageView我可以通过将位图设置为 null in来停止洗牌onPreExecute

有没有办法真正重用旧图像,或者每次新图像View到位时我真的必须从 sd 卡加载图像?这使得视图非常难看,因为要么首先有错误的图像,要么图像是纯白色的。

4

7 回答 7

34

由于视图重用,您将获取已包含内容的视图,ListViews如果您使用该ViewHolder模式,这也是一个问题,您应该这样做。

这里有两种解决方案,好的做法和坏的 hack:

  • 在良好的实践中,您将您设置ImageView为在开始bindViewHolder(VH holder, int position)使用 setDrawable(null)或类似时不显示任何内容。

  • 在糟糕的 hack 中,您不会回收/重用视图,不会强制执行ViewHolder模式,并且每次都会对其进行膨胀,但这仅允许在ListView其他旧组件中使用。

于 2014-10-23T10:25:32.813 回答
8

您应该检查通用图像加载器。它有内存缓存、磁盘缓存,它异步加载你的图像,所以不会阻塞用户界面。您可以设置默认图像和/或无法获取图像等。它可以对图像进行采样以减少位图的内存占用。我真的建议您将其用于图像。

不要为您的案例禁用可回收,因为它没有意义。必须回收图像,因为如果未正确采样,它们的位图可绘制对象会产生非常高的内存过载。

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);
于 2015-02-19T08:36:59.580 回答
5

您应该在开始新请求之前取消旧请求,但无论取消,如果两个图像或多或少同时加载到已回收的同一个容器/视图持有者上,您仍然可以显示错误的图像(很容易发生快速滚动和小图像)。

解决方案是:

  1. 在 onBindViewHolder 期间在 View Holder 中存储一些唯一标识符(这是同步发生的,所以如果 VH 被回收,这将被覆盖)
  2. 然后异步加载图像(使用 AsynchTask、RxJava 等)并在异步调用中传递此唯一 id 以供参考
  3. 最后,在用于后处理的图像加载方法(onPostExecute for AsyncTasks)中,检查异步请求中传递的 id 是否与 View Holder 中存在的当前 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 更改的视图持有者的字段

于 2016-05-31T16:13:15.760 回答
1

您必须在“onBindViewHolder”方法中取消旧请求:

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

}

记得将图像容器保存在 viewHolder 中:

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

}

于 2015-03-03T14:02:32.427 回答
0

你应该使用毕加索一个强大的安卓图像下载和缓存库。 易于使用和强大的库。使用这个库,您可以从资源、资产、文件、内容提供者中异步或同步地获取图像。

于 2016-05-02T06:10:11.510 回答
0

我会添加到良好的做法:

在良好的实践中,您使用 setDrawable(null) 或类似方法将 ImageView 设置为在 bindViewHolder(VH holder, int position) 的开头不显示任何内容。

不是什么都不显示,而是显示一个加载器图像,以便给用户一个反馈,即在该视图中正在进行一些处理,稍后它将看到结果。只看到一个空白视图不是好的做法,您需要向用户提供反馈。

于 2016-06-16T18:19:15.290 回答
-2

MLProgrammer 说的非常正确。

幸运的是,解决方案很简单:停止回收

holder.setIsRecyclable(false);

这种选择有其后果,因为执行我上面的建议会抑制 RecyclerView 的主要目的之一:回收。

  • 您的 RecyclerView 滚动速度会变慢(每次都必须创建一个新的 Holder,再次从资源中膨胀);
  • 您的内存使用量会更大。
于 2015-02-13T20:36:31.447 回答