Android中ListView异步加载图片错位、重复、闪烁问题分析及解决方案
我们在使用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异步加载图片错位、重复、闪烁问题分析及解决方案的更多相关文章
- Android的ListView异步加载图片时,错位、重复、闪烁问题的分析及解决方法
Android ListView异步加载图片错位.重复.闪烁分析以及解决方案,具体问题分析以及解决方案请看下文. 我们在使用ListView异步加载图片的时候,在快速滑动或者网络不好的情况下,会出现图 ...
- Android 实现ListView异步加载图片
ListView异步加载图片是非常实用的方法,凡是是要通过网络获取图片资源一般使用这种方法比较好,用户体验好,下面就说实现方法,先贴上主方法的代码: package cn.wangmeng.test; ...
- Android之ListView异步加载图片且仅显示可见子项中的图片
折腾了好多天,遇到 N 多让人崩溃无语的问题,不过今天终于有些收获了,这是实验的第一版,有些混乱,下一步进行改造细分,先把代码记录在这儿吧. 网上查了很多资料,发现都千篇一律,抄来抄去,很多细节和完整 ...
- Android中ListView异步加载数据
1.主Activity public class MainActivity extends Activity { private ListView listView; private ArrayLis ...
- android listview 异步加载图片并防止错位
网上找了一张图, listview 异步加载图片之所以错位的根本原因是重用了 convertView 且有异步操作. 如果不重用 convertView 不会出现错位现象, 重用 convertVie ...
- listview异步加载图片并防止错位
android listview 异步加载图片并防止错位 网上找了一张图, listview 异步加载图片之所以错位的根本原因是重用了 convertView 且有异步操作. 如果不重用 conver ...
- 又优化了一下 Android ListView 异步加载图片
写这篇文章并不是教大家怎么样用listview异步加载图片,因为这样的文章在网上已经有很多了,比如这位仁兄写的就很好: http://www.iteye.com/topic/685986 我也是因为看 ...
- ListView异步加载图片,完美实现图文混排
昨天参加一个面试,面试官让当场写一个类似于新闻列表的页面,文本数据和图片都从网络上获取,想起我还没写过ListView异步加载图片并实现图文混排效果的文章,so,今天就来写一下,介绍一下经验. Lis ...
- wemall app商城源码Android之ListView异步加载网络图片(优化缓存机制)
wemall-mobile是基于WeMall的android app商城,只需要在原商城目录下上传接口文件即可完成服务端的配置,客户端可定制修改.本文分享wemall app商城源码Android之L ...
随机推荐
- PyCharm 远程连接linux中Python 运行pyspark
PySpark in PyCharm on a remote server 1.确保remote端Python.spark安装正确 2.remote端安装.设置 vi /etc/profile添加一行 ...
- Yii里文件上传的操作方法(图片修改,在详情上展示,批量上传待续...)
$model->img= UploadedFile::getInstance($model,'img');if ($model->validate()) {//$model->img ...
- (转)创建Graphics的三种方法
方法一.利用控件或窗体的Paint事件中的PainEventArgs 在窗体或控件的Paint事件中接收对图形对象的引用,作为PaintEventArgs(PaintEventArgs指定绘制控件所用 ...
- System.TypeInitializationException: The type initializer for 'Mono.Unix.Native.Stdlib' threw an exception.
08-31 17:02:03: ### DEBUG ##########################System.TypeInitializationException: The type ini ...
- js调用java代码返回解决方案
版权声明:本文为楼主原创文章,未经楼主允许不得转载,如要转载请注明来源. 今天封装一个加密标签,遇到一个问题,我需要对页面上的数据调用java后台代码进行解密,而标签里只能通过js获取到数据,所以就遇 ...
- 使用Jmeter录制脚本
相对于LoadRunner跟SilkPerformer来说,Jmeter确实有差距,但毕竟前两者太贵,Jmeter胜在免费开源. 先看下LoadRunner录制的脚本如下,美如画,结构清晰,易于修改编 ...
- 转载自@机智的新手:使用Auto Layout中的VFL(Visual format language)--代码实现自动布局
本文将通过简单的UI来说明如何用VFL来实现自动布局.在自动布局的时候避免不了使用代码来加以优化以及根据内容来实现不同的UI. 一:API介绍 NSLayoutConstraint API 1 2 3 ...
- 通过AngularJS实现图片上传及缩略图展示
从项目中截出的代码 HTML部分: <section> <img src="image/user-tuijian/tuijian_banner.png" /> ...
- SQLServer日志无法收缩原因分析及解决
SQL Server中的事务日志无疑是SQL Server中最重要的部分之一.因为SQL SERVER利用事务日志来确保持久性(Durability)和事务回滚(Rollback).从而还部分确保了事 ...
- JAVA编程规则【转自java编程思想】
本附录包含了大量有用的建议,帮助大家进行低级程序设计,并提供了代码编写的一般性指导: (1) 类名首字母应该大写.字段.方法以及对象(句柄)的首字母应小写.对于所有标识符,其中包含的所有单词都应紧靠在 ...