ListView中类似于微信朋友圈功能刷新的使用二

时间:2015-04-22 18:30:05   收藏:0   阅读:481

这是对上一篇blog的改进:主要有:增加一个内存缓存,首先ListVIew现在内存缓存中去寻找图片,不存在,再去本地缓存目录去寻找,不存在再去网上下载。然后,对于图片进行压缩,我们从网上或者内存中,读取出来的图片,他的分辨率等会比较高,我们不需要那么高的图片质量就可以了,所以我对图片进行了压缩,然后对缓存目录做了优化,当SD卡不存在的时候,则把缓存图片保存到手机自带的空间,存在则保存把SD卡。然后更新ImageView是利用Ui线程提供的一个方法,runOnUiThread去更新UI,大家可以以通过handle去更新也行。增加一些文件操作类。

下面请看代码:

MainActivity.java

public class MainActivity extends Activity
{
	private static ListView myListView;
	private static View footer;
	private static MyListViewAdapter adapter;
	private static boolean flag = true;
	private static Context context;
	public static final int OK = 1;//首次加载成功发送的信号
	public static final int ERROR = -1;//下载失败的信号
	public static final int YES = 2;//下拉时候加载成功的信号
	/* 第一个路径是用于初次加载,第二个路径是用于下拉刷新 */
	public static final String[] paths =
	{ "http://10.10.117.197:8080/web/mylist1.xml", "http://10.10.117.197:8080/web/mylist2.xml" };

	private static Handler mHandler = new Handler()
	{
		@SuppressWarnings("unchecked")
		public void handleMessage(android.os.Message msg)
		{
			if (msg.what == OK)// 用于首次下载数据成功之后绑定数据和适配器,以及listview
			{
				List<Picture> data = (List<Picture>) msg.obj;
				myListView.addFooterView(footer);
				adapter = new MyListViewAdapter(data, R.layout.listview_item, context);
				myListView.setAdapter(adapter);
				myListView.removeFooterView(footer);// 首次加载不用显式footer
			} else if (msg.what == YES)
			{
				flag = true;
				adapter.setDate((List<Picture>) msg.obj);
				adapter.notifyDataSetChanged();
				if (myListView.getFooterViewsCount() > 0)
				{
					myListView.removeFooterView(footer);
				}
			} else
			{
				Toast.makeText(context, "请检查网络", Toast.LENGTH_SHORT).show();
			}
		}
	};

	@Override
	protected void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);
		context = this;
		setContentView(R.layout.activity_main);
		myListView = (ListView) this.findViewById(R.id.my_listview);
		footer = getLayoutInflater().inflate(R.layout.footer, null);
		myListView.setOnScrollListener(new ListViewScrollListener());
		new LoadingDataThread(mHandler, paths[0], OK).start();
	}

	class ListViewScrollListener implements OnScrollListener
	{
		@Override
		public void onScrollStateChanged(AbsListView view, int scrollState)
		{
			if (adapter != null)
			{
				switch (scrollState) {
				case OnScrollListener.SCROLL_STATE_FLING://由于惯性listview任然在滚动
					adapter.setFlagBusy(true);
					break;
				case OnScrollListener.SCROLL_STATE_IDLE://不再滚动
					adapter.setFlagBusy(false);
					break;
				case OnScrollListener.SCROLL_STATE_TOUCH_SCROLL://用户触摸着屏幕
					adapter.setFlagBusy(false);
					break;
				default:
					break;
				}
				adapter.notifyDataSetChanged();
			}
		}

		@Override
		public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount)
		{
			if (adapter != null)
			{
				int lastVisiableItem = myListView.getLastVisiblePosition();
				if (totalItemCount == lastVisiableItem + 1 && totalItemCount > 0)
				{
					if (flag)
					{
						flag = false;
						myListView.addFooterView(footer);
						new LoadingDataThread(mHandler, paths[1], YES).start();
						// 下载新的数据
					}
				}
			}
		}
	}

	// 用户 退出时删除缓存
	@Override
	protected void onDestroy()
	{
		ImageViewLoader imageLoader = adapter.getLoader();// 获取一个loader,删除缓存
		if (imageLoader != null)
		{
			imageLoader.clearCache();// 清楚缓存
		}
		super.onDestroy();
	}
}

