当使用视图持有者模式时,Picasso将加载到错误的ImageView中。

14

我正在尝试使用Picasso库将外部图片加载到ListView的行中。我有一个自定义的ArrayAdapter,如下所示:

public class RevisedBusinessesAdapter extends ArrayAdapter<HashMap<String, String>> {

    Context context;
    int layoutResourceId;
    ArrayList<HashMap<String, String>> data = null;

    public RevisedBusinessesAdapter(Context context, int layoutResourceId,  ArrayList<HashMap<String, String>> data) {
        super(context, layoutResourceId, data);
        this.layoutResourceId = layoutResourceId;
        this.context = context;
        this.data = data;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        View row = convertView;
        RevisedBusinessHolder holder = null;

        if (row == null) {
            LayoutInflater inflater = ((Activity) context).getLayoutInflater();
            row = inflater.inflate(layoutResourceId, parent, false);
            holder = new RevisedBusinessHolder();
            holder.ivLogo = (ImageView) row.findViewById(R.id.ivBusinessLogo);
            row.setTag(holder);
        } else {
            holder = (RevisedBusinessHolder) row.getTag();
        }

        HashMap<String, String> business = data.get(position);

        String strLogoURL = business.get("logoURL");
        if (null != strLogoURL && !"".equals(strLogoURL)) {
            Picasso.with(this.context).load(strLogoURL).into(holder.ivLogo);        
        }

        return row;
    }

    static class RevisedBusinessHolder {
        ImageView ivLogo;
    }
}

当提供了logoURL作为远程图片的URL时,将显示该图片;如果未提供,则会使用本地src设置的ivBusinessLogo代替。当我快速滚动时,Picasso会将图像加载到错误的ImageView中,导致列表中出现多个重复的图像。

对于this问题的答案建议添加以下内容:

Picasso.with(context).cancelRequest(holder.ivLogo);

在现有的Picasso调用之前,但这并没有任何区别。如果我删除row == null检查并始终创建新视图,则似乎可以正常工作。然而,在完整版本中,还有四个文本视图和五个其他图像(从本地资源加载的小图标,而不是通过Picasso)需要在每个getView中更新。

是否有一种方法可以使用Android文档推荐的View Holder模式使其正常工作?


在 Picasso.with().load().into() 语句后面添加一个 else 语句。添加 else holder.ivLogo.setImageBitmap(null);。或者使用一个占位符位图。 - greenapps
@greenapps ivLogo在XML中已经设置了src集,因此它已经有了占位符。 - Ben Williams
这还不够,因为一个已回收的视图将包含旧位置的错误位图。 - greenapps
@ greenapps 看起来确实有效,谢谢!你应该把它添加为答案。 - Ben Williams
4个回答

23

即使您的URL为null,您也应始终调用Picasso。这样它就知道图像视图已被回收。

删除此if语句:

if (null != strLogoURL && !"".equals(strLogoURL)) {

你还应该考虑使用占位图或错误图像,这样当没有URL时也会显示一些内容。

如果你坚持使用if语句(但你不应该这样做!),你需要通过调用cancelRequest告诉Picasso图片视图已被回收:

Picasso.with(this.context).cancelRequest(holder.ivLogo);

8
如果删除if语句,你会把空的String传给 Picasso,导致出现IllegalArgumentException: Path must not be empty.异常,我的理解正确吗? - Ivan Morgillo
1
杰克,你甚至无法想象这是如何挽救了我们的一天。我尝试了许多取消和暂停fling事件标签组合,但什么都没有帮助,只有正确放置cancelRequest才起作用。 - miroslavign
1
@IvanMorgillo 对,Jake犯了个错误,但他的观点是你应该始终调用Picasso,而这个if条件语句正好阻止了它。不管怎样,谢谢Jake! - Dmytro Karataiev
1
在这种情况下,只需传递 null - 0101100101
经过多个小时的搜索,我终于解决了我的问题,我认为这不应该像这样难以找到... - hiddeneyes02
这行代码帮了我很多:“即使你的URL为空,你也应该始终调用Picasso。这样它就知道ImageView已经被回收了。”谢谢。 - kalandar

2

如果当前项没有从URL下载图像,则布局.xml中默认设置的src drawable(在ImageView上)将被最后缓存的下载图像覆盖。

对于没有图像属性的项目,您必须手动设置默认drawable:

try {
     Picasso.with(activity.getApplicationContext()).load(customer.getImage().getPath()).placeholder(R.drawable.image_placeholder)
                .error(R.drawable.image_placeholder).into(imageView);
    }
catch (Exception e) {
       imageView.setImageResource(R.drawable.default_customer_icon);
       // this set the default img source if the path provided in .load is null or some error happened on download.
    }

0

我曾经遇到同样的问题并解决了它。请考虑在getView方法中使用参数convertView

convertView是旧视图,如果可能的话可以重用。注意:在使用之前,您应该检查此视图是否非空且类型适当。如果无法将此视图转换为显示正确数据,则此方法可以创建新视图。

当您使用holder时,Picasso会将图像加载到一行的imageview中,并重复使用它。这就是为什么您会看到重复的图像。我认为您应该在第一次创建行视图时加载图像。尝试更改您的代码为

if (row == null) {
    LayoutInflater inflater = ((Activity) context).getLayoutInflater();
    row = inflater.inflate(layoutResourceId, parent, false);
    holder = new RevisedBusinessHolder();
    ImageVIew ivLogo = (ImageView) row.findViewById(R.id.ivBusinessLogo);
    Picasso.with(this.context).load("Your Image URL").into(holder.ivLogo);
    row.setTag(holder);
}

-1
在 Picasso.with().load().into() 语句后添加一个 else 语句。 添加 else holder.ivLogo.setImageBitmap(null);。 或者使用占位符位图。
看到 Octa George 的解决方案后,最好在调用 Picasso 之前始终执行holder.ivLogo.setImageBitmap(placeholderbitmap);。否则,当 Picasso“花费时间”时,您会首先看到错误的回收图像。

1
这不会解决问题。仍然存在一个潜在的问题,因为Picasso并不知道图像视图已被回收。 - Jake Wharton
@Jake 如果答案不完整,没有理由给它点踩。正如你可以在评论中看到的那样,它已经帮助大大减少了原始问题的问题。而且这是“受邀”的。 - greenapps
6
答案不正确,并包含与问题相同的漏洞。在当今泛滥的复制/粘贴编程中,我想确保没有人使用这个解决方案。 - Jake Wharton

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接