Androi手动实现ListView+ImagLoader功能

 
Androi手动实现ListView+ImagLoader功能 
 三级缓概念:
 一级缓存:内存缓存,bitmap对象,Map<string,Bitmap> 结构保存,可以是url
 二级缓存:本地(sd卡)缓存,缓存图片文件   /storage/sdcard/Android/packageName/file/图片.png
 三级缓存: 1,2没有那么从服务器去取

 三级缓存伪代码:
   1. 根据url Key从本地中找Bitmap对象
        如果有,那么显示
        如果没有,那么进入2

   2.  从二级缓存中查找,得到sd卡下的bitmap
       如果有,显示,缓存到一级缓存
       没有进入3

   3. 现在正在加载图片,启动分线程联网请求bitmap
        如果没有,显示错误图片
        如果有: 显示
        缓存到一级缓存、二级缓存

  
  问题:ListVie使用三级缓存加载图片存在图片闪动bug 
  原因:conterView复用,conterView 现在A图片,去加载A图片,
  滑动以后conterView被B复用,去加载B图片
  滑动以后conterView别C复用,去加载C图片,
  可能由于网络原因,显示B,显示A,在显示C,错误
  解决方法:去加载C的时候,不要去加载A,B,即使加载完毕也不现实,存入缓存即可
  
  
   问题代码分析: 

  // 1.ListView 复用,每次滑动重新绘制item都会执行
        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            if(convertView==null) {
                convertView = View.inflate(MainActivity.this, R.layout.item_main, null);
            }
            imageLoader.loadImage(imagePath, imageView);
         }
         //2.Item复用以后为了可以以后  为了区分该item是否已经复用,使用保存tag方法,A用的保存A,那么B用的时候保存B
             public void loadImage(String imagePath, ImageView imageView) {
                   //将需要显示的图片url保存到视图上
              imageView.setTag(imagePath);
          }
              
             // 3.网络加载前:如果 A item被A 占据,但是此时滑动 Item已经B复用 【imageView.setTag(imagePath);】,那么不要去加载网络去图片了
                String newImagePath = (String) imageView.getTag();
                    if(newImagePath!=imagePath) {//视图已经被复用了 【imagePath 还是A的、tag是比的,这里把1,2级别缓存都可以单做综合耗时】
                        return null;
                    }
                    
                    // 4. 网络加载后,如果A item之前被A分配,网络延迟才执行完毕对于A[newImagePath], 此时已经被E复用,Tag已经E了,那么不显示A图片,把A图片缓存即可
                        String newImagePath = (String) imageView.getTag();
                if(newImagePath!=imagePath) {//视图已经被复用了
                    return;
                }

    直接上图: 

代码实现: 

<uses-permission android:name="android.permission.INTERNET"></uses-permission>

1. ShopInfo 实体类保存数据

package com.example.listimageloader;

/*
 * 商品的信息
 */
public class ShopInfo {

	private int id;
	private String name;
	private String imagepath;
	private double price;

	public ShopInfo() {
		super();
	}

	public ShopInfo(int id, String name, String imagepath, double price) {
		super();
		this.id = id;
		this.name = name;
		this.imagepath = imagepath;
		this.price = price;
	}

	public double getPrice() {
		return price;
	}

	public void setPrice(double price) {
		this.price = price;
	}

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getImagepath() {
		return imagepath;
	}

	public void setImagepath(String imagepath) {
		this.imagepath = imagepath;
	}

	@Override
	public String toString() {
		return "ShopInfo [id=" + id + ", name=" + name + ", imagepath="
				+ imagepath + ", price=" + price + "]";
	}

}


 2. ImageLoader 框架封装

package com.example.listimageloader;

import java.io.FileOutputStream;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.os.SystemClock;
import android.util.Log;
import android.widget.ImageView;

/**
 * 用于加载图片并显示的类
 * @author 张晓飞
 *
 */
/*
	String iamgePath = http://192.168.10.165:8080//L05_Web/images/f10.jpg和ImageView对象
	1). 根据url从一级缓存中取对应的bitmap对象
		如果有, 显示(结束)
		如果没有, 进入2)
	2). 从二级缓存中查找: 得到文件名并在sd卡的缓存目录下加载对应的图片得到Bitmap对象
		如果有: 显示, 缓存到一级缓存中(结束)
		如果没有, 进入3)
	3). 显示代表提示正在加载的图片, 启动分线程联网请求得到Bitmap对象
			如果没有: 显示提示错误的图片(结束)
			如果有: 
				显示
				缓存到一级缓存
				缓存到二级缓存
 */
