最简单也是最复杂的控件——ListView,这句话真的一点也不夸张。依稀记得大三下学期,抱着一本《Android第一行代码》,每天开开心心的学一点基础知识。UI学了没多少,就接触到了ListView。用个for循环,给ListView塞一串item,自己还可以滑,(@ο@) 哇~,这是极好的。
又扯远了,上干货。
假设一个界面就一个ListView,然后ListView的Item就是一个ImageView,额,撸代码:
activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:id="@+id/list_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
image_item.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/image"
android:layout_width="match_parent"
android:layout_height="150dp"
android:src="@mipmap/ic_launcher" />
</LinearLayout>
然后要做的就是从网络上获取图片放在ListView中显示,有人说这不简单,直接异步加载不就收工了么,先贴出来ImageAdapter的代码:
package com.cjt_pc.listviewtest;
/**
* Created by cjt-pc on 2015/10/13.
* Email:879309896@qq.com
*/
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.ArrayAdapter;
import android.widget.ImageView;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
public class ImageAdapter extends ArrayAdapter<String> {
private int resource;
public ImageAdapter(Context context, int resource, String[] objects) {
super(context, resource, objects);
this.resource = resource;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
String url = getItem(position);
ViewHolder viewHolder;
if (convertView == null) {
convertView = LayoutInflater.from(getContext()).inflate(resource, null);
viewHolder = new ViewHolder();
viewHolder.imageView = (ImageView) convertView.findViewById(R.id.image);
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
}
new BitmapTask(viewHolder.imageView).execute(url);
return convertView;
}
class ViewHolder {
ImageView imageView;
}
/**
* 异步下载图片的任务。
*
* @author cjt-pc
*/
class BitmapTask extends AsyncTask<String, Void, Bitmap> {
private ImageView imageView;
public BitmapTask(ImageView imageView) {
this.imageView = imageView;
}
@Override
protected Bitmap doInBackground(String... strings) {
String url = strings[0];
InputStream is = null;
Bitmap bitmap = null;
try {
is = new URL(url).openStream();
// 注意这里没有直接用decodeStream,是因为由于本身的原因这样极其不稳定
byte[] bytes = PictureCompressUtil.readStream(is);
bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
} catch (Exception e) {
e.printStackTrace();
return null;
} finally {
try {
if (is != null) {
is.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return bitmap;
}
@Override
protected void onPostExecute(Bitmap bitmap) {
imageView.setImageBitmap(bitmap);
}
}
}
图片地址的封装类:
package com.cjt_pc.listviewtest;
/**
* Created by cjt-pc on 2015/10/13.
* Email:879309896@qq.com
*/
public class Images {
public final static String[] imageUrls = new String[] {
"http://b.hiphotos.baidu.com/zhidao/pic/item/241f95cad1c8a7862039afc56609c93d71cf50ee.jpg",
"http://d.hiphotos.baidu.com/zhidao/pic/item/09fa513d269759ee5eb29a00b3fb43166c22df1f.jpg",
"http://h.hiphotos.baidu.com/zhidao/pic/item/fd039245d688d43f78756b397c1ed21b0ff43b19.jpg",
"http://e.hiphotos.baidu.com/zhidao/pic/item/30adcbef76094b3608b1fe26a2cc7cd98c109dde.jpg",
"http://a.hiphotos.baidu.com/zhidao/pic/item/42a98226cffc1e17bae869fa4b90f603728de9a2.jpg",
"http://f.hiphotos.baidu.com/zhidao/pic/item/b7003af33a87e950b86616a711385343faf2b48c.jpg",
"http://a.hiphotos.baidu.com/zhidao/pic/item/9922720e0cf3d7ca0b84dabdf31fbe096a63a98f.jpg",
"http://c.hiphotos.baidu.com/zhidao/pic/item/838ba61ea8d3fd1fa30f47f1314e251f94ca5ff3.jpg",
"http://a.hiphotos.baidu.com/zhidao/pic/item/d4628535e5dde71128bea63da6efce1b9c166189.jpg",
"http://f.hiphotos.baidu.com/zhidao/pic/item/e7cd7b899e510fb31a77925cd833c895d0430c8a.jpg",
"http://www.lipu.net/Photo_Max/200981321538.jpg",
"http://t1.niutuku.com/960/58/58-421244.jpg",
"http://a2.att.hudong.com/32/06/01300542212415138174064802766.jpg",
"http://static.bbs.sgnet.cc/attachment/forum/201211/17/110951koommdjbo7rbrr5r.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760758_3497.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760758_6667.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760757_3588.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760756_3304.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760755_6715.jpeg",
"https://img-my.csdn.net/uploads/201508/05/1438760726_5120.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760726_8364.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760725_4031.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760724_9463.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760724_2371.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760707_4653.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760706_6864.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760706_9279.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760704_2341.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760704_5707.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760685_5091.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760685_4444.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760684_8827.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760683_3691.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760683_7315.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760663_7318.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760662_3454.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760662_5113.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760661_3305.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760661_7416.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760589_2946.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760589_1100.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760588_8297.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760587_2575.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760587_8906.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760550_2875.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760550_9517.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760549_7093.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760549_1352.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760548_2780.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760531_1776.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760531_1380.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760530_4944.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760530_5750.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760529_3289.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760500_7871.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760500_6063.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760499_6304.jpeg",
"https://img-my.csdn.net/uploads/201508/05/1438760499_5081.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760498_7007.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760478_3128.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760478_6766.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760477_1358.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760477_3540.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760476_1240.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760446_7993.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760446_3641.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760445_3283.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760444_8623.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760444_6822.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760422_2224.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760421_2824.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760420_2660.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760420_7188.jpg",
"https://img-my.csdn.net/uploads/201508/05/1438760419_4123.jpg"
};
}
额,主Activity代码就简单了:
package com.cjt_pc.listviewtest;
import android.app.Activity;
import android.os.Bundle;
import android.widget.ListView;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ListView listView = (ListView) findViewById(R.id.list_view);
ImageAdapter adapter = new ImageAdapter(this, R.layout.image_item, Images.imageUrls);
listView.setAdapter(adapter);
}
}
是的,没有什么难度,但是运行之后就会直接闪退,OOM,何为OOM?out of memory!!内存溢出!!直观点说就是图片太大了,手机内存不够!!也是,用浏览器看看这些地址的图片,都是我精挑细选的,超大超级大有木有!!这样我们就遇到了第一个问题,OOM!!
仔细想想,一张3000*2000的图片,要放在300*200的ImageView中显示,直接加载有意义吗?答案当然是NO。这个时候我们就要想到压缩了,怎么压,借助Options这个属性,先看看这个工具类:
package com.cjt_pc.listviewtest;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
/**
* Created by cjt-pc on 2015/10/13.
* Email:879309896@qq.com
*/
public class PictureCompressUtil {
/**
* 获取适当的压缩比率
*
* @param options 解析参赛
* @param tarWidth 目标宽度
* @param tarHeight 目标高度
* @return 适当的InSampleSize
*/
public static int calculateInSampleSize(BitmapFactory.Options options, int tarWidth, int tarHeight) {
// 获取源图片的实际宽度和高度
final int realWidth = options.outWidth;
final int realHeight = options.outHeight;
int inSampleSize = 1;
if (realHeight > tarHeight || realWidth > tarWidth) {
// 计算出实际宽高和目标宽高的比率
final int heightRatio = Math.round((float) realHeight / (float) tarHeight);
final int widthRatio = Math.round((float) realWidth / (float) tarWidth);
// 注意:网上郭大神说去较小的那一个,实测选较小的那一个图片大了多了滑动起来会比较卡,选较大的那一个完美解决
inSampleSize = heightRatio > widthRatio ? heightRatio : widthRatio;
}
return inSampleSize;
}
public static Bitmap decodeSampledBitmapFromByteArray(byte[] bytes, int tarWidth, int tarHeight) {
// 第一次解析将inJustDecodeBounds设置为true,来获取图片大小
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options);
// 调用上面定义的方法计算inSampleSize值
options.inSampleSize = calculateInSampleSize(options, tarWidth, tarHeight);
// 使用获取到的inSampleSize值再次解析图片
options.inJustDecodeBounds = false;
return BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options);
}
public static byte[] readStream(InputStream inStream) throws Exception {
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len;
while ((len = inStream.read(buffer)) != -1) {
outStream.write(buffer, 0, len);
}
outStream.close();
inStream.close();
return outStream.toByteArray();
}
}
关于压缩就不细讲了,不是本篇的重点,代码也不是太难懂,需要指出的是,先设置options.inJustDecodeBounds为true,代表只解析边界,得到源图片的宽度和高度,然后根据需要需要放置的目标ImageView的大小计算一个适当的压缩比率,然后将options.inJustDecodeBounds设置为false即可完成解析图片,最后返回一个Bitmap对象。
修改哈ImageAdapter中部分代码:
package com.cjt_pc.listviewtest;
/**
* Created by cjt-pc on 2015/10/13.
* Email:879309896@qq.com
*/
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.ArrayAdapter;
import android.widget.ImageView;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
public class ImageAdapter extends ArrayAdapter<String> {
...
/**
* 异步下载图片的任务。
*
* @author cjt-pc
*/
class BitmapTask extends AsyncTask<String, Void, Bitmap> {
...
is = new URL(url).openStream();
// 注意这里没有直接用decodeStream,是因为由于本身的原因这样极其不稳定
byte[] bytes = PictureCompressUtil.readStream(is);
// bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
bitmap = PictureCompressUtil.decodeSampledBitmapFromByteArray(bytes, 1440, 300);
...
}
}
改动的地方非常少,只是把解析byte数组的方法封装成了一个工具类,然后再运行看看,没有闪退了出现OOM了吧,但是懊恼的是,为什么图片好乱,感觉有些图被加载了几遍,而且出现在了不该出现的位置。
是的,基于ListView的工作原理,离开屏幕的view转为convertView,能够重用提高效率,但试想,离开的view已经加载好的图片,然后在下一个重用该view的item上初始化的时候就会显示上一张图片,然后在异步加载图片完成后会显示本该显示的图片,这样就出现了闪屏、乱序的样子。
怎么解决呢?看看一般情况下采用ViewHolder提高效率时,是采用的convertView.setTag(viewHolder)形式,然后读的时候用viewHolder = (ViewHolder) convertView.getTag(),这样做的好处就是不用每次都用findViewById。
ImageView继承自View,当然也具有setTag(Object object)方法,一般情况下要是tag唯一,就设置成图片的url地址。那么怎么拿到绑定的ImageView呢?其实,除了findViewById之外,还有一个findViewByTag方法,这样思路就屡清楚了。
看看改进后的ImageAdapter源代码:
package com.cjt_pc.listviewtest;
/**
* Created by cjt-pc on 2015/10/13.
* Email:879309896@qq.com
*/
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.ArrayAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
public class ImageAdapter extends ArrayAdapter<String> {
private int resource;
private ListView mListView;
public ImageAdapter(Context context, int resource, String[] objects) {
super(context, resource, objects);
this.resource = resource;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
mListView = (ListView) parent;
String url = getItem(position);
ViewHolder viewHolder;
if (convertView == null) {
convertView = LayoutInflater.from(getContext()).inflate(resource, null);
viewHolder = new ViewHolder();
viewHolder.imageView = (ImageView) convertView.findViewById(R.id.image);
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
}
viewHolder.imageView.setTag(url);
viewHolder.imageView.setImageResource(R.mipmap.ic_launcher);
new BitmapTask().execute(url);
return convertView;
}
class ViewHolder {
ImageView imageView;
}
/**
* 异步下载图片的任务。
*
* @author cjt-pc
*/
class BitmapTask extends AsyncTask<String, Void, Bitmap> {
private String url;
@Override
protected Bitmap doInBackground(String... strings) {
url = strings[0];
InputStream is = null;
Bitmap bitmap = null;
try {
is = new URL(url).openStream();
// 注意这里没有直接用decodeStream,是因为由于本身的原因这样极其不稳定
byte[] bytes = PictureCompressUtil.readStream(is);
// bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
bitmap = PictureCompressUtil.decodeSampledBitmapFromByteArray(bytes, 1440, 300);
} catch (Exception e) {
e.printStackTrace();
return null;
} finally {
try {
if (is != null) {
is.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return bitmap;
}
@Override
protected void onPostExecute(Bitmap bitmap) {
ImageView imageView = (ImageView) mListView.findViewWithTag(url);
if (imageView != null) {
imageView.setImageBitmap(bitmap);
}
}
}
}
这里显示定义了一个全局变量mListView,然后在getView中初始化,修改BitmapTask的构造方法,不需传入ImageView,直接在异步加载完成后onPostExecute中拿到ImageView:ImageView imageView = (ImageView) mListView.findViewWithTag(url);如果得到的不为空,说明该item还没有离开屏幕,即刻给ImageView赋值。
还有,在getView方法中,加了两句话:viewHolder.imageView.setTag(url); viewHolder.imageView.setImageResource(R.mipmap.ic_launcher);这两句话十分重要,setTag是给ImageView绑定一个Tag,便于后面获取;setImageResource则是在item刚刚加载的时候给ImageView设置一个默认图片,这次是个android机器人,可自行设置。
哇咔咔,试试运行,大功告成有木有。但是出现了一个不好的用户体验,每次移出屏幕的图片再次显示必须得二次加载,这是极为不好的。因此我们又接触到了图片缓存技术——lruCache。这里就略微介绍一下,通过lruCache,分配一个合理的缓存空间,然后每加载一张就往里面塞一张,超过设置的大小后释放掉最不常用的缓存。有了这个算法,我们就可以轻易解决上述问题。
完善ImageAdapter:
package com.cjt_pc.listviewtest;
/**
* Created by cjt-pc on 2015/10/13.
* Email:879309896@qq.com
*/
import android.content.Context;
import android.graphics.Bitmap;
import android.os.AsyncTask;
import android.support.v4.util.LruCache;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
public class ImageAdapter extends ArrayAdapter<String> {
private int resource;
private ListView mListView;
// 图片缓存,以键值的方式存储
private LruCache<String, Bitmap> mMemoryCache;
public ImageAdapter(Context context, int resource, String[] objects) {
super(context, resource, objects);
this.resource = resource;
// 获取到可用内存的最大值,使用内存超出这个值会引起OutOfMemory异常。
// LruCache通过构造函数传入缓存值,以KB为单位。
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
// 使用最大可用内存值的1/8作为缓存的大小。
int cacheSize = maxMemory / 8;
mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
// 重写此方法来衡量每张图片的大小,默认返回图片数量。
return bitmap.getByteCount() / 1024;
}
};
}
// 添加bitmap到缓存中
public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
if (getBitmapFromMemCache(key) == null) {
mMemoryCache.put(key, bitmap);
}
}
// 从缓存中获取bitmap
public Bitmap getBitmapFromMemCache(String key) {
return mMemoryCache.get(key);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
mListView = (ListView) parent;
String url = getItem(position);
ViewHolder viewHolder;
if (convertView == null) {
convertView = LayoutInflater.from(getContext()).inflate(resource, null);
viewHolder = new ViewHolder();
viewHolder.imageView = (ImageView) convertView.findViewById(R.id.image);
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
}
viewHolder.imageView.setTag(url);
viewHolder.imageView.setImageResource(R.mipmap.ic_launcher);
loadBitmap(url, viewHolder.imageView);
return convertView;
}
// 通过图片缓存技术,快速重新加载和处理图片
public void loadBitmap(String imageKey, ImageView imageView) {
Bitmap bitmap = getBitmapFromMemCache(imageKey);
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
} else {
imageView.setImageResource(R.mipmap.ic_launcher);
new BitmapTask().execute(imageKey);
}
}
class ViewHolder {
ImageView imageView;
}
/**
* 异步下载图片的任务。
*
* @author cjt-pc
*/
class BitmapTask extends AsyncTask<String, Void, Bitmap> {
private String url;
@Override
protected Bitmap doInBackground(String... strings) {
url = strings[0];
InputStream is = null;
Bitmap bitmap = null;
try {
is = new URL(url).openStream();
// 注意这里没有直接用decodeStream,是因为由于本身的原因这样极其不稳定
byte[] bytes = PictureCompressUtil.readStream(is);
// bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
bitmap = PictureCompressUtil.decodeSampledBitmapFromByteArray(bytes, 1440, 300);
} catch (Exception e) {
e.printStackTrace();
return null;
} finally {
try {
if (is != null) {
is.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
addBitmapToMemoryCache(url, bitmap);
return bitmap;
}
@Override
protected void onPostExecute(Bitmap bitmap) {
ImageView imageView = (ImageView) mListView.findViewWithTag(url);
if (imageView != null) {
imageView.setImageBitmap(bitmap);
}
}
}
}
这样在滑动浏览的时候,移出屏幕的图片会加载到缓存中,若该图片在下一次加载的时候没有被释放掉,则直接读取设置到ImageView上,既加强了用户体验,也使程序更为的流畅。
啧啧,到了这里,是不是就真的会用ListView了呢,我可以放心的告诉你,快了T T。
对于这个程序的选材,我可是花了一番功夫,图片要大,又要多,还要好看。。。然而在快速滚动的时候仍显不足,只要是触发了getView并且缓存中没有该图片的会开启线程异步加载,这样的话快速滚动的话,会触发很多的异步加载线程,但是滚动过去的图片在加载完没有多大的意义,当屏的图片并没有显示,每个线程会分些网速,显示图片会越来越慢。所以我们要让它在滑动的时候不进行任何操作,在停止滚动的时候加载当前屏幕内的图片。
最终的ImageAdapter:
package com.cjt_pc.listviewtest;
/**
* Created by cjt-pc on 2015/10/13.
* Email:879309896@qq.com
*/
import android.content.Context;
import android.graphics.Bitmap;
import android.os.AsyncTask;
import android.support.v4.util.LruCache;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
public class ImageAdapter extends ArrayAdapter<String> implements AbsListView.OnScrollListener {
// 记录异步加载任务的集合
List<BitmapTask> taskList = new ArrayList<>();
private int resource;
private ListView mListView;
// 图片缓存,以键值的方式存储
private LruCache<String, Bitmap> mMemoryCache;
private int mFirstVisibleItem;
private int mVisibleItemCount;
private boolean isFirstEnter = true;
public ImageAdapter(Context context, int resource, String[] objects) {
super(context, resource, objects);
this.resource = resource;
// 获取到可用内存的最大值,使用内存超出这个值会引起OutOfMemory异常。
// LruCache通过构造函数传入缓存值,以KB为单位。
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
// 使用最大可用内存值的1/8作为缓存的大小。
int cacheSize = maxMemory / 8;
mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
// 重写此方法来衡量每张图片的大小,默认返回图片数量。
return bitmap.getByteCount() / 1024;
}
};
}
// 添加bitmap到缓存中
public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
if (getBitmapFromMemCache(key) == null) {
mMemoryCache.put(key, bitmap);
}
}
// 从缓存中获取bitmap
public Bitmap getBitmapFromMemCache(String key) {
return mMemoryCache.get(key);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
mListView = (ListView) parent;
String url = getItem(position);
ViewHolder viewHolder;
if (convertView == null) {
convertView = LayoutInflater.from(getContext()).inflate(resource, null);
viewHolder = new ViewHolder();
viewHolder.imageView = (ImageView) convertView.findViewById(R.id.image);
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
}
viewHolder.imageView.setTag(url);
viewHolder.imageView.setImageResource(R.mipmap.ic_launcher);
loadBitmap(url, viewHolder.imageView);
return convertView;
}
// 通过图片缓存技术,快速重新加载和处理图片
public void loadBitmap(String imageKey, ImageView imageView) {
Bitmap bitmap = getBitmapFromMemCache(imageKey);
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
}
}
@Override
public void onScrollStateChanged(AbsListView absListView, int i) {
// 仅当ListView静止时才去下载图片,GridView滑动时取消所有正在下载的任务
if (i == SCROLL_STATE_IDLE) {
loadBitmaps(mFirstVisibleItem, mVisibleItemCount);
} else {
cancelAllTasks();
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
int totalItemCount) {
mFirstVisibleItem = firstVisibleItem;
mVisibleItemCount = visibleItemCount;
// 下载的任务应该由onScrollStateChanged里调用,但首次进入程序时onScrollStateChanged并不会调用,
// 因此在这里为首次进入程序开启下载任务。
if (isFirstEnter && visibleItemCount > 0) {
loadBitmaps(firstVisibleItem, visibleItemCount);
isFirstEnter = false;
}
}
/**
* 加载Bitmap对象。此方法会在LruCache中检查所有屏幕中可见的ImageView的Bitmap对象,
* 如果发现任何一个ImageView的Bitmap对象不在缓存中,就会开启异步线程去下载图片。
*
* @param firstVisibleItem 第一个可见的ImageView的下标
* @param visibleItemCount 屏幕中总共可见的元素数
*/
private void loadBitmaps(int firstVisibleItem, int visibleItemCount) {
try {
for (int i = firstVisibleItem; i < firstVisibleItem + visibleItemCount; i++) {
String imageUrl = Images.imageUrls[i];
Bitmap bitmap = getBitmapFromMemCache(imageUrl);
if (bitmap == null) {
BitmapTask task = new BitmapTask();
taskList.add(task);
task.execute(imageUrl);
} else {
ImageView imageView = (ImageView) mListView.findViewWithTag(imageUrl);
if (imageView != null) {
imageView.setImageBitmap(bitmap);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
public void cancelAllTasks() {
if (!taskList.isEmpty()) {
// 遍历任务集合,取消正在进行的异步任务,然后清空任务列表
for (BitmapTask task : taskList) {
task.cancel(true);
}
taskList.clear();
}
}
class ViewHolder {
ImageView imageView;
}
/**
* 异步下载图片的任务。
*
* @author cjt-pc
*/
class BitmapTask extends AsyncTask<String, Void, Bitmap> {
private String url;
@Override
protected Bitmap doInBackground(String... strings) {
url = strings[0];
InputStream is = null;
Bitmap bitmap = null;
try {
is = new URL(url).openStream();
// 注意这里没有直接用decodeStream,是因为由于本身的原因这样极其不稳定
byte[] bytes = PictureCompressUtil.readStream(is);
// bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
bitmap = PictureCompressUtil.decodeSampledBitmapFromByteArray(bytes, 1440, 300);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (is != null) {
is.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
if (bitmap != null) {
addBitmapToMemoryCache(url, bitmap);
}
return bitmap;
}
@Override
protected void onPostExecute(Bitmap bitmap) {
ImageView imageView = (ImageView) mListView.findViewWithTag(url);
if (imageView != null && bitmap != null) {
imageView.setImageBitmap(bitmap);
}
// 要记得在异步加载过程完成后将本次任务移出
taskList.remove(this);
}
}
}
主要的就是让ImageAdapter实现了OnScorllListener的接口,然后要记住在Activity中加上监听:
package com.cjt_pc.listviewtest;
import android.app.Activity;
import android.os.Bundle;
import android.widget.AbsListView;
import android.widget.ListView;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ListView listView = (ListView) findViewById(R.id.list_view);
ImageAdapter adapter = new ImageAdapter(this, R.layout.image_item, Images.imageUrls);
listView.setAdapter(adapter);
listView.setOnScrollListener(adapter);
}
}
额,差不多了。总的来说,就四个方面
- 图片压缩
- 放置乱序
- 缓存技术
- 滑动监听
具体的上面都有,好了,就是这些了,我们终于敢说我会用ListView了额,GridView原理都是一样。今天就到这了,拜了个拜^-^。