4

我正在做一个小项目,我在其中创建了listview一个 ArrayAdapter 的绑定。在getView()ArrayAdapter 的功能中,我urls在线程上从 web 加载图像,并将它们设置为列表项(当然基于位置,因此url[0]图像设置为list_item[0]etc)。这一切似乎运作良好。

但是,当我测试应用程序时,我注意到如果我等待列表视图完全显示,然后来回快速滚动,我会看到有时一个列表项上的图像错位在另一个列表项上(比如处于中间状态) . 但是,直到我将错误显示的项目滚动出屏幕然后再返回,它才会消失。

我不知道这是否与我url使用线程加载网络有关,或者从本地资源文件夹加载图像可能会遇到同样的问题。

这实际上导致了我对getView()功能的疑问。我认为我的逻辑是正确的,getView()因为它就像将 url 绑定到基于位置的视图一样简单。每当getView()有机会被调用时,比如当我将一个项目滚动出屏幕然后返回时,它都会使列表项正确显示。

我不明白的是如何解释发生的问题(如中间状态),以及在编写代码时如何避免它?

我将我的适配器代码粘贴在下面,但我认为这个问题可能是一个普遍的问题:

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

        ViewHolder viewHolder = null;

        if (v == null) {
            LayoutInflater inflater = (LayoutInflater) getContext()
                    .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            v = inflater.inflate(layoutResourceId, parent, false);

            viewHolder = new ViewHolder();
            viewHolder.title = (TextView) v.findViewById(R.id.title);
            viewHolder.description = (TextView) v.findViewById(R.id.description);
            viewHolder.image = (ImageView) v.findViewById(R.id.image);

            v.setTag(viewHolder);
        } else {
            viewHolder = (ViewHolder) v.getTag();
        }

        listItem item = items[position];  //items is some global array
                                          //passed in to ArrayAdapter constructor
        if (item != null) {
            viewHolder.title.setText(item.title);   
            viewHolder.description.setText(item.description);

            if (!(item.imageHref).equalsIgnoreCase("null")) {
                mDrawableManager.fetchDrawableOnThread(item.imageHref, viewHolder.image);
            } else {
                viewHolder.image.setImageDrawable(null);
            }
        }
        return v;
    }
}

static class ViewHolder {
    TextView title;
    TextView description;
    ImageView image;
}
4

5 回答 5

3

我在快速滚动时遇到同样的问题,它会将某些项目的值交替到其他项目,就像某些项目的背景颜色随机变化一样。我通过大量搜索解决了这个问题并找到确切的解决方案,如果您在适配器中使用 ViewHolder,则只需在适配器中添加这两种方法

@Override
    public int getViewTypeCount() {
        return getCount();
    }

    @Override
    public int getItemViewType(int position) {
        return position;
    }
于 2016-08-31T09:43:05.107 回答
0

我再次尝试同步。要么同步 fetchDrawableOnThread(),要么同步 fetchDrawableOnThread() 中的全局 hashmap 数据。一开始我以为问题已经解决了,但是当我后来尝试更多次时,我发现问题仍然存在。

然后我想到了同步。从getView()调用fetchDrawableOnThread(),getview()本身不存在并发问题。即使如Yogesh所说,INSIDE getView()发生的事情是基于线程的,早晚返回,也不会影响getView()的正确性,即列表项的显示,只是迟早而已。

我在 fetchDrawableOnThread() 中所做的(同步)我认为它仍然是正确的,因为我使用 hashmap 来缓存从远程 url 下载的图像,并且这个 hashmap 在多线程情况下是读/写的,所以它应该被锁定. 但我不认为这是 UI 错位的根本原因,如果 hashmap 搞砸了,图像错位将是永久性的。

然后我根据 Praful 的解释进一步研究了 convertView 重用机制。他清楚地解释了当图像总是来自远程并且本地没有缓存时会发生什么,但我的情况是我等待我的列表完全显示,即所有图像下载完成并缓存完成,然后我才进行快速滚动。所以在我的实验中,图像是从缓存中读取的。

然后在检查我的代码时,我发现在使用 convertView 和 getView() 方法时有一个小的区别,很多示例用法是这样的:

public View getView(int position, View convertView, ViewGroup parent) {  // case 1
    View v = convertView;
    ....  // modify v
    return v;
}

但是我碰巧从使用中复制的示例:

public View getView(int position, View convertView, ViewGroup parent) {  // case 2
    ....  // modify convertView
    return convertView;
}

起初我认为它没有区别,因为根据 android 所说,'ListView 向适配器发送一个旧视图,它在 convertView 参数中不再使用。',那么为什么不直接使用 'convertView' 参数呢?

但我想我错了。我将 getView() 代码更改为案例 1。Boom!一切正常。无论我滚动列表的速度有多快,都没有有趣的事情。

很奇怪,convertView 只是旧的,还是旧的和正在使用的?如果是后者,我们应该只拿到一个副本然后修改...... ??

于 2012-10-26T22:43:14.663 回答
0

假设您没有缓存下载的图像..让我们看看以下代码:

    if (!(item.imageHref).equalsIgnoreCase("null")) {
        mDrawableManager.fetchDrawableOnThread(item.imageHref, viewHolder.image);
    } else {
        viewHolder.image.setImageDrawable(null);
    }

现在,如果图像视图被重用,那么它已经拥有分配列表项的旧图像。因此,在线程从网络下载图像之前,它会显示旧图像,而当线程下载当前项目的图像时,它将被新图像替换。尝试将其更改为:

    if (!(item.imageHref).equalsIgnoreCase("null")) {
        viewHolder.image.setImageDrawable(SOME_DEFULAT_IMAGE);
        mDrawableManager.fetchDrawableOnThread(item.imageHref, viewHolder.image);
    } else {
        viewHolder.image.setImageDrawable(null);
    }

或者您可以使用支持 HTTP URI 并缓存图像的链接智能图像视图。查看以下链接以获取智能图像视图:

https://github.com/loopj/android-smart-image-view http://loopj.com/android-smart-image-view/

于 2012-10-26T08:55:41.620 回答
0

在您的项目中从下面的链接添加 ImageLoader 类。 关联

只需在 getView() 中调用 Image loader 类的 DisplayImage() 方法,如下所示

  ImageLoader imageLoader = new ImageLoader();


 yourImageView.setTag(URL_FOR_Your_Image);

 imageLoader.DisplayImage(URL_FOR_Your_Image,ACTIVITY.this, yourImageView);

您的图像将根据需要在后台加载,无需等待。

于 2012-10-26T10:11:22.953 回答
0

我认为您应该将下载器方法 fetchDrawableOnThread() 声明为 "synchronized" 。因为很多线程同时工作,而任何稍后开始的线程都可以更早结束。因此,图像有可能被放错位置。

它发生在我身上很长一段时间。最后“同步”帮助我干干净净。我希望它也能帮助你。

于 2012-10-26T12:17:07.110 回答