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)