public class ImageLoader {
	
	private Context context;
	private int loadingImageRes;
	private int errorImageRes;

	public ImageLoader(Context context, int loadingImageRes, int errorImageRes) {
		super();
		this.context = context;
		this.loadingImageRes = loadingImageRes;
		this.errorImageRes = errorImageRes;
	}

	//用于缓存bitmap的容器对象
	private Map<String, Bitmap> cacheMap = new HashMap<String, Bitmap>();
	
	/**
	 * 加载图片并显示
	 * @param imagePath
	 * @param imageView
	 */
	public void loadImage(String imagePath, ImageView imageView) {
		
		//将需要显示的图片url保存到视图上
		imageView.setTag(imagePath);
		
		/*
		 1). 根据url从一级缓存中取对应的bitmap对象
			如果有, 显示(结束)
			如果没有, 进入2)
		 */
		Bitmap bitmap = getFromFirstCache(imagePath);
		if(bitmap!=null) {
			imageView.setImageBitmap(bitmap);
			return;
		}
		/*
		2). 从二级缓存中查找: 得到文件名并在sd卡的缓存目录下加载对应的图片得到Bitmap对象
				如果有: 显示, 缓存到一级缓存中(结束)
				如果没有, 进入3)
			
			/storage/sdcard/Android/data/packageName/files/图片文件名(xxx.jpg)
		 */
		bitmap = getFromSecondCache(imagePath);
		if(bitmap!=null) {
			imageView.setImageBitmap(bitmap);
			cacheMap.put(imagePath, bitmap);
			return;
		}
		
		/*
		 3). 显示代表提示正在加载的图片, 启动分线程联网请求得到Bitmap对象
			如果没有: 显示提示错误的图片(结束)
			如果有: 
				缓存到一级缓存(分线程)
				缓存到二级缓存(分线程)
				显示(主线程)
				
		 */

		loadBitmapFromThirdCache(imagePath, imageView);
	}

	/**
	 * 根据图片url从三级缓存中取对应的bitmap对象并显示
	 * @param imagePath
	 * @param imageView
	 * AsyncTask
	 * loadBitmapFromThirdCache("../b.jpg", imageView)
	 * loadBitmapFromThirdCache("../f.jpg", imageView)--->imageView.setTag("../f.jpg")
	 */
	private void loadBitmapFromThirdCache(final String imagePath, final ImageView imageView) {
		new AsyncTask<Void, Void, Bitmap>() {
			protected void onPreExecute() {
				imageView.setImageResource(loadingImageRes);
			}
			
			//联网请求得到bitmap对象
			@Override
			protected Bitmap doInBackground(Void... params) {
				//在分线程执行, 可能需要等待一定时间才会执行
				//在等待的过程中imageView中的tag值就有可能改变了
				//如果改变了, 就不应该再去加载图片(此图片此时不需要显示)
				
				Bitmap bitmap = null;
				try {
					
					//在准备请求服务器图片之前, 判断是否需要加载
					String newImagePath = (String) imageView.getTag();
					if(newImagePath!=imagePath) {//视图已经被复用了
						return null;
					}

					SystemClock.sleep(100);

					//得到连接
					URL url = new URL(imagePath);
					HttpURLConnection connection = (HttpURLConnection) url.openConnection();
					//设置
					connection.setConnectTimeout(5000);
					connection.setReadTimeout(5000);
					//连接
					connection.connect();
					//发请求读取返回的数据并封装为bitmap
					int responseCode = connection.getResponseCode();
					if(responseCode==200) {
						InputStream is = connection.getInputStream();//图片文件流
						//将is封装为bitmap
						bitmap = BitmapFactory.decodeStream(is);
						is.close();
						
						if(bitmap!=null) {
							//缓存到一级缓存(分线程)
							cacheMap.put(imagePath, bitmap);
							//缓存到二级缓存(分线程)
							// /storage/sdcard/Android/data/packageName/files/
							String filesPath = context.getExternalFilesDir(null).getAbsolutePath();
							// http://192.168.10.165:8080//L05_Web/images/f10.jpg
							String fileName = imagePath.substring(imagePath.lastIndexOf("/")+1);//  f10.jpg
							String filePath = filesPath+"/"+fileName;
							Log.e("denganzhi","filePath:"+filePath);
							// 格式
							// 透明度 jpg 没有透明度 png 有透明度
							// 质量0-100   50 压缩比例50%
							bitmap.compress(CompressFormat.JPEG, 100, new FileOutputStream(filePath));
						}
					}
					connection.disconnect();
				} catch (Exception e) {
					e.printStackTrace();
				}
				
				
				return bitmap;
			}
			
			protected void onPostExecute(Bitmap bitmap) {//从联网请求图片到得到图片对象需要一定的时间, 视图可能被复用了,不需要显示
				//在主线程准备显示图片之前, 需要判断是否需要显示
				String newImagePath = (String) imageView.getTag();
				if(newImagePath!=imagePath) {//视图已经被复用了
					return;
				}
						
				//如果没有: 显示提示错误的图片(结束)
				if(bitmap==null) {
					imageView.setImageResource(errorImageRes);
				} else {//如果有, 显示
					imageView.setImageBitmap(bitmap);
				}
			}
		}.execute();
	}

