ListView的滚动对画面的流畅度要求是非常高的,当作异步加载的时候,虽然在加载的过程中是用新的线程去执行的,并没有阻塞UI线程,但当加载好之后,去更新UI线程,就会导致UI线程发生一次重绘,而如果这次重绘如果刚好正好发生在ListView滚动的时候,就会导致ListView滚动的过程中卡顿一下
改善方法:
ListView滑动停止后才加载可见项
ListView滑动时,取消加载可见项,也就是在滚动的时候停止加载任务
(也可以运用到GridView中)
下面是修改后的代码
1.LoaderImage
public class ImageLoader {
private LruCache<String, Bitmap> mLruCache;
private ListView mListView;
private Set<LoadingAsyncTask> mAsyncTasks;//用来管理所有的LoadingAsyncTask实例
public ImageLoader(ListView listView) {
mListView=listView;
mAsyncTasks=new HashSet<ImageLoader.LoadingAsyncTask>();
int maxMemory=(int) Runtime.getRuntime().maxMemory();
int cacheSize=maxMemory/5;
mLruCache=new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap value) {
//在每次存入缓存的时候调用
return value.getByteCount();
};
};
}
public void addBitmapToCache(String url,Bitmap bitmap) {
if(mLruCache.get(url)==null) {
mLruCache.put(url, bitmap);
}
}
public Bitmap getBitmapFromCache(String url) {
return mLruCache.get(url);
}
/**
* 使用AsyncTask实现异步加载,showImageByAsyncTask自身是在主线程当中的
* @param iv
* @param url
*/
public void showImageByAsyncTask(final ImageView iv,final String url) {
Bitmap bitmap=getBitmapFromCache(url);
if (bitmap == null) {
iv.setImageResource(drawable.ic_launcher);
}
else {
iv.setImageBitmap(bitmap);
}
}
private class LoadingAsyncTask extends AsyncTask<String, Void, Bitmap> {
private String url;
public LoadingAsyncTask(String url) {
this.url=url;
}
@Override
protected Bitmap doInBackground(String... params) {
Bitmap bm=getBitmapFromURL(params[0]);
if(bm!=null) {
addBitmapToCache(params[0], bm);
}
return bm;
}
@Override
protected void onPostExecute(Bitmap result) {
ImageView iv = (ImageView) mListView.findViewWithTag(url);
if (iv != null && result != null) {
iv.setImageBitmap(result);
}
mAsyncTasks.remove(this);
}
}
public Bitmap getBitmapFromURL(String urlString) {
Bitmap bm=null;
InputStream is=null;
try {
URL url=new URL(urlString);
HttpURLConnection connection=(HttpURLConnection) url.openConnection();
is=new BufferedInputStream(connection.getInputStream());
bm=BitmapFactory.decodeStream(is);
connection.disconnect();//释放资源
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return bm;
}
/**
* 将显示图片的控制权从showImageByAsyncTask方法转移到了loadImages方法
* 不再让getView的时候去触发LoadingAsyncTask的加载
* 而使得ListView滚动的时候去触发加载任务
*
* 加载指定的从start到end项的图片
* @param start
* @param end
*/
public void loadImages(int start,int end) {
for(int i=start;i<=end;i++) {
String url=NewsAdapter.URLS[i];
Bitmap bitmap=getBitmapFromCache(url);
if (bitmap == null) {
LoadingAsyncTask task=new LoadingAsyncTask(url);
task.execute(url);
mAsyncTasks.add(task);
}
else {
ImageView iv=(ImageView) mListView.findViewWithTag(url);
iv.setImageBitmap(bitmap);
}
}
}
public void cancelAllTasks() {
if(mAsyncTasks!=null) {
for(LoadingAsyncTask task:mAsyncTasks) {
task.cancel(true);
}
}
}
}
这里,就只实现了AsyncTask的,至于多线程的,也可以实现,只不过有点麻烦,有兴趣的可以尝试一下。
2.NewsAdapter
//注意要为AbsListView的OnScrollListener
public class NewsAdapter extends BaseAdapter implements android.widget.AbsListView.OnScrollListener{
private List<NewsBean> mList;
private LayoutInflater mInflater;
private ImageLoader mImageLoader;
private int mStart,mEnd;//可见的item的起始项
private boolean mFirstIn;//用于判断是不是刚开始运行程序
public static String[] URLS;//保存当前所有获取到的图片的URL,然后通过mStart,mEnd就从数组中得到需要的图片的url
public NewsAdapter(Context context,List<NewsBean> data,ListView listView) {
mList=data;
mInflater=LayoutInflater.from(context);
mImageLoader=new ImageLoader(listView);
URLS=new String[data.size()];
for(int i=0;i<URLS.length;i++) {
URLS[i]=data.get(i).newsIconUrl;
}
listView.setOnScrollListener(this);//记得一定要注册
mFirstIn=true;
}
@Override
public int getCount() {
return mList.size();
}
@Override
public Object getItem(int position) {
return mList.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder=null;
if(convertView==null) {
viewHolder=new ViewHolder();
convertView=mInflater.inflate(R.layout.item_layout, null);
viewHolder.icon=(ImageView) convertView.findViewById(R.id.id_icon);
viewHolder.title=(TextView) convertView.findViewById(R.id.id_tv_title);
viewHolder.content=(TextView) convertView.findViewById(R.id.id_tv_content);
convertView.setTag(viewHolder);
}
else {
viewHolder=(ViewHolder) convertView.getTag();
}
viewHolder.icon.setImageResource(R.drawable.ic_launcher);
String url=mList.get(position).newsIconUrl;
viewHolder.icon.setTag(url);
// new ImageLoader().showImageByAsyncTask(viewHolder.icon, url);
// new ImageLoader().showImageByThread(viewHolder.icon, url);
mImageLoader.showImageByAsyncTask(viewHolder.icon,url);
// mImageLoader.showImageByThread(viewHolder.icon, url);
viewHolder.title.setText(mList.get(position).newsTitle);
viewHolder.content.setText(mList.get(position).newsContent);
return convertView;
}
class ViewHolder {
public ImageView icon;
public TextView title,content;
}
//实现OnScrollListener接口涉及的两个方法
/**
* 只有在ListView滑动的状态切换的时候才会调用
* 初始化的时候不会调用该方法,所以程序在首次打开的时候不会去加载图片
* 而标题和内容是在getView方法中设置的
* 所以还需要进行开始运行的程序时的预加载(在onScroll中设置)
*/
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
//如果当前状态是停止状态,就加载可见项
if(scrollState==SCROLL_STATE_IDLE) {
mImageLoader.loadImages(mStart, mEnd);
}
//否则,停止加载任务
else {
mImageLoader.cancelAllTasks();
}
}
/**
* 在整个滑动过程中都会去调用
* firstVisibleItem 代表第一个可见的元素(即item)
* visibleItemCount 代表当前可见元素的长度
*/
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
mStart=firstVisibleItem;
mEnd=firstVisibleItem+visibleItemCount-1;
//这里要注意,因为起始的基数为0,所以这里要减1,不然在ImageLoader的loadImages方法中会发生数组越界异常
//启动程序的时候调用,预加载
if (mFirstIn&&visibleItemCount>0) {//还要判断visibleItemCount是否大于0,当item还没有加载的时候,visibleItemCount是等于0的
mImageLoader.loadImages(mStart, mEnd);
mFirstIn=false;
}
}
}
至于为什么是在onScroll方法中实现预加载,可以参考一下这里:
http://www.tuicool.com/articles/mEneeq
(每次开始运行程序时,listview明明没有滚动,那为什么系统会调用onScroll方法呢?)