/**
 * 完成xml数据的下载,包括首次下载和以后每一次需要的下载
 * 
 * @author Administrator 黎伟杰
 *
 */
class LoadingDataThread extends Thread
{
	private Handler UIHnalder;
	private List<Picture> data = new ArrayList<Picture>();
	private String path;
	private int code;

	public LoadingDataThread (Handler mHandler , String path , int code)
	{
		this.UIHnalder = mHandler;
		this.path = path;
		this.code = code;
	}

	@Override
	public void run()
	{
		data.addAll(ListViewService.getData(path));// 得到数据并且通过Handler返回,更新适配器绑定的数据
		if (data.size() > 0)
		{
			if (code == MainActivity.YES)
			{
				try
				{
					Thread.sleep(2000);
				} catch (InterruptedException e)
				{
					e.printStackTrace();
				}
			}
			Message msg = Message.obtain();
			msg.what = code;// 根据不同的what发送给handler进行不同的处理
			msg.obj = data;
			UIHnalder.sendMessage(msg);// 发送数据
		} else
		{
			UIHnalder.sendEmptyMessage(MainActivity.ERROR);
		}
	}
}
MyListAdapter.java

/**
 * 适配器类
 * 运行在UI线程中
 * @author Administrator
 *
 */
public class MyListViewAdapter extends BaseAdapter
{
	private List<Picture> data = new ArrayList<Picture>();// 数据
	private int listviewItem;// listview显示条目的id
	private LayoutInflater inflater;// 加载layout器
	private ImageViewLoader loader;// 图片下载器
	
	private boolean mBusy = false;//当ListView不存在惯性的时为true,用户拖动或者没有触摸屏幕为false

	public void setFlagBusy(boolean busy) {
		this.mBusy = busy;
	}
	// 添加新的数据到adapter中
	public void setDate(List<Picture> data)
	{
		this.data.addAll(data);
	}

	/**
	 * @return the loader
	 */
	public ImageViewLoader getLoader()
	{
		return loader;
	}

	public MyListViewAdapter (List<Picture> data , int listviewItem , Context context)
	{
		this.data.addAll(data);
		this.listviewItem = listviewItem;
		inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
		loader = new ImageViewLoader(context);
	}

	public int getCount()
	{
		return data.size();
	}

	public Object getItem(int position)
	{
		return data.get(position);
	}

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

	public View getView(int position, View convertView, ViewGroup parent)
	{
		ViewHolder holder = null;
		if (convertView == null)
		{
			holder = new ViewHolder();
			convertView = inflater.inflate(listviewItem, null);
			holder.imageview = (ImageView) convertView.findViewById(R.id.iv_image);
			holder.textView = (TextView) convertView.findViewById(R.id.tv_tips);
			convertView.setTag(holder);
		} else
		{
			holder = (ViewHolder) convertView.getTag();
		}
		Picture picture = data.get(position);
		if (!mBusy) {
			loader.DisplayImage(picture.getPath(), holder.imageview, false);
			holder.textView.setText(picture.getName()+"--" + position+ "--IDLE ||TOUCH_SCROLL");
		} else {
			loader.DisplayImage(picture.getPath(), holder.imageview, false);		
			holder.textView.setText("--" + picture.getName()+"--FLING"+position );
		}
		return convertView;
	}

	// holder类,
	static class ViewHolder
	{
		ImageView imageview;
		TextView textView;
	}
}
ListService.java
public class ListViewService
{

	public static List<Picture> getData(String path)
	{
		// 192.168.1.109
		try
		{
			HttpURLConnection conn = (HttpURLConnection) new URL(path).openConnection();
			conn.setConnectTimeout(5000);
			conn.setRequestMethod("GET");
			if(200 == conn.getResponseCode())
			{
				return parserXML(conn.getInputStream());
			}
		} catch (IOException e)
		{
			e.printStackTrace();
		} catch (Exception e)
		{
			e.printStackTrace();
		}
		return null;
	}

