今天在网上找了一整天如何实现ListView显示网络图片的方法
1:显示的图片比较小(1-2张)。这样我觉得不需要使用异步来加载,直接使用位图显示就OK
2:显示的图片比较多,可以使用异步加载。但存在BUG:单显示的条目很多。ListView需要往下拉的时候。图片不能显示,但是使用1的方法却能全部显示出来
参考资料:http://hulefei29.iteye.com/blog/616262
例子:
1、定义主界面,在界面里面添加一个ListView控件
main.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="wrap_content" android:layout_height="fill_parent"> <TextView android:id="@+id/indexTitle" android:layout_width="fill_parent" android:gravity="center" android:layout_height="wrap_content" android:text="使用ListView显示图片"/> <ListView android:id="@+id/searchList" android:layout_width="fill_parent" android:layout_height="wrap_content" android:choiceMode="singleChoice" /> </LinearLayout>
2、定义Item页面
search_list.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="wrap_content"> <ImageView android:id="@+id/stationImg" android:layout_width="wrap_content" android:layout_height="wrap_content" android:minWidth="80px" android:maxWidth="80px" android:minHeight="45px" android:maxHeight="45px" android:scaleType="fitXY" android:src="@drawable/icon" android:layout_margin="5px"/> <LinearLayout android:orientation="vertical" android:layout_width="wrap_content" android:layout_height="wrap_content"> <TextView android:id="@+id/stationTitle" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="#FFFFFFFF" android:textSize="22px" /> <TextView android:id="@+id/stationInfo" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="#FFFFFFFF" android:textSize="13px" /> </LinearLayout> </LinearLayout>
三、定义MainActivity
MainActivity.java
package lee.listviewimage;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import lee.listviewimage.R;
import android.app.Activity;
import android.os.Bundle;
import android.widget.ListView;
public class MainActivity extends Activity {
// private List<Map<String, Object>> generateData(List<XXXX> xxxxs) {
// List<Map<String, Object>> resList = new ArrayList<Map<String, Object>>();
// for (Iterator<XXXX> iterator = xxxxs.iterator(); iterator
// .hasNext();) {
// XXXX xxxx= (XXXX) iterator.next();
// Map<String, Object> resMap = new HashMap<String, Object>();
// resMap.put("title", xxxx.getTitle());
// resMap.put("info", xxxx.getInfo());
// resMap.put("img", xxxx.getImageUrl());
// resList.add(resMap);
// }
// return resList;
// }
ListView view;
List<Map<String, Object>> resList;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
view = (ListView) findViewById(R.id.searchList);
resList = new ArrayList<Map<String, Object>>();
Map<String, Object> resMap = new HashMap<String, Object>();
resMap.put("title", "title111");
resMap.put("info", "111111");
resMap.put("img", "http://tb.himg.baidu.com/sys/portrait/item/d71e5a30323837797979d300");
resList.add(resMap);
resMap = new HashMap<String, Object>();
resMap.put("title", "title222");
resMap.put("info", "222222");
resMap.put("img", "http://img.baidu.com/img/post-jg.gif");
resList.add(resMap);
SearchAdapter adapter = new SearchAdapter(
this,resList, R.layout.search_list,
new String[] {"title", "info", "img" },
new int[] {R.id.stationTitle, R.id.stationInfo,R.id.stationImg
});
view.setAdapter(adapter);
}
}
四、定义通过图片url返回图片Bitmap的工具类"(如果不需要异步,直接调用这个方法)
WebImageBuilder.java
package lee.listviewimage;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
public class WebImageBuilder {
/**
* 通过图片url返回图片Bitmap
* @param url
* @return
*/
public static Bitmap returnBitMap(String path) {
URL url = null;
Bitmap bitmap = null;
try {
url = new URL(path);
} catch (MalformedURLException e) {
e.printStackTrace();
}
try {
HttpURLConnection conn = (HttpURLConnection) url.openConnection();//利用HttpURLConnection对象,我们可以从网络中获取网页数据.
conn.setDoInput(true);
conn.connect();
InputStream is = conn.getInputStream(); //得到网络返回的输入流
bitmap = BitmapFactory.decodeStream(is);
is.close();
} catch (IOException e) {
e.printStackTrace();
}
return bitmap;
}
}
五、定义异步加载图片的工具类
AsyncImageLoader.java
package lee.listviewimage;
import java.lang.ref.SoftReference;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.Message;
public class AsyncImageLoader {
private Map<String, SoftReference<Drawable>> imageCache = new HashMap<String, SoftReference<Drawable>>();
public Drawable loadDrawable(final String imageUrl,final ImageCallback callback) {
if (imageCache.containsKey(imageUrl)) {
SoftReference<Drawable> softReference = imageCache.get(imageUrl);
if (softReference.get() != null) {
return softReference.get();
}
}
final Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
callback.imageLoaded((Drawable) msg.obj, imageUrl);
}
};
//load data
new Thread() {
public void run() {
Drawable drawable = loadImageFromUrl(imageUrl);
imageCache.put(imageUrl, new SoftReference<Drawable>(drawable));
handler.sendMessage(handler.obtainMessage(0, drawable));
};
}.start();
return null;
}
protected Drawable loadImageFromUrl(String imageUrl) {
try {
return Drawable.createFromStream(new URL(imageUrl).openStream(),
"src");
} catch (Exception e) {
throw new RuntimeException(e);
}
}
//call back interface
public interface ImageCallback {
public void imageLoaded(Drawable imageDrawable, String imageUrl);
}
}
六、定义异步加载图片的方法
PS:原文章说只有 public SearchAdapter构造方法和 public void setViewImage(final ImageView v, String url)有用,其他都是源代码。理论上可以去掉不写,但去掉后确不能正常显示图片,有全部都是同一张图片,或者只显示1条数据,但去掉的方法却可以不在异步的显示图片中测试成功。(看第七点)
SearchAdapter.java
package lee.listviewimage;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Checkable;
import android.widget.ImageView;
import android.widget.SimpleAdapter;
import android.widget.TextView;
public class SearchAdapter extends SimpleAdapter {
private AsyncImageLoader imageLoader = new AsyncImageLoader();
private Map<Integer, View> viewMap = new HashMap<Integer, View>();
private ViewBinder mViewBinder;
private List<? extends Map<String, ?>> mData; //List列表存放的数据
private int mResource; //绑定的页面 ,例如:R.layout.search_item,
private LayoutInflater mInflater;
private String[] mFrom; //绑定控件对应的数组里面的值名称
private int[] mTo; //绑定控件的ID
//构造器
public SearchAdapter(Context context, List<? extends Map<String, ?>> data,int resource, String[] from, int[] to) {
super(context, data, resource, from, to);
mData = data;
mResource = resource;
mFrom = from;
mTo = to;
// 布局泵(LayoutInflater)根据XML布局文件来绘制视图(View)对象。这个类无法直接创建实例,要通过context对象的getLayoutInflater()或getSystemService(String)方法来获得实例,这样获得的布局泵实例符合设备的环境配置。
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
/*
SimpleAdapter基类显示每个Item都是通过这个方法生成的
在getView(int position, View convertView, ViewGroup parent)中又调用了SimpleAdapter的私有方法createViewFromResource
来组装View,在createViewFromResource中对SimpleAdapter的参数String[] from 和int[] to进行了组装
*/
public View getView(int position, View convertView, ViewGroup parent) {
return createViewFromResource(position, convertView, parent, mResource); //调用下面方法
}
//在createViewFromResource方法中又有一个bindView(position, v)方法对item中的各个View进行了组装,bindView(position, v)
private View createViewFromResource(int position, View convertView, ViewGroup parent, int resource) {
View rowView = this.viewMap.get(position);
if (rowView == null) {
rowView = mInflater.inflate(resource, null);
final int[] to = mTo;
final int count = to.length;
final View[] holder = new View[count];
for (int i = 0; i < count; i++) {
holder[i] = rowView.findViewById(to[i]);
}
rowView.setTag(holder);
bindView(position, rowView); //调用下面方法对Item中的
viewMap.put(position, rowView);
}
return rowView;
}
//对ViewImage进行组装的代码了“else if (v instanceof ImageView)”
@SuppressWarnings("unchecked")
private void bindView(int position, View view) {
final Map dataSet = mData.get(position);
if (dataSet == null) {
return;
}
final ViewBinder binder = mViewBinder;
final View[] holder = (View[]) view.getTag();
final String[] from = mFrom;
final int[] to = mTo;
final int count = to.length;
for (int i = 0; i < count; i++) {
final View v = holder[i];
if (v != null) {
final Object data = dataSet.get(from[i]);
String urlText = null;
if (data == null) {
urlText = "";
} else {
urlText = data.toString();
}
boolean bound = false;
if (binder != null) {
bound = binder.setViewValue(v, data, urlText);
}
if (!bound) {
if (v instanceof Checkable) {
if (data instanceof Boolean) {
((Checkable) v).setChecked((Boolean) data);
} else {
throw new IllegalStateException(v.getClass()
.getName()
+ " should be bound to a Boolean, not a "
+ data.getClass());
}
} else if (v instanceof TextView) {
setViewText((TextView) v, urlText);
} else if (v instanceof ImageView) {
if (data instanceof Integer) {
setViewImage((ImageView) v, (Integer) data);
} else {
setViewImage((ImageView) v, urlText);
}
} else {
throw new IllegalStateException(
v.getClass().getName()
+ " is not a "
+ " view that can be bounds by this SimpleAdapter");
}
}
}
}
}
public void setViewImage(ImageView v, int value) {
v.setImageResource(value);
}
public void setViewImage(final ImageView v, String url) {
//如果只是单纯的把图片显示,而不进行缓存。直接用下面的方法拿到URL的Bitmap就行显示就OK
// Bitmap bitmap = WebImageBuilder.returnBitMap(url);
// ((ImageView) v).setImageBitmap(bitmap);
imageLoader.loadDrawable(url, new AsyncImageLoader.ImageCallback() {
public void imageLoaded(Drawable imageDrawable, String imageUrl) {
if(imageDrawable!=null && imageDrawable.getIntrinsicWidth()>0 ) {
v.setImageDrawable(imageDrawable);
}
}
});
}
}
七、如果不需要异步加载,可以修改上面的SearchAdapter方法
(1):可以在上面的基础上修改public void setViewImage(final ImageView v, String url)方法
public void setViewImage(final ImageView v, String url) {
Bitmap bitmap = WebImageBuilder.returnBitMap(url);
((ImageView) v).setImageBitmap(bitmap);
}
(2)可以只剩下public SearchAdapter构造方法和 public void setViewImage(final ImageView v, String url)2个方法,其他方法都去掉
package lee.listviewimage;
import java.util.List;
import java.util.Map;
import android.content.Context;
import android.graphics.Bitmap;
import android.widget.ImageView;
import android.widget.SimpleAdapter;
public class SearchAdapter extends SimpleAdapter {
//构造器
public SearchAdapter(Context context, List<? extends Map<String, ?>> data,int resource, String[] from, int[] to) {
super(context, data, resource, from, to);
}
public void setViewImage(final ImageView v, String url) {
Bitmap bitmap = WebImageBuilder.returnBitMap(url);
((ImageView) v).setImageBitmap(bitmap);
}
}
如果只是打开单个图片可以使用这个方法
PS:异步方法应该也可以精简到上面那样只剩下2个方法,但测试不成功。,有待修改
问题1:
今天测试的时候又发现了问题:单图片的地址错误或者无法连接时,使用异步的方法虽然能正常显示其他图片,但单连接错误的图片超时后。系统会自动跳出提示错误信息。原来源文件没有处理这种情况,所以需要对异步的AsyncImageLoader.java文件的loadImageFromUrl方法进行修改
//定义方法链接URL获取输入流,然后转换成Drawable
protected Drawable loadImageFromUrl(String imageUrl) {
try {
return Drawable.createFromStream(new URL(imageUrl).openStream(),"src");//当URL不正确或者链接不上。new URL(imageUrl).openStream()会抛错。所以需要在抛错的时候返回NULL。
} catch (Exception e) {
// throw new RuntimeException(e);
return Drawable.createFromStream(null,"src");
}
}:
问题2:
单显示的条目很多。需要Listview往下拉,这样当翻页的图片如果是第一页相同的图片。会出现不能显示的问题,这样就导致了没有缓冲的同能。
原因:
主要是因为
if (imageCache.containsKey(imageUrl)) {
SoftReference<Drawable> softReference = imageCache.get(imageUrl);
if (softReference.get() != null) {
return softReference.get();
}
}
这段代码没有起作用。因为他只是直接返回softReference.get();结果。并没有对他进行显示
解决:
把上面的代码放到线程里面去。同时优化了一下。加了线程池管理线程启动的个数
try {
threadPool.execute(new Runnable() {
@Override
public void run() {
if (imageCache.containsKey(imageUrl)) { //检查缓冲imageCache是否存在对应的KEY
SoftReference<Drawable> softReference = imageCache.get(imageUrl); //存在就获取对应的值
Log.i("abc", "1:"+softReference.get().toString());
if (softReference.get() != null) {
Log.i("abc", "2:"+softReference.get().toString());
Message message = handler.obtainMessage(0, softReference.get());
handler.sendMessage(message);
}
}else{
Drawable drawable = loadImageFromUrl(imageUrl); //使用下面的方法获取Drawable
imageCache.put(imageUrl, new SoftReference<Drawable>(drawable)); //把图片放到HasMap中
Log.i("abc", "0:"+drawable);
Message message = handler.obtainMessage(0, drawable);
handler.sendMessage(message);
}
}
});
} catch (Exception e) {
e.printStackTrace();
}