快速滚动列表视图时出现问题

时间:2012-10-26 08:43:13

标签: android android-listview android-arrayadapter

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

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

我不知道它是否与使用线程加载网络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;
}

5 个答案:

答案 0 :(得分: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/

答案 1 :(得分:0)

从项目的下方链接添加ImageLoader类。 link

在getView()

中调用Image loader类的DisplayImage()方法,如下所示
  ImageLoader imageLoader = new ImageLoader();


 yourImageView.setTag(URL_FOR_Your_Image);

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

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

答案 2 :(得分:0)

我认为你应该将你的下载方法fetchDrawableOnThread()声明为“synchronized”。因为很多线程同时工作,并且以后启动的任何线程都可以提前结束。因此,图像存在错位的可能性。

很长一段时间发生在我身上。最后“同步”帮助我干净利落地做到了。我希望它对你也有所帮助。

答案 3 :(得分:0)

我再试一次同步。要么同步fetchDrawableOnThread(),要么同步fetchDrawableOnThread()中的全局hashmap数据。首先我认为这个问题已经消失了,但是当我多次尝试以后,我发现问题仍然存在。

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

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

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

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

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'para ?

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

很奇怪,转换视图只是旧的,还是旧的&正在使用?如果以后,我们应该只得到一个副本,然后修改..... ??

答案 4 :(得分:0)

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

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

    @Override
    public int getItemViewType(int position) {
        return position;
    }