ListView优化(1),列表错乱问题

这节我们来讨论一下ListView的优化问题,ListView是我们在开发中非常常用的控件之一,而在开发中也经常会遇到关于ListView的问题,下面我们主要就ListView的两个主要问题进行分析和解决。

  • ListView的列表错乱问题
  • ListView的卡顿问题

一、ListView的列表错乱问题

1. 问题描述

在我们使用ListView异步加载很多图片的时候,会发现有时应该出现图片A的地方出现了图片B,或者某人的头像变成了其他人的头像,这就是ListView的列表错乱问题。

ListView列表错乱原因有两种情况。

2. 问题原因

原因就在于我们在Adapter的getView方法中复用了convertView。原本复用convertView的出发点是好的,是为了避免重复加载列表布局,也就是说本来用于加载显示图片A的列表项布局(这里称为View1)在图片A的位置移除到屏幕外之后,View1会被复用(也就是convertView)并用于加载新的图片B,从而避免了对View1重复加载列表项布局。
而问题就在于,如果图片B加载失败,没有覆盖复用的View1之前显示的图片A,或者另一种情况,View1在加载图片A完成前,View1已经被复用并完成加载显示图片B,此时图片A才完成加载并显示在View1上。这两种情况都会造成原本应该显示图片B的控件View1显示了图片A。这就是所谓的ListView图片错乱。
总结两种情况就是:

  1. View1显示图片A,ListView滚动,View1被复用用于显示图片B,而图片B加载不成功,此时View1仍然显示的是图片A。
  2. View1正在加载图片A,ListView滚动,View1被复用用于显示图片B,图片B很快加载显示,然后图片A才加载完并显示在View1中,此时图片A会把图片B覆盖,导致View1显示的是图片A。

3. 问题代码

复用convertView的BaseAdapter的getView方法

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    ViewHolder vh = null;
    if(convertView == null)
    {
        convertView = LayoutInflater.from(mContext).inflate(R.layout.item_list, parent, false);
        vh = new ViewHolder(
                ((ImageView)convertView.findViewById(R.id.iv_uil)));
        convertView.setTag(vh);
        
    }else
    {
        vh = (ViewHolder) convertView.getTag();
    }
    
    //在子线程中异步加载图片,其中List包含图片的URL
    loadImageAync(list.get(position),vh.imageView);
    
    return convertView;
}

异步加载图片并在Handler中显示图片的代码:

public void loadImageAync(String uri, ImageView imageView)
{
    //利用线程池加载图片
    THREAD_POOL_EXECUTOR.execute(new Runnable()
        {
            @Override
            public void run()
            {
                //通过url获取Bitmap
                Bitmap bitmap = loadBitmap(uri);
                if(bitmap != null)
                {
                    //用一个封装类将uri,Bitmap和ImageView传送到Handler处理
                    LoaderResult result = new LoaderResult(iamgeView, uri, bitmap);
                    mMainHandler.obtainMessage(RESULT, reuslt).sendToTarget();
                }
            }
        });
}

//用于在主线程中加载图片的Handler
Handler mMainHandler = new Handler(Looper.getMainLooper())
{
    @Override
    public void handleMessage(Message msg)
    {
        LoaderResult result = (LoaderResult) msg.obj;
        ImageView imageView = result.getImageView();
        Bitmap bitmap = result.getBitmap();
        //显示图片到imageView中
        imageView.setImageBitmap(bitmap);
    }
};

上面这些就是我们通常用的,在子线程中加载图片,然后通过Handler将图片加载到ImageView当中。也就是这段代码,造成了图片错乱的原因。

4. 解决方法

解决切入点

回顾一下两种造成图片错乱的原因,一个是由于图片B加载不成功导致图片A没有被刷新,另一个是图片A加载时间长把已经加载完成的图片B覆盖了。两种原因不一样,因此要完全解决图片错乱的问题需要分别从两个问题原因入手。

第一种问题的解决方法

对于第一种情况,通常有这样的解决思路,那就是不管后面的图片B能不能加载成功,我在开始加载图片前我都对ImageView设置一个正在加载默认图片,这样即使后面的图片B加载失败,那显示的至少也是一个正在加载的图片(还可以在加载失败时显示失败图片),而不是图片A,这样就能避免了图片A显示在应该显示图片B的View上了。