	/**
	 * 根据图片url从二级缓存中取对应的bitmap对象
	 * @param imagePath
	 * @return
	 */
	private Bitmap getFromSecondCache(String imagePath) {
		
		// /storage/sdcard/Android/data/packageName/files/
		String filesPath = context.getExternalFilesDir(null).getAbsolutePath();
		// http://192.168.10.165:8080//L05_Web/images/f10.jpg
		String fileName = imagePath.substring(imagePath.lastIndexOf("/")+1);//  f10.jpg
		String filePath = filesPath+"/"+fileName;
		
		return BitmapFactory.decodeFile(filePath);
	}

	/**
	 * 根据图片url从一级缓存中取对应的bitmap对象
	 * @param imagePath
	 * @return
	 */
	private Bitmap getFromFirstCache(String imagePath) {
		return cacheMap.get(imagePath);
	}
}


3. MainActivity 数据加载 设置适配器,显示数据

package com.example.listimageloader;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

public class MainActivity extends AppCompatActivity {
    List<ShopInfo> datas=new ArrayList<>();

    private ShopInfoAdapter adapter;
    ListView lv_main;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        List<ShopInfo> list=getAllShops();
        Log.e("denganzhi",list.toString());
        adapter = new ShopInfoAdapter();
        lv_main = (ListView)findViewById(R.id.lv_main);
        lv_main.setAdapter(adapter);
    }

    /*
 * 得到所有商品信息对象的集合
 */
    private List<ShopInfo> getAllShops() {
        // 准备一个空集合
        datas= new ArrayList<ShopInfo>();
        // 得到images文件夹的真实路径
      //  String imagesPath = getServletContext().getRealPath("/images");
        String imagesPath = "http://192.168.2.110:8080/Web001/images";
        // 创建images文件夹File对象
        // 遍历
        for (int i = 1; i < 31; i++) {
            // 得到商品的相关信息
            int id = i + 1;

            String name = "f"+i + "的商品名称";
            String imagePath =imagesPath + "/f"+i+"."+"jpg";
            float price = new Random().nextInt(20) + 20;
            // 封装成对象
            ShopInfo info = new ShopInfo(id, name, imagePath, price);
            // 添加到集合中
            datas.add(info);
        }
        return datas;
    }
    class ShopInfoAdapter extends BaseAdapter {

        private ImageLoader imageLoader;

        public ShopInfoAdapter() {
            imageLoader = new ImageLoader(MainActivity.this, R.drawable.loading, R.drawable.error);
        }
        @Override
        public int getCount() {
            return datas.size();
        }

        @Override
        public Object getItem(int position) {
            return datas.get(position);
        }

        @Override
        public long getItemId(int position) {
            return 0;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            if(convertView==null) {
                convertView = View.inflate(MainActivity.this, R.layout.item_main, null);
            }
            //得到当前行的数据对象
            ShopInfo shopInfo = datas.get(position);
            //得到当前行的子View
            TextView nameTV = (TextView) convertView.findViewById(R.id.tv_item_name);
            TextView priceTV = (TextView) convertView.findViewById(R.id.tv_item_price);
            ImageView imageView = (ImageView) convertView.findViewById(R.id.iv_item_icon);
            //设置数据
            nameTV.setText(shopInfo.getName());
            priceTV.setText(shopInfo.getPrice()+"元");
            String imagePath = shopInfo.getImagepath();
            //根据图片路径启动分线程动态请求服务加载图片并显示
            imageLoader.loadImage(imagePath, imageView);
            return convertView;
        }

    }

}
发布了75 篇原创文章 · 获赞 68 · 访问量 11万+

猜你喜欢

转载自blog.csdn.net/dreams_deng/article/details/104727866
今日推荐