	private static List<Picture> parserXML(InputStream inputStream) throws Exception
	{
		List<Picture> data = new ArrayList<Picture>();
		Picture picture = null;
		XmlPullParser parser = Xml.newPullParser();
		parser.setInput(inputStream,"UTF-8");
		int event = parser.getEventType();
		while(XmlPullParser.END_DOCUMENT != event)
		{
			switch (event)
			{
			case XmlPullParser.START_TAG:
				if("image".equals(parser.getName()))
				{
					picture = new Picture();
					picture.setName(parser.getAttributeValue(0));
					break;
				}
				if("path".equals(parser.getName()))
				{
					picture.setPath(parser.nextText());
					break;
				}
				break;

			case XmlPullParser.END_TAG:
				if("image".equals(parser.getName()))
				{
					data.add(picture);
					picture = null;
				}
				break;
			}
			event = parser.next();
		}
		System.out.println(data.size());
		return data;
	}

}
这三个类跟之前的功能差不多,代码也是,只是现在我们利用了ImageView去下载并且维护ImageView的更新

下面是新的重要代码:

ImageViewLoader.java,他的功能,利用线程池去下载维护图片,并且更新ImageVIew,分别利用内存缓存图片和本地缓存图片

/**
 * 图片下载,运行在UI线程
 * @author Administrator
 *
 */
public class ImageViewLoader
{
	private MemoryCache memoryCache = new MemoryCache();// 内存缓存
	private FileCache fileCache;// 文件缓存
	// 以弱键 实现的基于哈希表的 Map,在 WeakHashMap 中,当某个键不再正常使用时,将自动移除其条目。
	// 更精确地说,对于一个给定的键,其映射的存在并不阻止垃圾回收器对该键的丢弃,这就使该键成为可终止的,被终止,然后被回收。
	// 丢弃某个键时,其条目从映射中有效地移除,因此,该类的行为与其他的 Map 实现有所不同。
	private Map<ImageView, String> imageViews = Collections.synchronizedMap(new WeakHashMap<ImageView, String>());
	// 线程池
	private ExecutorService executorService;

	public ImageViewLoader (Context context)
	{
		fileCache = new FileCache(context);
		executorService = Executors.newFixedThreadPool(5);// 最大线程数量
	}

	/**
	 * 下载图片准备类
	 * @param url 下载路径
	 * @param imageview 显示组件
	 */
	public void DisplayImage(String url, ImageView imageview, boolean isLoadOnlyFromCache)
	{
		imageViews.put(imageview, url);

		Bitmap bitmap = memoryCache.get(url);
		
		if (bitmap != null)
		{
			imageview.setImageBitmap(bitmap);// 缓存中存在该bitmap,返回,否则下载
		} else if(!isLoadOnlyFromCache)
		{
			queuePhoto(imageview, url);//不存在与内存的缓存中,则取下文件中查找或者下载
		}

	}

	/**
	 * 防止图片错位
	 * @param photoToLoad
	 * @return
	 */
	private boolean imageViewReused(PhotoToLoad photoToLoad)
	{
		String tag = imageViews.get(photoToLoad.imageView);
		if (tag == null || !tag.equals(photoToLoad.url))
			return true;
		return false;
	}

	/**
	 * 下载图片
	 * @param url 路径
	 * @param imageView 需要显示该图片的ImageView组件
	 */
	private void queuePhoto(ImageView imageview, String url)
	{
		PhotoToLoad p = new PhotoToLoad(url, imageview);
		// 每一张需要下载的图片都需要提交给线程池处理,他的最大同时能运行的线程数是5,多余这个数目后来的线程会处于等待状态
		executorService.submit(new PhotosLoader(p));
	}

