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.javapublic 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)