我们在使用ListView异步加载图片的时候,在快速滑动或者网络不好的情况下,会出现图片错位、重复、闪烁等问题,其实这些问题总结起来就是一个问题,我们需要对这些问题进行ListView的优化。

比如ListView上有100个Item,一屏只显示10个Item,我们知道getView()中convertView是用来复用View对象的,因为一个Item的对应一个View对象,而ImageView控件就是View对象通过findViewById()获得的,而我们在复用View对象时,同时这个ImageView对象也被复用了。比如第11个Item的View复用了第1个Item View对象,那么ImageView就同时被复用了,所以当图片没下载出来,这个ImageView(第11个Item)显示的数据就是复用(第1个Item)的数据。

1:Item图片显示重复

这个显示重复是指当前行Item显示了之前某行Item的图片。

比如ListView滑动到第2行会异步加载某个图片,但是加载很慢,加载过程中ListView已经滑动到了第14行,且滑动过程中该图片加载结束。第2行已不在屏幕内,根据上面介绍的缓存原理,第2行的View对象可能被第14行复用,这样我们看到的就是第14行显示了本该属于第2行的图片,造成显示重复。

2. Item图片显示错乱

这个显示错乱是指某行Item显示了不属于该行Item的图片。

跟上面的原因一样。

3. Item图片显示闪烁

上面介绍的另外一种情况,如果第14行图片又很快加载结束,所以我们看到第14行先显示了复用的第2行的图片,立马又显示了自己的图片进行覆盖造成闪烁错乱。

解决方案:

通过上面的分析我们知道了出现错乱的原因是异步加载及对象被复用造成的,如果每次getView能给对象一个标识,在异步加载完成时比较标识与当前行Item的标识是否一致,一致则显示,否则不做处理即可。

 

下面给个简单的实例(此实例没有对图片做缓存!!!!!!)

MainActivity --------------------------------------------------------------------------------------------------

package com.example.day001;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.widget.AbsListView;
import android.widget.ListView;
import android.widget.AbsListView.OnScrollListener;
/**
* @author echo
*
* ListView的使用步骤:
* 1.在xml中使用ListView标签,并找到该listview。
* 2.创建一个数据源(ListView要展示的数据)
* 3.自定义一个BaseAdapter并实例化它。
* 4.lv.setAdapter(adapter);
*
*/
public class MainActivity extends Activity {

// 集合,用来放数据源
List<PeopleBean> list;
// 自定义的BaseAdpater
MyBaseAdapter adapter;
// 当前的页数
int page = 1;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

// 1.找到listview
ListView lv = (ListView) findViewById(R.id.lv);

// 2.创建一个数据源
// 用来装数据的集合
list = new ArrayList<PeopleBean>();
// 联网请求数据
MyTask myTask = new MyTask();
myTask.execute("http://www.ytmfdw.com/coupon/index.php?c=user&a=getstudent&page="
+ page);

// 3.实例化adapter
adapter = new MyBaseAdapter(list, this);

// 4.设置adapter
lv.setAdapter(adapter);

// 设置listview的滚动监听事件
lv.setOnScrollListener(new OnScrollListener() {

@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
if (scrollState == 0) {
// 当listview滑动到底部的时候,需要联网加载更多数据
if (view.getLastVisiblePosition() == view.getCount() - 1) {
page++;
MyTask task = new MyTask();
task.execute("http://www.ytmfdw.com/coupon/index.php?c=user&a=getstudent&page="
+ page);
}
}
}

@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
// TODO Auto-generated method stub
}
});

}

/**
* 联网获取接口返回的数据
*
* 异步任务的使用步骤:
* 1.新建类,继承自AsyncTask<Params, Progress, Result>
*          三个参数:(1)Params:代表输入到任务的参数类型,也即是doInBackground()的参数类型

*                        (2)Progress:代表处理过程中的参数类型,也就是doInBackground()执行过程中的
*                                             产出参数类型,通过publishProgress()发消息,传递给
*                                             onProgressUpdate(),一般用来更新界面.

*                        (3)Result:任务结束的产出类型,也就是doInBackground()的返回值类型,
*                                          和onPostExecute()的参数类型

*           这三个参数你需要什么类型的就传入什么类型的,如果是图片,可以传入Bitmap
*
* 2.重写doInBackground()方法,在这个方法中进行联网请求数据
*           它返回的值会传给onPostExecute()。
*
* 3.重写onPostExecute()方法,在这个方法中进行UI的更新
*
* 4.重写onProgressUpdate()方法,这个方法可以用于更新进度,比如下载电影,下载到百分之几。
* 它的进度值则是通过publishProgress()这个方法发布出来的。

*/