这个方法能够解决第一种问题,通常这也就够了,因为大部分发生列表错乱的原因就是因为后面的图片加载不出来。

具体的实现就是,在getView中或者在loadImageAsync中异步加载图片前,给ImageView设置一张正在加载图片,更好的还需要在获取图片失败时将图片设置成一张失败图片,所以有两点需要改进。
第一种问题的解决方法:

public void loadImageAync(final String uri, final ImageView imageView)
{
    //1.在加载图片之前设置一个默认图片
    imageView.setImageDrawable(R.drawable.ic_loading);

    THREAD_POOL_EXECUTOR.execute(new Runnable()
        {
            @Override
            public void run()
            {
                Bitmap bitmap = loadBitmap(uri);
                //注意判断条件改变了
                if(bitmap == null)
                {
                    //2. 加载图片失败时设置一个失败图片
                    bitmap = getDefaultErrorBitmap();
                }

                LoaderResult result = new LoaderResult(iamgeView, uri, bitmap);
                mMainHandler.obtainMessage(RESULT, reuslt).sendToTarget();
            }
        });
}
第二种问题的解决方法

第二中问题就比较特殊了,根本原因就是,图片A不知道自己在绑定的ImageView中已经过期了,用来显示自己的ImageView已经被复用了,但是图片A还是不要脸的在加载完成之后不管不顾的显示到ImageView中,如果图片B加载的比A久,那么不会出现问题(会出现图片A闪一下然后立刻换成B),如果B加载得快,那么最后图片A就会把图片B给覆盖。

既然问题找出来了,那么解决方法就好想了,只要让ImageView知道图片A已经过期,不显示图片A就行了。

实现的方法就是,在加载图片A前,给ImageView设置一个标签,用于表示当前应该显示的图片A的uri。然后在图片A加载完成之后显示图片A前,再次验证ImageView的标签是否还和所加载的图片A的uri一致,这里标签会发生变化的原因就在于,ImageView被复用用于加载另一张图片B时,标签就会标称图片B的uri。如果最后uri一致,说明ImageView还没有被复用,然后便可以心安理得的显示图片A;如果不一致,说明ImageView已经被复用啦,已经不属于图片A的啦,此时就不要再显示A啦。

在代码中的实现主要分为两步,一是在加载图片之前为ImageView添加Tag,二是在Handler的handleMessage中显示图片前验证uri是否一致,一致的话才显示图片。以下是图片错乱的解决方法,包括了第一种问题的解决办法。

public void loadImageAync(final String uri, final ImageView imageView)
{
    imageView.setImageDrawable(R.drawable.ic_loading);

    //在加载之前为ImageView设置Tag为uri
    imageView.setTag(uri);

    THREAD_POOL_EXECUTOR.execute(new Runnable()
        {
            @Override
            public void run()
            {
                Bitmap bitmap = loadBitmap(uri);
                //注意判断条件改变了
                if(bitmap == null)
                {
                    bitmap = getDefaultErrorBitmap();
                }

                LoaderResult result = new LoaderResult(iamgeView, uri, bitmap);
                mMainHandler.obtainMessage(RESULT, reuslt).sendToTarget();
            }
        });
}

Handler mMainHandler = new Handler(Looper.getMainLooper())
{
    @Override
    public void handleMessage(Message msg)
    {
        LoaderResult result = (LoaderResult) msg.obj;
        ImageView imageView = result.getImageView();
        Bitmap bitmap = result.getBitmap();
        String uri = result.getUri();
        //如果uri一致才显示图片到imageView中
        if(uri.equals((String)imageView.getTag()))
        {
            imageView.setImageBitmap(bitmap);
        }else
        {
            Log.w(TAG,"set image bitmap, but uri has changed, ignored!");   
        }
    }
};

以上就是ListView列表错乱的产生原因以及解决方法啦~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,761评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,953评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,998评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,248评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,130评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,145评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,550评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,236评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,510评论 1 291
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,601评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,376评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,247评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,613评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,911评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,191评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,532评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,739评论 2 335

推荐阅读更多精彩内容