0

Android 使用开源库StickyGridHeaders来实现带sections和headers的GridView显示本地图片效果

转载请注明本文出自xiaanming的博客(http://blog.csdn.net/xiaanming/article/details/20481185),请尊重他人的辛勤劳动成果,谢谢!

大家好!过完年回来到现在差不多一个月没写文章了,一是觉得不知道写哪些方面的文章,没有好的题材来写,二是因为自己的一些私事给耽误了,所以过完年的第一篇文章到现在才发表出来,2014年我还是会继续在CSDN上面更新我的博客,欢迎大家关注一下,今天这篇文章主要的是介绍下开源库StickyGridHeaders的使用,StickyGridHeaders是一个自定义GridView带sections和headers的Android库,sections就是GridView item之间的分隔,headers就是固定在GridView顶部的标题,类似一些Android手机联系人的效果,StickyGridHeaders的介绍在https://github.com/TonicArtos/StickyGridHeaders,与此对应也有一个相同效果的自定义ListView带sections和headers的开源库https://github.com/emilsjolander/StickyListHeaders,大家有兴趣的可以去看下,我这里介绍的是StickyGridHeaders的使用,我在Android应用方面看到使用StickyGridHeaders的不是很多,而是在Iphone上看到相册采用的是这种效果,于是我就使用StickyGridHeaders来仿照Iphone按照日期分隔显示本地图片

我们先新建一个Android项目StickyHeaderGridView,去https://github.com/TonicArtos/StickyGridHeaders下载开源库,为了方便浏览源码我直接将源码拷到我的工程中了

com.tonicartos.widget.stickygridheaders这个包就是我放StickyGridHeaders开源库的源码,com.example.stickyheadergridview这个包是我实现此功能的代码,类看起来还蛮多的,下面我就一一来介绍了

GridItem用来封装StickyGridHeadersGridView 每个Item的数据,里面有本地图片的路径,图片加入手机系统的时间和headerId

  1. package com.example.stickyheadergridview;
  2. /**
  3.  * @blog http://blog.csdn.net/xiaanming
  4.  * 
  5.  * @author xiaanming
  6.  *
  7.  */
  8. public class GridItem {
  9.     /**
  10.      * 图片的路径
  11.      */
  12.     private String path;
  13.     /**
  14.      * 图片加入手机中的时间,只取了年月日
  15.      */
  16.     private String time;
  17.     /**
  18.      * 每个Item对应的HeaderId
  19.      */
  20.     private int headerId;
  21.     public GridItem(String path, String time) {
  22.         super();
  23.         this.path = path;
  24.         this.time = time;
  25.     }
  26.     public String getPath() {
  27.         return path;
  28.     }
  29.     public void setPath(String path) {
  30.         this.path = path;
  31.     }
  32.     public String getTime() {
  33.         return time;
  34.     }
  35.     public void setTime(String time) {
  36.         this.time = time;
  37.     }
  38.     public int getHeaderId() {
  39.         return headerId;
  40.     }
  41.     public void setHeaderId(int headerId) {
  42.         this.headerId = headerId;
  43.     }
  44. }

图片的路径path和图片加入的时间time 我们直接可以通过ContentProvider获取,但是headerId需要我们根据逻辑来生成。

  1. package com.example.stickyheadergridview;
  2. import android.content.ContentResolver;
  3. import android.content.Context;
  4. import android.content.Intent;
  5. import android.database.Cursor;
  6. import android.net.Uri;
  7. import android.os.Environment;
  8. import android.os.Handler;
  9. import android.os.Message;
  10. import android.provider.MediaStore;
  11. /**
  12.  * 图片扫描器
  13.  * 
  14.  * @author xiaanming
  15.  *
  16.  */
  17. public class ImageScanner {
  18.     private Context mContext;
  19.     public ImageScanner(Context context){
  20.         this.mContext = context;
  21.     }
  22.     /**
  23.      * 利用ContentProvider扫描手机中的图片,将扫描的Cursor回调到ScanCompleteCallBack
  24.      * 接口的scanComplete方法中,此方法在运行在子线程中
  25.      */
  26.     public void scanImages(final ScanCompleteCallBack callback) {
  27.         final Handler mHandler = new Handler() {
  28.             @Override
  29.             public void handleMessage(Message msg) {
  30.                 super.handleMessage(msg);
  31.                 callback.scanComplete((Cursor)msg.obj);
  32.             }
  33.         };
  34.         new Thread(new Runnable() {
  35.             @Override
  36.             public void run() {
  37.                 //先发送广播扫描下整个sd卡
  38.                 mContext.sendBroadcast(new Intent(
  39.                             Intent.ACTION_MEDIA_MOUNTED,
  40.                             Uri.parse(“file://” + Environment.getExternalStorageDirectory())));
  41.                 Uri mImageUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
  42.                 ContentResolver mContentResolver = mContext.getContentResolver();
  43.                 Cursor mCursor = mContentResolver.query(mImageUri, nullnullnull, MediaStore.Images.Media.DATE_ADDED);
  44.                 //利用Handler通知调用线程
  45.                 Message msg = mHandler.obtainMessage();
  46.                 msg.obj = mCursor;
  47.                 mHandler.sendMessage(msg);
  48.             }
  49.         }).start();
  50.     }
  51.     /**
  52.      * 扫描完成之后的回调接口
  53.      *
  54.      */
  55.     public static interface ScanCompleteCallBack{
  56.         public void scanComplete(Cursor cursor);
  57.     }
  58. }

ImageScanner是一个图片的扫描器类,该类使用ContentProvider扫描手机中的图片,我们通过调用scanImages()方法就能对手机中的图片进行扫描,将扫描的Cursor回调到ScanCompleteCallBack 接口的scanComplete方法中,由于考虑到扫描图片属于耗时操作,所以该操作运行在子线程中,在我们扫描图片之前我们需要先发送广播来扫描外部媒体库,为什么要这么做呢,假如我们新增加一张图片到sd卡,图片确实已经添加了进去,但是我们此时的媒体库还没有同步更新,若不同步媒体库我们就看不到新增加的图片,当然我们可以通过重新启动系统来更新媒体库,但是这样不可取,所以我们直接发送广播就可以同步媒体库了。

  1. package com.example.stickyheadergridview;
  2. import java.util.concurrent.ExecutorService;
  3. import java.util.concurrent.Executors;
  4. import android.graphics.Bitmap;
  5. import android.graphics.BitmapFactory;
  6. import android.graphics.Point;
  7. import android.os.Handler;
  8. import android.os.Message;
  9. import android.support.v4.util.LruCache;
  10. import android.util.Log;
  11. /**
  12.  * 本地图片加载器,采用的是异步解析本地图片,单例模式利用getInstance()获取NativeImageLoader实例
  13.  * 调用loadNativeImage()方法加载本地图片,此类可作为一个加载本地图片的工具类
  14.  * 
  15.  * @blog http://blog.csdn.net/xiaanming
  16.  * 
  17.  * @author xiaanming
  18.  *
  19.  */
  20. public class NativeImageLoader {
  21.     private static final String TAG = NativeImageLoader.class.getSimpleName();
  22.     private static NativeImageLoader mInstance = new NativeImageLoader();
  23.     private static LruCache<String, Bitmap> mMemoryCache;
  24.     private ExecutorService mImageThreadPool = Executors.newFixedThreadPool(1);
  25.     private NativeImageLoader(){
  26.         //获取应用程序的最大内存
  27.         final int maxMemory = (int) (Runtime.getRuntime().maxMemory());
  28.         //用最大内存的1/8来存储图片
  29.         final int cacheSize = maxMemory / 8;
  30.         mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
  31.             //获取每张图片的bytes
  32.             @Override
  33.             protected int sizeOf(String key, Bitmap bitmap) {
  34.                 return bitmap.getRowBytes() * bitmap.getHeight();
  35.             }
  36.         };
  37.     }
  38.     /**
  39.      * 通过此方法来获取NativeImageLoader的实例
  40.      * @return
  41.      */
  42.     public static NativeImageLoader getInstance(){
  43.         return mInstance;
  44.     }
  45.     /**
  46.      * 加载本地图片,对图片不进行裁剪
  47.      * @param path
  48.      * @param mCallBack
  49.      * @return
  50.      */
  51.     public Bitmap loadNativeImage(final String path, final NativeImageCallBack mCallBack){
  52.         return this.loadNativeImage(path, null, mCallBack);
  53.     }
  54.     /**
  55.      * 此方法来加载本地图片,这里的mPoint是用来封装ImageView的宽和高,我们会根据ImageView控件的大小来裁剪Bitmap
  56.      * 如果你不想裁剪图片,调用loadNativeImage(final String path, final NativeImageCallBack mCallBack)来加载
  57.      * @param path
  58.      * @param mPoint
  59.      * @param mCallBack
  60.      * @return
  61.      */
  62.     public Bitmap loadNativeImage(final String path, final Point mPoint, final NativeImageCallBack mCallBack){
  63.         //先获取内存中的Bitmap
  64.         Bitmap bitmap = getBitmapFromMemCache(path);
  65.         final Handler mHander = new Handler(){
  66.             @Override
  67.             public void handleMessage(Message msg) {
  68.                 super.handleMessage(msg);
  69.                 mCallBack.onImageLoader((Bitmap)msg.obj, path);
  70.             }
  71.         };
  72.         //若该Bitmap不在内存缓存中,则启用线程去加载本地的图片,并将Bitmap加入到mMemoryCache中
  73.         if(bitmap == null){
  74.             mImageThreadPool.execute(new Runnable() {
  75.                 @Override
  76.                 public void run() {
  77.                     //先获取图片的缩略图
  78.                     Bitmap mBitmap = decodeThumbBitmapForFile(path, mPoint == null ? 0: mPoint.x, mPoint == null ? 0: mPoint.y);
  79.                     Message msg = mHander.obtainMessage();
  80.                     msg.obj = mBitmap;
  81.                     mHander.sendMessage(msg);
  82.                     //将图片加入到内存缓存
  83.                     addBitmapToMemoryCache(path, mBitmap);
  84.                 }
  85.             });
  86.         }
  87.         return bitmap;
  88.     }
  89.     /**
  90.      * 往内存缓存中添加Bitmap
  91.      * 
  92.      * @param key
  93.      * @param bitmap
  94.      */
  95.     private void addBitmapToMemoryCache(String key, Bitmap bitmap) {
  96.         if (getBitmapFromMemCache(key) == null && bitmap != null) {
  97.             mMemoryCache.put(key, bitmap);
  98.         }
  99.     }
  100.     /**
  101.      * 根据key来获取内存中的图片
  102.      * @param key
  103.      * @return
  104.      */
  105.     private Bitmap getBitmapFromMemCache(String key) {
  106.         Bitmap bitmap = mMemoryCache.get(key);
  107.         if(bitmap != null){
  108.             Log.i(TAG, “get image for LRUCache , path = “ + key);
  109.         }
  110.         return bitmap;
  111.     }
  112.     /**
  113.      * 清除LruCache中的bitmap
  114.      */
  115.     public void trimMemCache(){
  116.         mMemoryCache.evictAll();
  117.     }
  118.     /**
  119.      * 根据View(主要是ImageView)的宽和高来获取图片的缩略图
  120.      * @param path
  121.      * @param viewWidth
  122.      * @param viewHeight
  123.      * @return
  124.      */
  125.     private Bitmap decodeThumbBitmapForFile(String path, int viewWidth, int viewHeight){
  126.         BitmapFactory.Options options = new BitmapFactory.Options();
  127.         //设置为true,表示解析Bitmap对象,该对象不占内存
  128.         options.inJustDecodeBounds = true;
  129.         BitmapFactory.decodeFile(path, options);
  130.         //设置缩放比例
  131.         options.inSampleSize = computeScale(options, viewWidth, viewHeight);
  132.         //设置为false,解析Bitmap对象加入到内存中
  133.         options.inJustDecodeBounds = false;
  134.         Log.e(TAG, “get Iamge form file,  path = “ + path);
  135.         return BitmapFactory.decodeFile(path, options);
  136.     }
  137.     /**
  138.      * 根据View(主要是ImageView)的宽和高来计算Bitmap缩放比例。默认不缩放
  139.      * @param options
  140.      * @param width
  141.      * @param height
  142.      */
  143.     private int computeScale(BitmapFactory.Options options, int viewWidth, int viewHeight){
  144.         int inSampleSize = 1;
  145.         if(viewWidth == 0 || viewWidth == 0){
  146.             return inSampleSize;
  147.         }
  148.         int bitmapWidth = options.outWidth;
  149.         int bitmapHeight = options.outHeight;
  150.         //假如Bitmap的宽度或高度大于我们设定图片的View的宽高,则计算缩放比例
  151.         if(bitmapWidth > viewWidth || bitmapHeight > viewWidth){
  152.             int widthScale = Math.round((float) bitmapWidth / (float) viewWidth);
  153.             int heightScale = Math.round((float) bitmapHeight / (float) viewWidth);
  154.             //为了保证图片不缩放变形,我们取宽高比例最小的那个
  155.             inSampleSize = widthScale < heightScale ? widthScale : heightScale;
  156.         }
  157.         return inSampleSize;
  158.     }
  159.     /**
  160.      * 加载本地图片的回调接口
  161.      * 
  162.      * @author xiaanming
  163.      *
  164.      */
  165.     public interface NativeImageCallBack{
  166.         /**
  167.          * 当子线程加载完了本地的图片,将Bitmap和图片路径回调在此方法中
  168.          * @param bitmap
  169.          * @param path
  170.          */
  171.         public void onImageLoader(Bitmap bitmap, String path);
  172.     }
  173. }

NativeImageLoader该类是一个单例类,提供了本地图片加载,内存缓存,裁剪等逻辑,该类在加载本地图片的时候采用的是异步加载的方式,对于大图片的加载也是比较耗时的,所以采用子线程的方式去加载,对于图片的缓存机制使用的是LruCache,我们使用手机分配给应用程序内存的1/8用来缓存图片,给图片缓存的内存不宜太大,太大也可能会发生OOM,该类是用我之前写的文章Android 使用ContentProvider扫描手机中的图片,仿微信显示本地图片效果,在这里我就不做过多的介绍,有兴趣的可以去看看那篇文章,不过这里新增了一个方法trimMemCache(),,用来清空LruCache使用的内存

我们看主界面的布局代码,里面只有一个自定义的StickyGridHeadersGridView控件

  1. <?xml version=“1.0” encoding=“utf-8”?>
  2. <com.tonicartos.widget.stickygridheaders.StickyGridHeadersGridView xmlns:android=“http://schemas.android.com/apk/res/android”
  3.     xmlns:tools=“http://schemas.android.com/tools”
  4.     android:id=“@+id/asset_grid”
  5.     android:layout_width=“match_parent”
  6.     android:layout_height=“match_parent”
  7.     android:clipToPadding=“false”
  8.     android:columnWidth=“90dip”
  9.     android:horizontalSpacing=“3dip”
  10.     android:numColumns=“auto_fit”
  11.     android:verticalSpacing=“3dip” />

在看主界面的代码之前我们先看StickyGridAdapter的代码

  1. package com.example.stickyheadergridview;
  2. import java.util.List;
  3. import android.content.Context;
  4. import android.graphics.Bitmap;
  5. import android.graphics.Point;
  6. import android.view.LayoutInflater;
  7. import android.view.View;
  8. import android.view.ViewGroup;
  9. import android.widget.BaseAdapter;
  10. import android.widget.GridView;
  11. import android.widget.ImageView;
  12. import android.widget.TextView;
  13. import com.example.stickyheadergridview.MyImageView.OnMeasureListener;
  14. import com.example.stickyheadergridview.NativeImageLoader.NativeImageCallBack;
  15. import com.tonicartos.widget.stickygridheaders.StickyGridHeadersSimpleAdapter;
  16. /**
  17.  * StickyHeaderGridView的适配器,除了要继承BaseAdapter之外还需要
  18.  * 实现StickyGridHeadersSimpleAdapter接口
  19.  * 
  20.  * @blog http://blog.csdn.net/xiaanming
  21.  * 
  22.  * @author xiaanming
  23.  *
  24.  */
  25. public class StickyGridAdapter extends BaseAdapter implements
  26.         StickyGridHeadersSimpleAdapter {
  27.     private List<GridItem> hasHeaderIdList;
  28.     private LayoutInflater mInflater;
  29.     private GridView mGridView;
  30.     private Point mPoint = new Point(00);//用来封装ImageView的宽和高的对象 
  31.     public StickyGridAdapter(Context context, List<GridItem> hasHeaderIdList,
  32.             GridView mGridView) {
  33.         mInflater = LayoutInflater.from(context);
  34.         this.mGridView = mGridView;
  35.         this.hasHeaderIdList = hasHeaderIdList;
  36.     }
  37.     @Override
  38.     public int getCount() {
  39.         return hasHeaderIdList.size();
  40.     }
  41.     @Override
  42.     public Object getItem(int position) {
  43.         return hasHeaderIdList.get(position);
  44.     }
  45.     @Override
  46.     public long getItemId(int position) {
  47.         return position;
  48.     }
  49.     @Override
  50.     public View getView(int position, View convertView, ViewGroup parent) {
  51.         ViewHolder mViewHolder;
  52.         if (convertView == null) {
  53.             mViewHolder = new ViewHolder();
  54.             convertView = mInflater.inflate(R.layout.grid_item, parent, false);
  55.             mViewHolder.mImageView = (MyImageView) convertView
  56.                     .findViewById(R.id.grid_item);
  57.             convertView.setTag(mViewHolder);
  58.              //用来监听ImageView的宽和高  
  59.             mViewHolder.mImageView.setOnMeasureListener(new OnMeasureListener() {
  60.                 @Override
  61.                 public void onMeasureSize(int width, int height) {
  62.                     mPoint.set(width, height);
  63.                 }
  64.             });
  65.         } else {
  66.             mViewHolder = (ViewHolder) convertView.getTag();
  67.         }
  68.         String path = hasHeaderIdList.get(position).getPath();
  69.         mViewHolder.mImageView.setTag(path);
  70.         Bitmap bitmap = NativeImageLoader.getInstance().loadNativeImage(path, mPoint,
  71.                 new NativeImageCallBack() {
  72.                     @Override
  73.                     public void onImageLoader(Bitmap bitmap, String path) {
  74.                         ImageView mImageView = (ImageView) mGridView
  75.                                 .findViewWithTag(path);
  76.                         if (bitmap != null && mImageView != null) {
  77.                             mImageView.setImageBitmap(bitmap);
  78.                         }
  79.                     }
  80.                 });
  81.         if (bitmap != null) {
  82.             mViewHolder.mImageView.setImageBitmap(bitmap);
  83.         } else {
  84.             mViewHolder.mImageView.setImageResource(R.drawable.friends_sends_pictures_no);
  85.         }
  86.         return convertView;
  87.     }
  88.     @Override
  89.     public View getHeaderView(int position, View convertView, ViewGroup parent) {
  90.         HeaderViewHolder mHeaderHolder;
  91.         if (convertView == null) {
  92.             mHeaderHolder = new HeaderViewHolder();
  93.             convertView = mInflater.inflate(R.layout.header, parent, false);
  94.             mHeaderHolder.mTextView = (TextView) convertView
  95.                     .findViewById(R.id.header);
  96.             convertView.setTag(mHeaderHolder);
  97.         } else {
  98.             mHeaderHolder = (HeaderViewHolder) convertView.getTag();
  99.         }
  100.         mHeaderHolder.mTextView.setText(hasHeaderIdList.get(position).getTime());
  101.         return convertView;
  102.     }
  103.     /**
  104.      * 获取HeaderId, 只要HeaderId不相等就添加一个Header
  105.      */
  106.     @Override
  107.     public long getHeaderId(int position) {
  108.         return hasHeaderIdList.get(position).getHeaderId();
  109.     }
  110.     public static class ViewHolder {
  111.         public MyImageView mImageView;
  112.     }
  113.     public static class HeaderViewHolder {
  114.         public TextView mTextView;
  115.     }
  116. }

除了要继承BaseAdapter之外还需要实现StickyGridHeadersSimpleAdapter接口,继承BaseAdapter需要实现getCount(),getItem(int position), getItemId(int position),getView(int position, View convertView, ViewGroup parent)这四个方法,这几个方法的实现跟我们平常实现的方式一样,主要是看一下getView()方法,我们将每个item的图片路径设置Tag到该ImageView上面,然后利用NativeImageLoader来加载本地图片,在这里使用的ImageView依然是自定义的MyImageView,该自定义ImageView主要实现当MyImageView测量完毕之后,就会将测量的宽和高回调到onMeasureSize()中,然后我们可以根据MyImageView的大小来裁剪图片

另外我们需要实现StickyGridHeadersSimpleAdapter接口的getHeaderId(int position)和getHeaderView(int position, View convertView, ViewGroup parent),getHeaderId(int position)方法返回每个Item的headerId,getHeaderView()方法是生成sections和headers的,如果某个item的headerId跟他下一个item的HeaderId不同,则会调用getHeaderView方法生成一个sections用来区分不同的组,还会根据firstVisibleItem的headerId来生成一个位于顶部的headers,所以如何生成每个Item的headerId才是关键,生成headerId的方法在MainActivity中

  1. package com.example.stickyheadergridview;
  2. import java.text.SimpleDateFormat;
  3. import java.util.ArrayList;
  4. import java.util.Collections;
  5. import java.util.Date;
  6. import java.util.HashMap;
  7. import java.util.List;
  8. import java.util.ListIterator;
  9. import java.util.Map;
  10. import java.util.TimeZone;
  11. import android.app.Activity;
  12. import android.app.ProgressDialog;
  13. import android.database.Cursor;
  14. import android.os.Bundle;
  15. import android.provider.MediaStore;
  16. import android.widget.GridView;
  17. import com.example.stickyheadergridview.ImageScanner.ScanCompleteCallBack;
  18. public class MainActivity extends Activity {
  19.     private ProgressDialog mProgressDialog;
  20.     /**
  21.      * 图片扫描器
  22.      */
  23.     private ImageScanner mScanner;
  24.     private GridView mGridView;
  25.     /**
  26.      * 没有HeaderId的List
  27.      */
  28.     private List<GridItem> nonHeaderIdList = new ArrayList<GridItem>();
  29.     @Override
  30.     protected void onCreate(Bundle savedInstanceState) {
  31.         super.onCreate(savedInstanceState);
  32.         setContentView(R.layout.activity_main);
  33.         mGridView = (GridView) findViewById(R.id.asset_grid);
  34.         mScanner = new ImageScanner(this);
  35.         mScanner.scanImages(new ScanCompleteCallBack() {
  36.             {
  37.                 mProgressDialog = ProgressDialog.show(MainActivity.thisnull“正在加载…”);
  38.             }
  39.             @Override
  40.             public void scanComplete(Cursor cursor) {
  41.                 // 关闭进度条
  42.                 mProgressDialog.dismiss();
  43.                 if(cursor == null){
  44.                     return;
  45.                 }
  46.                 while (cursor.moveToNext()) {
  47.                     // 获取图片的路径
  48.                     String path = cursor.getString(cursor
  49.                             .getColumnIndex(MediaStore.Images.Media.DATA));
  50.                     //获取图片的添加到系统的毫秒数
  51.                     long times = cursor.getLong(cursor
  52.                             .getColumnIndex(MediaStore.Images.Media.DATE_ADDED));
  53.                     GridItem mGridItem = new GridItem(path, paserTimeToYMD(times, “yyyy年MM月dd日”));
  54.                     nonHeaderIdList.add(mGridItem);
  55.                 }
  56.                 cursor.close();
  57.                 //给GridView的item的数据生成HeaderId
  58.                 List<GridItem> hasHeaderIdList = generateHeaderId(nonHeaderIdList);
  59.                 //排序
  60.                 Collections.sort(hasHeaderIdList, new YMDComparator());
  61.                 mGridView.setAdapter(new StickyGridAdapter(MainActivity.this, hasHeaderIdList, mGridView));
  62.             }
  63.         });
  64.     }
  65.     /**
  66.      * 对GridView的Item生成HeaderId, 根据图片的添加时间的年、月、日来生成HeaderId
  67.      * 年、月、日相等HeaderId就相同
  68.      * @param nonHeaderIdList
  69.      * @return
  70.      */
  71.     private List<GridItem> generateHeaderId(List<GridItem> nonHeaderIdList) {
  72.         Map<String, Integer> mHeaderIdMap = new HashMap<String, Integer>();
  73.         int mHeaderId = 1;
  74.         List<GridItem> hasHeaderIdList;
  75.         for(ListIterator<GridItem> it = nonHeaderIdList.listIterator(); it.hasNext();){
  76.             GridItem mGridItem = it.next();
  77.             String ymd = mGridItem.getTime();
  78.             if(!mHeaderIdMap.containsKey(ymd)){
  79.                 mGridItem.setHeaderId(mHeaderId);
  80.                 mHeaderIdMap.put(ymd, mHeaderId);
  81.                 mHeaderId ++;
  82.             }else{
  83.                 mGridItem.setHeaderId(mHeaderIdMap.get(ymd));
  84.             }
  85.         }
  86.         hasHeaderIdList = nonHeaderIdList;
  87.         return hasHeaderIdList;
  88.     }
  89.     @Override
  90.     protected void onDestroy() {
  91.         super.onDestroy();
  92.         //退出页面清除LRUCache中的Bitmap占用的内存
  93.         NativeImageLoader.getInstance().trimMemCache();
  94.     }
  95.     /**
  96.      * 将毫秒数装换成pattern这个格式,我这里是转换成年月日
  97.      * @param time
  98.      * @param pattern
  99.      * @return
  100.      */
  101.     public static String paserTimeToYMD(long time, String pattern ) {
  102.         System.setProperty(“user.timezone”“Asia/Shanghai”);
  103.         TimeZone tz = TimeZone.getTimeZone(“Asia/Shanghai”);
  104.         TimeZone.setDefault(tz);
  105.         SimpleDateFormat format = new SimpleDateFormat(pattern);
  106.         return format.format(new Date(time * 1000L));
  107.     }
  108. }

主界面的代码主要是组装StickyGridHeadersGridView的数据,我们将扫描出来的图片的路径,时间的毫秒数解析成年月日的格式封装到GridItem中,然后将GridItem加入到List中,此时每个Item还没有生成headerId,我们需要调用generateHeaderId(),该方法主要是将同一天加入的系统的图片生成相同的HeaderId,这样子同一天加入的图片就在一个组中,当然你要改成同一个月的图片在一起,修改paserTimeToYMD()方法的第二个参数就行了,当Activity finish之后,我们利用NativeImageLoader.getInstance().trimMemCache()释放内存,当然我们还需要对GridView的数据进行排序,比如说headerId相同的item不连续,headerId相同的item就会生成多个sections(即多个分组),所以我们要利用YMDComparator使得在同一天加入的图片在一起,YMDComparator的代码如下

  1. package com.example.stickyheadergridview;
  2. import java.util.Comparator;
  3. public class YMDComparator implements Comparator<GridItem> {
  4.     @Override
  5.     public int compare(GridItem o1, GridItem o2) {
  6.         return o1.getTime().compareTo(o2.getTime());
  7.     }
  8. }

当然这篇文章不使用YMDComparator也是可以的,因为我在利用ContentProvider获取图片的时候,就是根据加入系统的时间排序的,排序只是针对一般的数据来说的。

接下来我们运行下程序看看效果如何

今天的文章就到这里结束了,感谢大家的观看,上面还有一个类和一些资源文件没有贴出来,大家有兴趣研究下就直接下载项目源码,记住采用LruCache缓存图片的时候,cacheSize不要设置得过大,不然产生OOM的概率就更大些,我利用上面的程序测试显示600多张图片来回滑动,没有产生OOM,有问题不明白的同学可以在下面留言!

项目源码,点击下载

转自:http://blog.csdn.net/xiaanming/article/details/20481185

使用到的github library地址:https://github.com/emilsjolander/StickyListHeaders

天边的星星