`

Android实现ListView异步加载图片

阅读更多

今天在网上找了一整天如何实现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();   
        } 

 

分享到:
评论
4 楼 tyjxf 2012-10-06  
加载图片是个烦人的事,我也正在困扰中
3 楼 hack_zhang 2011-11-22  
不错的例子。。。。
2 楼 android_bin 2011-11-17  
不能用!!!!
1 楼 android_bin 2011-11-17  
多谢分享!!!

相关推荐

Global site tag (gtag.js) - Google Analytics