	/**
	 * 真正下载图片的方法,
	 * 
	 * @param url
	 *            返回一个经过压缩之后的bitmap
	 * @return
	 */
	private Bitmap getBitmap(String url)
	{
		File f = fileCache.getFile(url);// 查看缓存文件是否存在,存在去压缩,之后返回,不存在则取下载并且保存在SD卡目录中
		Bitmap b = null;
		if (f != null && f.exists())
		{
			b = decodeFile(f);
			return b;
		}
		InputStream is = null;
		FileOutputStream fos = null;
		try
		{
			URL imageUrl = new URL(url);// 从网络下载图片
			HttpURLConnection conn = (HttpURLConnection) imageUrl.openConnection();
			conn.setConnectTimeout(3000);
			conn.setReadTimeout(3000);
			conn.setRequestMethod("GET");
			if (200 == conn.getResponseCode())
			{
				is = conn.getInputStream();
				fos = new FileOutputStream(f);//下载图片到缓存目录
				int len;
				byte[] buffer = new byte[1024];
				while ((len = is.read(buffer)) != -1)
				{
					fos.write(buffer, 0, len);
				}
				b = decodeFile(f);//压缩图片
				return b;
			}
		} catch (MalformedURLException e)
		{
			e.printStackTrace();
		} catch (ProtocolException e)
		{
			e.printStackTrace();
		} catch (IOException e)
		{
			e.printStackTrace();
		} finally
		{
			if (is != null)
			{
				try
				{
					is.close();
				} catch (IOException e)
				{
					e.printStackTrace();
				}
			}
			if(fos != null)
			{
				try
				{
					fos.close();
				} catch (IOException e)
				{
					e.printStackTrace();
				}
			}
		}
		return null;
	}

	/**
	 * 对图片进行压缩
	 * @param f 需要压缩的图片
	 * @return
	 */
	private Bitmap decodeFile(File f)
	{
		try
		{
			BitmapFactory.Options o = new BitmapFactory.Options();// 获取一个option对象
			o.inJustDecodeBounds = true;//设置不显示实际的bitmap,获得他的实际宽和高
			BitmapFactory.decodeStream(new FileInputStream(f), null, o);
			final int REQUIRED_SIZE = 100;//需要显示的大小,应该是小与等于200(宽或者是高)
			int width_tmp = o.outWidth;
			int height_tmp = o.outHeight;
			System.out.println("width_tmp="+width_tmp+"height_tmp="+height_tmp);
			int scale = 1;
			while (true)
			{
				if (width_tmp / 2 < REQUIRED_SIZE || height_tmp / 2 < REQUIRED_SIZE)
				{
					break;
				}
				width_tmp /= 2;//进行压缩
				height_tmp /= 2;
				scale *= 2;
			}
			o.inJustDecodeBounds = false;//设置显示
			o.inSampleSize = scale;
			return BitmapFactory.decodeStream(new FileInputStream(f), null, o);

		} catch (FileNotFoundException e)
		{
			System.out.println("压缩失败");
		}
		return null;
	}


	public void clearCache()
	{
		memoryCache.clear();
		fileCache.clear();
	}

	/**
	 * 实际更新ImageView的类
	 * @author Administrator
	 *
	 */
	class BitmapDisplayer implements Runnable
	{
		Bitmap bmp;
		PhotoToLoad photoToLoad;

		public BitmapDisplayer (Bitmap bmp , PhotoToLoad photoToLoad)
		{
			this.bmp = bmp;
			this.photoToLoad = photoToLoad;
		}

		@Override
		public void run()
		{
			if (imageViewReused(photoToLoad))
			{
				return;
			}
			if (bmp != null)
			{
				photoToLoad.imageView.setImageBitmap(bmp);
			}
		}
	}
	
	private class PhotoToLoad
	{
		public String url;
		public ImageView imageView;
		
		public PhotoToLoad (String url , ImageView iamgeView)
		{
			this.url = url;
			this.imageView = iamgeView;
		}
	}
	
	/**
	 * 图片真正下载管理类类
	 * @author Administrator
	 *
	 */
	class PhotosLoader implements Runnable
	{
		PhotoToLoad photoToLoad;

		public PhotosLoader (PhotoToLoad photoToLoad)
		{
			this.photoToLoad = photoToLoad;
		}

		@Override
		public void run()
		{
			if (imageViewReused(photoToLoad))
			{
				return;
			}

			Bitmap bmp = getBitmap(photoToLoad.url);
			memoryCache.put(photoToLoad.url, bmp);// 添加到内存的缓存中
			if (imageViewReused(photoToLoad))
			{
				return;
			}
			BitmapDisplayer bd = new BitmapDisplayer(bmp, photoToLoad);
			Activity a = (Activity) photoToLoad.imageView.getContext();//更新UI,这里使用时UI线程中利用runOnUiThread方法更新,这里去的上下文
			a.runOnUiThread(bd);
		}
	}

}
内存缓存类:

MemoryCache.java