class MyTask extends AsyncTask<String, String, String> {
/**
* 此方法是在子线程中
* ------注意:联网请求只能写在子线程中
*/
@Override
protected String doInBackground(String... params) {
/**
* String... params:泛型
*        如果调用异步任务类的时候---->
*             MyTask myTask = new MyTask();
               myTask.execute(url1,url2,……);
    execute里面的参数有多个值,这些值会被放在params里面,成为一个数组,
    取值的时候就像数组一样取值,想要第几个,就用params[index]取出来就行。
    如果只有一个参数,则直接用params[0]即可。
*/
String http = params[0];
HttpURLConnection conn = null;
StringBuilder sb = new StringBuilder();
try {
URL url = new URL(http);
conn = (HttpURLConnection) url.openConnection();
InputStream in = conn.getInputStream();
BufferedReader reader = new BufferedReader(
new InputStreamReader(in));
String tmp = null;
while ((tmp = reader.readLine()) != null) {
sb.append(tmp);
}
in.close();
} catch (MalformedURLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
if (conn != null) {
conn.disconnect();
}
}
Log.d("TAG", sb.toString());

return sb.toString();
}

/**
* 此方法是在主线程中。
* ----注意:UI更新只能在主线程中进行,不能在子线程中更新UI
*/
@Override
protected void onPostExecute(String result) {
// TODO Auto-generated method stub
super.onPostExecute(result);
try {
// json解析
JSONObject json = new JSONObject(result);
JSONArray jsons = json.getJSONArray("data");
int len = jsons.length();
for (int i = 0; i < len; i++) {
JSONObject obj = jsons.getJSONObject(i);
PeopleBean bean = new PeopleBean();
bean.img = obj.getString("image");
bean.name = obj.getString("name");
list.add(bean);
}

// 刷新适配器,当数据改变的时候调用该方法,listview显示的数据就改变
adapter.notifyDataSetChanged();
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}

activity_main.xml----------------------------------------------------------------------------------------------------

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.day001.MainActivity" >

<ListView
android:id="@+id/lv"
android:layout_width="match_parent"
android:layout_height="match_parent" />

</RelativeLayout>

PeopleBean---------------------------------------------------------------------------------

public class PeopleBean {
String img;
String name;
String sex;
}

MyBaseAdapter---------------------------------------------------------------------------------

package com.example.day001;

import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.List;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;
/**
* @author echo
*
* 一、自定义BaseAdapter步骤:
* 1.新建一个类,继承BaseAdapter
* 2.重写4个方法;
* 3.修改getCount()返回的值(listview要展示的数据总个数)
* 4.在getView()方法里面去展示数据。
*        ---convertView复用:内存空间优化
*        ---ViewHolder:运行时间优化
*
* 二、ListView异步加载图片错位、重复、闪烁
*
解决办法:为了防止ListView异步加载图片错位、重复、闪烁等情况,我们需要给ImageView设置一个Tag,
这个Tag中设置的是图片的url,在异步加载完成时我们可以比较下图片的url与当前行Item的标识是否一致,
一致则显示,否则不做处理即可。

解决步骤:
1.给当前ImageView对象设置标识tag
2.给当前ImageView设置一个默认的图片
3.联网下载到图片后判断下tag值和当前的图片地址是否相等,相等就展示出来。

*
*/
public class MyBaseAdapter extends BaseAdapter {

List<PeopleBean> data;
Context context;

// 有参构造方法,传递值过来
public MyBaseAdapter(List<PeopleBean> data, Context context) {
super();
this.data = data;
this.context = context;
}

// 返回数据长度
@Override
public int getCount() {
// TODO Auto-generated method stub
return data.size();
}

// 返回一个item的view
@Override
public View getView(int position, View convertView, ViewGroup parent) {

ViewHolder holder = null;

/**
* ListView中的每一个Item显示都需要Adapter调用一次getView的方法, 这个方法会传入一个convertView的参数,
* 返回的View就是这个Item显示的View
* 。如果当Item的数量足够大,再为每一个Item都创建一个View对象,必将占用很多内存,创建View对象
* (mInflater.inflate(R.layout.lv_item,
* null);从xml中生成View,这是属于IO操作)也是耗时操作
* ,所以必将影响性能。Android提供了一个叫做Recycler(反复循环器
* )的构件,就是当ListView的Item从上方滚出屏幕视角之外
* ,对应Item的View会被缓存到Recycler中,相应的会从下方生成一个Item
* ,而此时调用的getView中的convertView参数就是滚出屏幕的Item的View
* ,所以说如果能重用这个convertView,就会大大改善性能。
*/
if (convertView == null) {

// 1.布局容器
LayoutInflater inflater = LayoutInflater.from(context);
// 2.把布局文件装进布局容器里面,得到item的view
convertView = inflater.inflate(R.layout.items, null);
holder = new ViewHolder();
// holder设置tag(标记)值
convertView.setTag(holder);
} else {
/**
* 如果convertView不为空,说明该view之前加载进来过,所以直接将其赋
* 给view,即反复使用,避免再创建新的view浪费资源
*/
holder = (ViewHolder) convertView.getTag();
}

// 找到当前的对象
PeopleBean PeopleBean = data.get(position);
// 获取当前对象里面的图片
String img_url = PeopleBean.img;
// 获取当前对象里面的文字
String text = PeopleBean.name;

// 找到控件
holder.tv = (TextView) convertView.findViewById(R.id.tv);
holder.iv = (ImageView) convertView.findViewById(R.id.iv);

/**
* 为了防止ListView异步加载图片错位、重复、闪烁等情况,我们需要给ImageView设置一个Tag,
* 这个Tag中设置的是图片的url,在异步加载完成时我们可以比较下图片的url与当前行Item的标识是否一致,
* 一致则显示,否则不做处理即可。
*/

/**
* 防止ListView异步加载图片错位、重复、闪烁等情况
*
* ---1.给当前ImageView对象设置标识
*/
holder.iv.setTag(img_url);
/**
* 防止ListView异步加载图片错位、重复、闪烁等情况
*
* ---2.给当前ImageView设置一个默认的值(因为控件是复用的, 用完之后其中的值还存在,所以需要清洗下,
* 这里可以给随意给一张默认的图片)
*/
holder.iv.setImageResource(R.drawable.ic_launcher);

// 异步任务--下载图片
DownImgTask downImgTask = new DownImgTask(holder.iv);
downImgTask.execute(img_url);

// 给控件设置值
holder.tv.setText(text);

return convertView;
}

/**
* 联网下载图片
* @author echo
*
*/
class DownImgTask extends AsyncTask<String, Void, Bitmap> {

ImageView iv;
String img_url;// 图片的地址

public DownImgTask(ImageView iv) {
super();
this.iv = iv;
}

@Override
protected Bitmap doInBackground(String... params) {
img_url = params[0];
HttpURLConnection conn = null;
Bitmap bm = null;
try {
URL url = new URL(img_url);
conn = (HttpURLConnection) url.openConnection();
InputStream in = conn.getInputStream();
bm = BitmapFactory.decodeStream(in);
in.close();
} catch (MalformedURLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
if (conn != null) {
conn.disconnect();
}
}
return bm;
}

@Override
protected void onPostExecute(Bitmap result) {
// TODO Auto-generated method stub
super.onPostExecute(result);

/**
* 防止ListView异步加载图片错位、重复、闪烁等情况
*
* ---3.判断下tag值和图片地址是否相等,若相等,则说明该控件在当前的位置所需要展示的图片 是对的,就可以直接展示出来啦
*/

// 防止图片错位,判断下tag值和图片地址是否相等
if (result != null && img_url.equals(iv.getTag())) {
// 设置图片
iv.setImageBitmap(result);
}

}
}

// 自定义一个ViewHolder
class ViewHolder {
TextView tv;
ImageView iv;
}

@Override
public Object getItem(int position) {
// TODO Auto-generated method stub
return null;
}

@Override
public long getItemId(int position) {
// TODO Auto-generated method stub
return 0;
}

}

Android中ListView异步加载图片错位、重复、闪烁问题分析及解决方案的更多相关文章

  1. Android的ListView异步加载图片时,错位、重复、闪烁问题的分析及解决方法

    Android ListView异步加载图片错位.重复.闪烁分析以及解决方案,具体问题分析以及解决方案请看下文. 我们在使用ListView异步加载图片的时候,在快速滑动或者网络不好的情况下,会出现图 ...

  2. Android 实现ListView异步加载图片

    ListView异步加载图片是非常实用的方法,凡是是要通过网络获取图片资源一般使用这种方法比较好,用户体验好,下面就说实现方法,先贴上主方法的代码: package cn.wangmeng.test; ...

  3. Android之ListView异步加载图片且仅显示可见子项中的图片

    折腾了好多天,遇到 N 多让人崩溃无语的问题,不过今天终于有些收获了,这是实验的第一版,有些混乱,下一步进行改造细分,先把代码记录在这儿吧. 网上查了很多资料,发现都千篇一律,抄来抄去,很多细节和完整 ...

  4. Android中ListView异步加载数据

    1.主Activity public class MainActivity extends Activity { private ListView listView; private ArrayLis ...

  5. android listview 异步加载图片并防止错位

    网上找了一张图, listview 异步加载图片之所以错位的根本原因是重用了 convertView 且有异步操作. 如果不重用 convertView 不会出现错位现象, 重用 convertVie ...

  6. listview异步加载图片并防止错位

    android listview 异步加载图片并防止错位 网上找了一张图, listview 异步加载图片之所以错位的根本原因是重用了 convertView 且有异步操作. 如果不重用 conver ...

  7. 又优化了一下 Android ListView 异步加载图片

    写这篇文章并不是教大家怎么样用listview异步加载图片,因为这样的文章在网上已经有很多了,比如这位仁兄写的就很好: http://www.iteye.com/topic/685986 我也是因为看 ...

  8. ListView异步加载图片,完美实现图文混排

    昨天参加一个面试,面试官让当场写一个类似于新闻列表的页面,文本数据和图片都从网络上获取,想起我还没写过ListView异步加载图片并实现图文混排效果的文章,so,今天就来写一下,介绍一下经验. Lis ...

  9. wemall app商城源码Android之ListView异步加载网络图片(优化缓存机制)

    wemall-mobile是基于WeMall的android app商城,只需要在原商城目录下上传接口文件即可完成服务端的配置,客户端可定制修改.本文分享wemall app商城源码Android之L ...

随机推荐

  1. SVN的忽略和只读使用方法学习记录

    前言,先扯几句.最近学了GIT,虽然很肤浅,但是也算是用上了分布式版本管理控制系统.Linus很牛,他也很厌烦SVN,而我看这些都是工具,是否拿来使用主要看是否顺手.我赞同分布式版本管理控制,它有诸多 ...

  2. 使用Uboot启动内核并挂载NFS根文件系统

    配置编译好内核之后,将生成的内核文件uImage拷贝到/tftpboot/下,通过tftp服务器将内核下载到开发板,使用命令:tftp 31000000 uImage.下载完成之后配置bootargs ...

  3. 简单谈谈Resource,Drawable和Bitmap之间的转换

    一直接触这些东西,还是归个类整理一下比较好. Resource -> Drawable Drawable draw1 = this.getResources().getDrawable(R.dr ...

  4. MySQL数据库备份和还原的常用命令

    其实很多情况下mysql备份就是采用了这些命令,例如: mysql导入和导出数据 linux自动定时备份web程序和mysql数据库 备份MySQL数据库的命令 mysqldump -hhostnam ...

  5. css z-index属性

    原文地址:http://www.neoease.com/css-z-index-property-and-layering-tree/ CSS 中的 z-index 属性用于设置节点的堆叠顺序, 拥有 ...

  6. 开源Math.NET基础数学类库使用(12)C#随机数扩展方法

    原文:[原创]开源Math.NET基础数学类库使用(12)C#随机数扩展方法                本博客所有文章分类的总目录:http://www.cnblogs.com/asxinyu/p ...

  7. [转载]ISO 8601规则

    1.每年有52周或者53周2.周一至周日为一个完整周.3.每周的周一是该周的第1天.周日是该周的第7天4.每年的第一周 为 每年的第一个周四所在的周.比如 2017年1月5日为当年的第一个周四,那么 ...

  8. 抛弃配置后的Spring终极教程

    一:前言 Spring 有XML配置和注解两种版本,我个人非常喜欢使用注解,相当热衷Spring boot! 对于Spring,核心就是IOC容器,这个容器说白了就是把你放在里面的对象(Bean)进行 ...

  9. 【学习总结】GirlsInAI ML-diary day-2-Python版本选取与Anaconda中环境配置与下载

    [学习总结]GirlsInAI ML-diary 总 原博github链接-day2 Python版本选取与Anaconda中环境配置与下载 1-查看当前Jupyter的Python版本 开始菜单选J ...

  10. [C++]最小生成元 (Digit Generator, ACM/ICPC Seoul 2005, UVa1583)

    Question 例题3-5 最小生成元 (Digit Generator, ACM/ICPC Seoul 2005, UVa1583) 如果x+x的各个数字之和得到y,就是说x是y的生成元.给出n( ...