/**
 * 内存缓存处理类
 * @author Administrator
 *
 */
public class MemoryCache
{
	// 放入缓存时是个同步操作,线程安全
	// LinkedHashMap构造方法的最后一个参数true代表这个map里的元素将按照最近使用次数由少到多排列,即LRU
	// 这样的好处是如果要将缓存中的元素替换,则先遍历出最近最少使用的元素来替换以提高效率
	private Map<String, Bitmap> cache = Collections.synchronizedMap(new LinkedHashMap<String, Bitmap>(10, 1.5f, true));
	// 缓存中图片所占用的字节,初始0,将通过此变量严格控制缓存所占用的堆内存
	private long size = 0;// current allocated size
	// 缓存只能占用的最大堆内存
	private long limit = 1000000;// max memory in bytes,最大允许的内存缓存,自己随意设定,需要合适

	public MemoryCache()
	{
		// use 25% of available heap size
		setLimit(Runtime.getRuntime().maxMemory() / 10);// 返回当前最大可用内存整除10
	}

	public void setLimit(long new_limit)
	{
		limit = new_limit;
	}

	public Bitmap get(String id)
	{
		try
		{
			if (!cache.containsKey(id))// 如果不包含该uri对于的图片,则返回null
				return null;
			return cache.get(id);// 存在则返回该图片
		} catch (NullPointerException ex)
		{
			return null;
		}
	}

	public void put(String uri, Bitmap bitmap)
	{
		try
		{
			if (cache.containsKey(uri))// 假如已经包含了该uri的图片,则size应该不用增加,所以先减去该长度,添加进去之后在加上,假如不存在则把他的长度添加到size中
				size -= getSizeInBytes(cache.get(uri));
			cache.put(uri, bitmap);
			size += getSizeInBytes(bitmap);
			checkSize();
		} catch (Throwable th)
		{
			th.printStackTrace();
		}
	}

	/**
	 * 严格控制堆内存,如果超过将首先替换最近最少使用的那个图片缓存
	 * 
	 */
	private void checkSize()
	{
		if (size > limit)
		{
			// 先遍历最近最少使用的元素
			Iterator<Entry<String, Bitmap>> iter = cache.entrySet().iterator();
			while (iter.hasNext())
			{
				Entry<String, Bitmap> entry = iter.next();
				size -= getSizeInBytes(entry.getValue());
				iter.remove();
				if (size <= limit)
					break;
			}
		}
	}

	public void clear()
	{
		cache.clear();// 把内存中的cache去掉,相当于把data去掉
	}

	/**
	 * 图片占用的内存
	 * @return bitmap占用的字节数
	 */
	long getSizeInBytes(Bitmap bitmap)
	{
		if (bitmap == null)
			return 0;
		return bitmap.getRowBytes() * bitmap.getHeight();
	}
}

图片本地缓存:

FileCache.java

/**
 * 图片本地缓存
 * @author Administrator
 *
 */
public class FileCache
{
	private String dir;

	public FileCache (Context context)
	{
		dir = getCahceDir();
		createDirectory(dir);
	}

	/**
	 * 获取文件保存路径,包括文件名称
	 * @param uri
	 * @return
	 */
	public String getSavePath(String uri)
	{
		String fileName = String.valueOf(uri.hashCode());
		return getCahceDir() + fileName;
	}

	/**
	 *  返回文件名称,包含路径,
	 * @param uri
	 * @return
	 */
	public File getFile(String uri)
	{
		File file = new File(getSavePath(uri));
		return file;
	}

	/**
	 * 获得保存缓存图片的目录
	 * @return
	 */
	public String getCahceDir()
	{
		return FileManager.getSaveFilePath();
	}

	public void clear()
	{
		deleteDirectory(dir);
	}
	
	public static boolean createDirectory(String filePath){
		if (null == filePath) {
			return false;
		}

		File file = new File(filePath);

		if (file.exists()){
			return true;
		}
		
		return file.mkdirs();

	}
	
	/**
	 * 删除保存本地的图片
	 * @param dir 目录
	 * @return 是否成功
	 */
	public boolean deleteDirectory(String dir)
	{
		if (null == dir) {
			System.out.println("Invalid param. dir: " + dir);
			return false;
		}

		File file = new File(dir);

		if (file == null || !file.exists()) {
			return false;
		}

		if (file.isDirectory()) {
			File[] list = file.listFiles();

			for (int i = 0; i < list.length; i++) {
				System.out.println("delete dir: " + list[i].getAbsolutePath());
				if (list[i].isDirectory()) {
					deleteDirectory(list[i].getAbsolutePath());
				} else {
					list[i].delete();
				}
			}
		}

		System.out.println("delete dir: " + file.getAbsolutePath());
		file.delete();
		return true;
	}
}

缓存文件管理类;

FileManager.java

/**
 * 缓存文件管理
 * @author Administrator
 *
 */
public class FileManager
{

	public static String getSaveFilePath()
	{
		if (hasSDCard()) {
			return getRootFilePath() + "liweijie/files/";//存在SD卡的时候,则保存到SD卡
		} else {
			return getRootFilePath() + "liweijie/files";//不存在SD卡保存到手机
		}
	}
	public static String getRootFilePath()
	{
		if (hasSDCard())
		{
			return Environment.getExternalStorageDirectory().getAbsolutePath() + "/";// filePath:/sdcard/,有内存卡
		} else
		{
			return Environment.getDataDirectory().getAbsolutePath() + "/data/"; // filePath:
		}
	}
	/**
	 * sd卡是否处于可读可写状态
	 * @return
	 */
	public static boolean hasSDCard()
	{
		String status = Environment.getExternalStorageState();
		if (!status.equals(Environment.MEDIA_MOUNTED))
		{// 内存卡可读可写
			return false;
		}
		return true;
	}
}


domain类

Pisture.java

/**
 * domain类 包含属性的get和set方法
 * @author Administrator
 *
 */
public class Picture
{
	private String name;
	private String path;

	public Picture ()
	{
	}

	public Picture (String name , String path)
	{
		super();
		this.name = name;
		this.path = path;
	}

	/**
	 * @return the name
	 */
	public String getName()
	{
		return name;
	}

	/**
	 * @param name
	 *            the name to set
	 */
	public void setName(String name)
	{
		this.name = name;
	}

	/**
	 * @return the path
	 */
	public String getPath()
	{
		return path;
	}

	/**
	 * @param path
	 *            the path to set
	 */
	public void setPath(String path)
	{
		this.path = path;
	}
}

请求的xml数据格式:

<?xml version="1.0" encoding="UTF-8"?>
<contacts>
	<contact id="1">
 		<name>张飞</name>
 		<image src="http://10.10.117.197:8080/web/images/1.gif"/>
	</contact>
	<contact id="2">
 		<name>黎伟杰</name>
 		<image src="http://10.10.117.197:8080/web/images/2.gif"/>
	</contact>
	<contact id="3">
 		<name>黎AA</name>
 		<image src="http://10.10.117.197:8080/web/images/3.gif"/>
	</contact>
	<contact id="4">
 		<name>黎BB</name>
 		<image src="http://10.10.117.197:8080/web/images/4.gif"/>
	</contact>		
	<contact id="5">
 		<name>黎CC</name>
 		<image src="http://10.10.117.197:8080/web/images/5.gif"/>
	</contact>
	<contact id="6">
 		<name>黎DD</name>
 		<image src="http://10.10.117.197:8080/web/images/6.gif"/>
	</contact>
	<contact id="7">
 		<name>黎EE</name>
 		<image src="http://10.10.117.197:8080/web/images/7.gif"/>
	</contact>
	<contact id="8">
 		<name>黎FF</name>
 		<image src="http://10.10.117.197:8080/web/images/8.gif"/>
	</contact>
	<contact id="9">
 		<name>黎GG</name>
 		<image src="http://10.10.117.197:8080/web/images/9.gif"/>
	</contact>
	<contact id="10">
 		<name>黎HH</name>
 		<image src="http://10.10.117.197:8080/web/images/10.gif"/>
	</contact>	
</contacts>
关于ListView以及界面布局文件就不贴出来了,还有footer的也是,大家使用的时候记得加上权限。

结果截图:

技术分享

技术分享

技术分享


评论(0
© 2014 mamicode.com 版权所有 京ICP备13008772号-2  联系我们:gaon5@hotmail.com
迷上了代码!