转载(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

**[java]** [view plain](http://blog.csdn.net/xiaanming/article/details/20481185#)[copy](http://blog.csdn.net/xiaanming/article/details/20481185#)[![在CODE上查看代码片](https://code.csdn.net/assets/CODE_ico.png)](https://code.csdn.net/snippets/220204)[![派生到我的代码片](https://code.csdn.net/assets/ico_fork.svg)](https://code.csdn.net/snippets/220204/fork)
  <div>
    <embed id="ZeroClipboardMovie_1" src="http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf" type="application/x-shockwave-flash" width="18" height="18" align="middle" name="ZeroClipboardMovie_1">
    </embed>
  </div>
</div>
- <span class="keyword">package</span> com.example.stickyheadergridview;

- <span class="comment">/**</span>

- <span class="comment"> * @blog http://blog.csdn.net/xiaanming</span>

- <span class="comment"> * </span>

- <span class="comment"> * @author xiaanming</span>

- <span class="comment"> *</span>

- <span class="comment"> */</span>

- <span class="keyword">public</span> <span class="keyword">class</span> GridItem {

- <span class="comment">/**</span>

- <span class="comment">     * 图片的路径</span>

- <span class="comment">     */</span>

- <span class="keyword">private</span> String path;

- <span class="comment">/**</span>

- <span class="comment">     * 图片加入手机中的时间,只取了年月日</span>

- <span class="comment">     */</span>

- <span class="keyword">private</span> String time;

- <span class="comment">/**</span>

- <span class="comment">     * 每个Item对应的HeaderId</span>

- <span class="comment">     */</span>

- <span class="keyword">private</span> <span class="keyword">int</span> headerId;

- 
- <span class="keyword">public</span> GridItem(String path, String time) {

- <span class="keyword">super</span>();

- <span class="keyword">this</span>.path = path;

- <span class="keyword">this</span>.time = time;

- }

- 
- <span class="keyword">public</span> String getPath() {

- <span class="keyword">return</span> path;

- }

- <span class="keyword">public</span> <span class="keyword">void</span> setPath(String path) {

- <span class="keyword">this</span>.path = path;

- }

- <span class="keyword">public</span> String getTime() {

- <span class="keyword">return</span> time;

- }

- <span class="keyword">public</span> <span class="keyword">void</span> setTime(String time) {

- <span class="keyword">this</span>.time = time;

- }

- 
- <span class="keyword">public</span> <span class="keyword">int</span> getHeaderId() {

- <span class="keyword">return</span> headerId;

- }

- 
- <span class="keyword">public</span> <span class="keyword">void</span> setHeaderId(<span class="keyword">int</span> headerId) {

- <span class="keyword">this</span>.headerId = headerId;

- }

- 
- 
- }

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

**[java]** [view plain](http://blog.csdn.net/xiaanming/article/details/20481185#)[copy](http://blog.csdn.net/xiaanming/article/details/20481185#)[![在CODE上查看代码片](https://code.csdn.net/assets/CODE_ico.png)](https://code.csdn.net/snippets/220204)[![派生到我的代码片](https://code.csdn.net/assets/ico_fork.svg)](https://code.csdn.net/snippets/220204/fork)
  <div>
    <embed id="ZeroClipboardMovie_2" src="http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf" type="application/x-shockwave-flash" width="18" height="18" align="middle" name="ZeroClipboardMovie_2">
    </embed>
  </div>
</div>
- <span class="keyword">package</span> com.example.stickyheadergridview;

- 
- <span class="keyword">import</span> android.content.ContentResolver;

- <span class="keyword">import</span> android.content.Context;

- <span class="keyword">import</span> android.content.Intent;

- <span class="keyword">import</span> android.database.Cursor;

- <span class="keyword">import</span> android.net.Uri;

- <span class="keyword">import</span> android.os.Environment;

- <span class="keyword">import</span> android.os.Handler;

- <span class="keyword">import</span> android.os.Message;

- <span class="keyword">import</span> android.provider.MediaStore;

- <span class="comment">/**</span>

- <span class="comment"> * 图片扫描器</span>

- <span class="comment"> * </span>

- <span class="comment"> * @author xiaanming</span>

- <span class="comment"> *</span>

- <span class="comment"> */</span>

- <span class="keyword">public</span> <span class="keyword">class</span> ImageScanner {

- <span class="keyword">private</span> Context mContext;

- 
- <span class="keyword">public</span> ImageScanner(Context context){

- <span class="keyword">this</span>.mContext = context;

- }

- 
- <span class="comment">/**</span>

- <span class="comment">     * 利用ContentProvider扫描手机中的图片,将扫描的Cursor回调到ScanCompleteCallBack</span>

- <span class="comment">     * 接口的scanComplete方法中,此方法在运行在子线程中</span>

- <span class="comment">     */</span>

- <span class="keyword">public</span> <span class="keyword">void</span> scanImages(<span class="keyword">final</span> ScanCompleteCallBack callback) {

- <span class="keyword">final</span> Handler mHandler = <span class="keyword">new</span> Handler() {

- 
- <span class="annotation">@Override</span>

- <span class="keyword">public</span> <span class="keyword">void</span> handleMessage(Message msg) {

- <span class="keyword">super</span>.handleMessage(msg);

- callback.scanComplete((Cursor)msg.obj);

- }

- };

- 
- <span class="keyword">new</span> Thread(<span class="keyword">new</span> Runnable() {

- 
- <span class="annotation">@Override</span>

- <span class="keyword">public</span> <span class="keyword">void</span> run() {

- <span class="comment">//先发送广播扫描下整个sd卡</span>

- mContext.sendBroadcast(<span class="keyword">new</span> Intent(

- Intent.ACTION_MEDIA_MOUNTED,

- Uri.parse(<span class="string">&#8220;file://&#8221;</span> + Environment.getExternalStorageDirectory())));

- 
- Uri mImageUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;

- ContentResolver mContentResolver = mContext.getContentResolver();

- 
- Cursor mCursor = mContentResolver.query(mImageUri, <span class="keyword">null</span>, <span class="keyword">null</span>, <span class="keyword">null</span>, MediaStore.Images.Media.DATE_ADDED);

- 
- <span class="comment">//利用Handler通知调用线程</span>

- Message msg = mHandler.obtainMessage();

- msg.obj = mCursor;

- mHandler.sendMessage(msg);

- }

- }).start();

- 
- }

- 
- <span class="comment">/**</span>

- <span class="comment">     * 扫描完成之后的回调接口</span>

- <span class="comment">     *</span>

- <span class="comment">     */</span>

- <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">interface</span> ScanCompleteCallBack{

- <span class="keyword">public</span> <span class="keyword">void</span> scanComplete(Cursor cursor);

- }

- 
- 
- }

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

**[java]** [view plain](http://blog.csdn.net/xiaanming/article/details/20481185#)[copy](http://blog.csdn.net/xiaanming/article/details/20481185#)[![在CODE上查看代码片](https://code.csdn.net/assets/CODE_ico.png)](https://code.csdn.net/snippets/220204)[![派生到我的代码片](https://code.csdn.net/assets/ico_fork.svg)](https://code.csdn.net/snippets/220204/fork)
  <div>
    <embed id="ZeroClipboardMovie_3" src="http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf" type="application/x-shockwave-flash" width="18" height="18" align="middle" name="ZeroClipboardMovie_3">
    </embed>
  </div>
</div>
- <span class="keyword">package</span> com.example.stickyheadergridview;

- <span class="keyword">import</span> java.util.concurrent.ExecutorService;

- <span class="keyword">import</span> java.util.concurrent.Executors;

- 
- <span class="keyword">import</span> android.graphics.Bitmap;

- <span class="keyword">import</span> android.graphics.BitmapFactory;

- <span class="keyword">import</span> android.graphics.Point;

- <span class="keyword">import</span> android.os.Handler;

- <span class="keyword">import</span> android.os.Message;

- <span class="keyword">import</span> android.support.v4.util.LruCache;

- <span class="keyword">import</span> android.util.Log;

- 
- <span class="comment">/**</span>

- <span class="comment"> * 本地图片加载器,采用的是异步解析本地图片,单例模式利用getInstance()获取NativeImageLoader实例</span>

- <span class="comment"> * 调用loadNativeImage()方法加载本地图片,此类可作为一个加载本地图片的工具类</span>

- <span class="comment"> * </span>

- <span class="comment"> * @blog http://blog.csdn.net/xiaanming</span>

- <span class="comment"> * </span>

- <span class="comment"> * @author xiaanming</span>

- <span class="comment"> *</span>

- <span class="comment"> */</span>

- <span class="keyword">public</span> <span class="keyword">class</span> NativeImageLoader {

- <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String TAG = NativeImageLoader.<span class="keyword">class</span>.getSimpleName();

- <span class="keyword">private</span> <span class="keyword">static</span> NativeImageLoader mInstance = <span class="keyword">new</span> NativeImageLoader();

- <span class="keyword">private</span> <span class="keyword">static</span> LruCache<String, Bitmap> mMemoryCache;

- <span class="keyword">private</span> ExecutorService mImageThreadPool = Executors.newFixedThreadPool(<span class="number">1</span>);

- 
- 
- <span class="keyword">private</span> NativeImageLoader(){

- <span class="comment">//获取应用程序的最大内存</span>

- <span class="keyword">final</span> <span class="keyword">int</span> maxMemory = (<span class="keyword">int</span>) (Runtime.getRuntime().maxMemory());

- 
- <span class="comment">//用最大内存的1/8来存储图片</span>

- <span class="keyword">final</span> <span class="keyword">int</span> cacheSize = maxMemory / <span class="number">8</span>;

- mMemoryCache = <span class="keyword">new</span> LruCache<String, Bitmap>(cacheSize) {

- 
- <span class="comment">//获取每张图片的bytes</span>

- <span class="annotation">@Override</span>

- <span class="keyword">protected</span> <span class="keyword">int</span> sizeOf(String key, Bitmap bitmap) {

- <span class="keyword">return</span> bitmap.getRowBytes() * bitmap.getHeight();

- }

- 
- };

- }

- 
- <span class="comment">/**</span>

- <span class="comment">     * 通过此方法来获取NativeImageLoader的实例</span>

- <span class="comment">     * @return</span>

- <span class="comment">     */</span>

- <span class="keyword">public</span> <span class="keyword">static</span> NativeImageLoader getInstance(){

- <span class="keyword">return</span> mInstance;

- }

- 
- 
- <span class="comment">/**</span>

- <span class="comment">     * 加载本地图片,对图片不进行裁剪</span>

- <span class="comment">     * @param path</span>

- <span class="comment">     * @param mCallBack</span>

- <span class="comment">     * @return</span>

- <span class="comment">     */</span>

- <span class="keyword">public</span> Bitmap loadNativeImage(<span class="keyword">final</span> String path, <span class="keyword">final</span> NativeImageCallBack mCallBack){

- <span class="keyword">return</span> <span class="keyword">this</span>.loadNativeImage(path, <span class="keyword">null</span>, mCallBack);

- }

- 
- <span class="comment">/**</span>

- <span class="comment">     * 此方法来加载本地图片,这里的mPoint是用来封装ImageView的宽和高,我们会根据ImageView控件的大小来裁剪Bitmap</span>

- <span class="comment">     * 如果你不想裁剪图片,调用loadNativeImage(final String path, final NativeImageCallBack mCallBack)来加载</span>

- <span class="comment">     * @param path</span>

- <span class="comment">     * @param mPoint</span>

- <span class="comment">     * @param mCallBack</span>

- <span class="comment">     * @return</span>

- <span class="comment">     */</span>

- <span class="keyword">public</span> Bitmap loadNativeImage(<span class="keyword">final</span> String path, <span class="keyword">final</span> Point mPoint, <span class="keyword">final</span> NativeImageCallBack mCallBack){

- <span class="comment">//先获取内存中的Bitmap</span>

- Bitmap bitmap = getBitmapFromMemCache(path);

- 
- <span class="keyword">final</span> Handler mHander = <span class="keyword">new</span> Handler(){

- 
- <span class="annotation">@Override</span>

- <span class="keyword">public</span> <span class="keyword">void</span> handleMessage(Message msg) {

- <span class="keyword">super</span>.handleMessage(msg);

- mCallBack.onImageLoader((Bitmap)msg.obj, path);

- }

- 
- };

- 
- <span class="comment">//若该Bitmap不在内存缓存中,则启用线程去加载本地的图片,并将Bitmap加入到mMemoryCache中</span>

- <span class="keyword">if</span>(bitmap == <span class="keyword">null</span>){

- mImageThreadPool.execute(<span class="keyword">new</span> Runnable() {

- 
- <span class="annotation">@Override</span>

- <span class="keyword">public</span> <span class="keyword">void</span> run() {

- <span class="comment">//先获取图片的缩略图</span>

- Bitmap mBitmap = decodeThumbBitmapForFile(path, mPoint == <span class="keyword">null</span> ? <span class="number"></span>: mPoint.x, mPoint == <span class="keyword">null</span> ? <span class="number"></span>: mPoint.y);

- Message msg = mHander.obtainMessage();

- msg.obj = mBitmap;

- mHander.sendMessage(msg);

- 
- <span class="comment">//将图片加入到内存缓存</span>

- addBitmapToMemoryCache(path, mBitmap);

- }

- });

- }

- <span class="keyword">return</span> bitmap;

- 
- }

- 
- 
- 
- <span class="comment">/**</span>

- <span class="comment">     * 往内存缓存中添加Bitmap</span>

- <span class="comment">     * </span>

- <span class="comment">     * @param key</span>

- <span class="comment">     * @param bitmap</span>

- <span class="comment">     */</span>

- <span class="keyword">private</span> <span class="keyword">void</span> addBitmapToMemoryCache(String key, Bitmap bitmap) {

- <span class="keyword">if</span> (getBitmapFromMemCache(key) == <span class="keyword">null</span> && bitmap != <span class="keyword">null</span>) {

- mMemoryCache.put(key, bitmap);

- }

- }

- 
- <span class="comment">/**</span>

- <span class="comment">     * 根据key来获取内存中的图片</span>

- <span class="comment">     * @param key</span>

- <span class="comment">     * @return</span>

- <span class="comment">     */</span>

- <span class="keyword">private</span> Bitmap getBitmapFromMemCache(String key) {

- Bitmap bitmap = mMemoryCache.get(key);

- 
- <span class="keyword">if</span>(bitmap != <span class="keyword">null</span>){

- Log.i(TAG, <span class="string">&#8220;get image for LRUCache , path = &#8220;</span> + key);

- }

- <span class="keyword">return</span> bitmap;

- }

- 
- <span class="comment">/**</span>

- <span class="comment">     * 清除LruCache中的bitmap</span>

- <span class="comment">     */</span>

- <span class="keyword">public</span> <span class="keyword">void</span> trimMemCache(){

- mMemoryCache.evictAll();

- }

- 
- 
- <span class="comment">/**</span>

- <span class="comment">     * 根据View(主要是ImageView)的宽和高来获取图片的缩略图</span>

- <span class="comment">     * @param path</span>

- <span class="comment">     * @param viewWidth</span>

- <span class="comment">     * @param viewHeight</span>

- <span class="comment">     * @return</span>

- <span class="comment">     */</span>

- <span class="keyword">private</span> Bitmap decodeThumbBitmapForFile(String path, <span class="keyword">int</span> viewWidth, <span class="keyword">int</span> viewHeight){

- BitmapFactory.Options options = <span class="keyword">new</span> BitmapFactory.Options();

- <span class="comment">//设置为true,表示解析Bitmap对象,该对象不占内存</span>

- options.inJustDecodeBounds = <span class="keyword">true</span>;

- BitmapFactory.decodeFile(path, options);

- <span class="comment">//设置缩放比例</span>

- options.inSampleSize = computeScale(options, viewWidth, viewHeight);

- 
- <span class="comment">//设置为false,解析Bitmap对象加入到内存中</span>

- options.inJustDecodeBounds = <span class="keyword">false</span>;

- 
- 
- Log.e(TAG, <span class="string">&#8220;get Iamge form file,  path = &#8220;</span> + path);

- 
- <span class="keyword">return</span> BitmapFactory.decodeFile(path, options);

- }

- 
- 
- <span class="comment">/**</span>

- <span class="comment">     * 根据View(主要是ImageView)的宽和高来计算Bitmap缩放比例。默认不缩放</span>

- <span class="comment">     * @param options</span>

- <span class="comment">     * @param width</span>

- <span class="comment">     * @param height</span>

- <span class="comment">     */</span>

- <span class="keyword">private</span> <span class="keyword">int</span> computeScale(BitmapFactory.Options options, <span class="keyword">int</span> viewWidth, <span class="keyword">int</span> viewHeight){

- <span class="keyword">int</span> inSampleSize = <span class="number">1</span>;

- <span class="keyword">if</span>(viewWidth == <span class="number"></span> || viewWidth == <span class="number"></span>){

- <span class="keyword">return</span> inSampleSize;

- }

- <span class="keyword">int</span> bitmapWidth = options.outWidth;

- <span class="keyword">int</span> bitmapHeight = options.outHeight;

- 
- <span class="comment">//假如Bitmap的宽度或高度大于我们设定图片的View的宽高,则计算缩放比例</span>

- <span class="keyword">if</span>(bitmapWidth > viewWidth || bitmapHeight > viewWidth){

- <span class="keyword">int</span> widthScale = Math.round((<span class="keyword">float</span>) bitmapWidth / (<span class="keyword">float</span>) viewWidth);

- <span class="keyword">int</span> heightScale = Math.round((<span class="keyword">float</span>) bitmapHeight / (<span class="keyword">float</span>) viewWidth);

- 
- <span class="comment">//为了保证图片不缩放变形,我们取宽高比例最小的那个</span>

- inSampleSize = widthScale < heightScale ? widthScale : heightScale;

- }

- <span class="keyword">return</span> inSampleSize;

- }

- 
- 
- <span class="comment">/**</span>

- <span class="comment">     * 加载本地图片的回调接口</span>

- <span class="comment">     * </span>

- <span class="comment">     * @author xiaanming</span>

- <span class="comment">     *</span>

- <span class="comment">     */</span>

- <span class="keyword">public</span> <span class="keyword">interface</span> NativeImageCallBack{

- <span class="comment">/**</span>

- <span class="comment">         * 当子线程加载完了本地的图片,将Bitmap和图片路径回调在此方法中</span>

- <span class="comment">         * @param bitmap</span>

- <span class="comment">         * @param path</span>

- <span class="comment">         */</span>

- <span class="keyword">public</span> <span class="keyword">void</span> onImageLoader(Bitmap bitmap, String path);

- }

- }

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

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

**[html]** [view plain](http://blog.csdn.net/xiaanming/article/details/20481185#)[copy](http://blog.csdn.net/xiaanming/article/details/20481185#)[![在CODE上查看代码片](https://code.csdn.net/assets/CODE_ico.png)](https://code.csdn.net/snippets/220204)[![派生到我的代码片](https://code.csdn.net/assets/ico_fork.svg)](https://code.csdn.net/snippets/220204/fork)
  <div>
    <embed id="ZeroClipboardMovie_4" src="http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf" type="application/x-shockwave-flash" width="18" height="18" align="middle" name="ZeroClipboardMovie_4">
    </embed>
  </div>
</div>
- <span class="tag"><?</span><span class="tag-name">xml</span> <span class="attribute">version</span>=<span class="attribute-value">&#8220;1.0&#8221;</span> <span class="attribute">encoding</span>=<span class="attribute-value">&#8220;utf-8&#8221;</span><span class="tag">?></span>

- <span class="tag"><</span><span class="tag-name">com.tonicartos.widget.stickygridheaders.StickyGridHeadersGridView</span> <span class="attribute">xmlns:android</span>=<span class="attribute-value">&#8220;http://schemas.android.com/apk/res/android&#8221;</span>

- <span class="attribute">xmlns:tools</span>=<span class="attribute-value">&#8220;http://schemas.android.com/tools&#8221;</span>

- <span class="attribute">android:id</span>=<span class="attribute-value">&#8220;@+id/asset_grid&#8221;</span>

- <span class="attribute">android:layout_width</span>=<span class="attribute-value">&#8220;match_parent&#8221;</span>

- <span class="attribute">android:layout_height</span>=<span class="attribute-value">&#8220;match_parent&#8221;</span>

- <span class="attribute">android:clipToPadding</span>=<span class="attribute-value">&#8220;false&#8221;</span>

- <span class="attribute">android:columnWidth</span>=<span class="attribute-value">&#8220;90dip&#8221;</span>

- <span class="attribute">android:horizontalSpacing</span>=<span class="attribute-value">&#8220;3dip&#8221;</span>

- <span class="attribute">android:numColumns</span>=<span class="attribute-value">&#8220;auto_fit&#8221;</span>

- <span class="attribute">android:verticalSpacing</span>=<span class="attribute-value">&#8220;3dip&#8221;</span> <span class="tag">/></span>

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

**[java]** [view plain](http://blog.csdn.net/xiaanming/article/details/20481185#)[copy](http://blog.csdn.net/xiaanming/article/details/20481185#)[![在CODE上查看代码片](https://code.csdn.net/assets/CODE_ico.png)](https://code.csdn.net/snippets/220204)[![派生到我的代码片](https://code.csdn.net/assets/ico_fork.svg)](https://code.csdn.net/snippets/220204/fork)
  <div>
    <embed id="ZeroClipboardMovie_5" src="http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf" type="application/x-shockwave-flash" width="18" height="18" align="middle" name="ZeroClipboardMovie_5">
    </embed>
  </div>
</div>
- <span class="keyword">package</span> com.example.stickyheadergridview;

- 
- <span class="keyword">import</span> java.util.List;

- 
- <span class="keyword">import</span> android.content.Context;

- <span class="keyword">import</span> android.graphics.Bitmap;

- <span class="keyword">import</span> android.graphics.Point;

- <span class="keyword">import</span> android.view.LayoutInflater;

- <span class="keyword">import</span> android.view.View;

- <span class="keyword">import</span> android.view.ViewGroup;

- <span class="keyword">import</span> android.widget.BaseAdapter;

- <span class="keyword">import</span> android.widget.GridView;

- <span class="keyword">import</span> android.widget.ImageView;

- <span class="keyword">import</span> android.widget.TextView;

- 
- <span class="keyword">import</span> com.example.stickyheadergridview.MyImageView.OnMeasureListener;

- <span class="keyword">import</span> com.example.stickyheadergridview.NativeImageLoader.NativeImageCallBack;

- <span class="keyword">import</span> com.tonicartos.widget.stickygridheaders.StickyGridHeadersSimpleAdapter;

- <span class="comment">/**</span>

- <span class="comment"> * StickyHeaderGridView的适配器,除了要继承BaseAdapter之外还需要</span>

- <span class="comment"> * 实现StickyGridHeadersSimpleAdapter接口</span>

- <span class="comment"> * </span>

- <span class="comment"> * @blog http://blog.csdn.net/xiaanming</span>

- <span class="comment"> * </span>

- <span class="comment"> * @author xiaanming</span>

- <span class="comment"> *</span>

- <span class="comment"> */</span>

- <span class="keyword">public</span> <span class="keyword">class</span> StickyGridAdapter <span class="keyword">extends</span> BaseAdapter <span class="keyword">implements</span>

- StickyGridHeadersSimpleAdapter {

- 
- <span class="keyword">private</span> List<GridItem> hasHeaderIdList;

- <span class="keyword">private</span> LayoutInflater mInflater;

- <span class="keyword">private</span> GridView mGridView;

- <span class="keyword">private</span> Point mPoint = <span class="keyword">new</span> Point(<span class="number"></span>, <span class="number"></span>);<span class="comment">//用来封装ImageView的宽和高的对象 </span>

- 
- <span class="keyword">public</span> StickyGridAdapter(Context context, List<GridItem> hasHeaderIdList,

- GridView mGridView) {

- mInflater = LayoutInflater.from(context);

- <span class="keyword">this</span>.mGridView = mGridView;

- <span class="keyword">this</span>.hasHeaderIdList = hasHeaderIdList;

- }

- 
- 
- <span class="annotation">@Override</span>

- <span class="keyword">public</span> <span class="keyword">int</span> getCount() {

- <span class="keyword">return</span> hasHeaderIdList.size();

- }

- 
- <span class="annotation">@Override</span>

- <span class="keyword">public</span> Object getItem(<span class="keyword">int</span> position) {

- <span class="keyword">return</span> hasHeaderIdList.get(position);

- }

- 
- <span class="annotation">@Override</span>

- <span class="keyword">public</span> <span class="keyword">long</span> getItemId(<span class="keyword">int</span> position) {

- <span class="keyword">return</span> position;

- }

- 
- <span class="annotation">@Override</span>

- <span class="keyword">public</span> View getView(<span class="keyword">int</span> position, View convertView, ViewGroup parent) {

- ViewHolder mViewHolder;

- <span class="keyword">if</span> (convertView == <span class="keyword">null</span>) {

- mViewHolder = <span class="keyword">new</span> ViewHolder();

- convertView = mInflater.inflate(R.layout.grid_item, parent, <span class="keyword">false</span>);

- mViewHolder.mImageView = (MyImageView) convertView

- .findViewById(R.id.grid_item);

- convertView.setTag(mViewHolder);

- 
- <span class="comment">//用来监听ImageView的宽和高  </span>

- mViewHolder.mImageView.setOnMeasureListener(<span class="keyword">new</span> OnMeasureListener() {

- 
- <span class="annotation">@Override</span>

- <span class="keyword">public</span> <span class="keyword">void</span> onMeasureSize(<span class="keyword">int</span> width, <span class="keyword">int</span> height) {

- mPoint.set(width, height);

- }

- });

- 
- } <span class="keyword">else</span> {

- mViewHolder = (ViewHolder) convertView.getTag();

- }

- 
- String path = hasHeaderIdList.get(position).getPath();

- mViewHolder.mImageView.setTag(path);

- 
- Bitmap bitmap = NativeImageLoader.getInstance().loadNativeImage(path, mPoint,

- <span class="keyword">new</span> NativeImageCallBack() {

- 
- <span class="annotation">@Override</span>

- <span class="keyword">public</span> <span class="keyword">void</span> onImageLoader(Bitmap bitmap, String path) {

- ImageView mImageView = (ImageView) mGridView

- .findViewWithTag(path);

- <span class="keyword">if</span> (bitmap != <span class="keyword">null</span> && mImageView != <span class="keyword">null</span>) {

- mImageView.setImageBitmap(bitmap);

- }

- }

- });

- 
- <span class="keyword">if</span> (bitmap != <span class="keyword">null</span>) {

- mViewHolder.mImageView.setImageBitmap(bitmap);

- } <span class="keyword">else</span> {

- mViewHolder.mImageView.setImageResource(R.drawable.friends_sends_pictures_no);

- }

- 
- <span class="keyword">return</span> convertView;

- }

- 
- 
- <span class="annotation">@Override</span>

- <span class="keyword">public</span> View getHeaderView(<span class="keyword">int</span> position, View convertView, ViewGroup parent) {

- HeaderViewHolder mHeaderHolder;

- 
- <span class="keyword">if</span> (convertView == <span class="keyword">null</span>) {

- mHeaderHolder = <span class="keyword">new</span> HeaderViewHolder();

- convertView = mInflater.inflate(R.layout.header, parent, <span class="keyword">false</span>);

- mHeaderHolder.mTextView = (TextView) convertView

- .findViewById(R.id.header);

- convertView.setTag(mHeaderHolder);

- } <span class="keyword">else</span> {

- mHeaderHolder = (HeaderViewHolder) convertView.getTag();

- }

- mHeaderHolder.mTextView.setText(hasHeaderIdList.get(position).getTime());

- 
- <span class="keyword">return</span> convertView;

- }

- 
- <span class="comment">/**</span>

- <span class="comment">     * 获取HeaderId, 只要HeaderId不相等就添加一个Header</span>

- <span class="comment">     */</span>

- <span class="annotation">@Override</span>

- <span class="keyword">public</span> <span class="keyword">long</span> getHeaderId(<span class="keyword">int</span> position) {

- <span class="keyword">return</span> hasHeaderIdList.get(position).getHeaderId();

- }

- 
- 
- <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">class</span> ViewHolder {

- <span class="keyword">public</span> MyImageView mImageView;

- }

- 
- <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">class</span> HeaderViewHolder {

- <span class="keyword">public</span> TextView mTextView;

- }

- 
- 
- 
- }

除了要继承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中

**[java]** [view plain](http://blog.csdn.net/xiaanming/article/details/20481185#)[copy](http://blog.csdn.net/xiaanming/article/details/20481185#)[![在CODE上查看代码片](https://code.csdn.net/assets/CODE_ico.png)](https://code.csdn.net/snippets/220204)[![派生到我的代码片](https://code.csdn.net/assets/ico_fork.svg)](https://code.csdn.net/snippets/220204/fork)
  <div>
    <embed id="ZeroClipboardMovie_6" src="http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf" type="application/x-shockwave-flash" width="18" height="18" align="middle" name="ZeroClipboardMovie_6">
    </embed>
  </div>
</div>
- <span class="keyword">package</span> com.example.stickyheadergridview;

- 
- <span class="keyword">import</span> java.text.SimpleDateFormat;

- <span class="keyword">import</span> java.util.ArrayList;

- <span class="keyword">import</span> java.util.Collections;

- <span class="keyword">import</span> java.util.Date;

- <span class="keyword">import</span> java.util.HashMap;

- <span class="keyword">import</span> java.util.List;

- <span class="keyword">import</span> java.util.ListIterator;

- <span class="keyword">import</span> java.util.Map;

- <span class="keyword">import</span> java.util.TimeZone;

- 
- <span class="keyword">import</span> android.app.Activity;

- <span class="keyword">import</span> android.app.ProgressDialog;

- <span class="keyword">import</span> android.database.Cursor;

- <span class="keyword">import</span> android.os.Bundle;

- <span class="keyword">import</span> android.provider.MediaStore;

- <span class="keyword">import</span> android.widget.GridView;

- 
- <span class="keyword">import</span> com.example.stickyheadergridview.ImageScanner.ScanCompleteCallBack;

- 
- <span class="keyword">public</span> <span class="keyword">class</span> MainActivity <span class="keyword">extends</span> Activity {

- <span class="keyword">private</span> ProgressDialog mProgressDialog;

- <span class="comment">/**</span>

- <span class="comment">     * 图片扫描器</span>

- <span class="comment">     */</span>

- <span class="keyword">private</span> ImageScanner mScanner;

- <span class="keyword">private</span> GridView mGridView;

- <span class="comment">/**</span>

- <span class="comment">     * 没有HeaderId的List</span>

- <span class="comment">     */</span>

- <span class="keyword">private</span> List<GridItem> nonHeaderIdList = <span class="keyword">new</span> ArrayList<GridItem>();

- 
- 
- <span class="annotation">@Override</span>

- <span class="keyword">protected</span> <span class="keyword">void</span> onCreate(Bundle savedInstanceState) {

- <span class="keyword">super</span>.onCreate(savedInstanceState);

- setContentView(R.layout.activity_main);

- 
- mGridView = (GridView) findViewById(R.id.asset_grid);

- mScanner = <span class="keyword">new</span> ImageScanner(<span class="keyword">this</span>);

- 
- mScanner.scanImages(<span class="keyword">new</span> ScanCompleteCallBack() {

- {

- mProgressDialog = ProgressDialog.show(MainActivity.<span class="keyword">this</span>, <span class="keyword">null</span>, <span class="string">&#8220;正在加载&#8230;&#8221;</span>);

- }

- 
- <span class="annotation">@Override</span>

- <span class="keyword">public</span> <span class="keyword">void</span> scanComplete(Cursor cursor) {

- <span class="comment">// 关闭进度条</span>

- mProgressDialog.dismiss();

- 
- <span class="keyword">if</span>(cursor == <span class="keyword">null</span>){

- <span class="keyword">return</span>;

- }

- 
- <span class="keyword">while</span> (cursor.moveToNext()) {

- <span class="comment">// 获取图片的路径</span>

- String path = cursor.getString(cursor

- .getColumnIndex(MediaStore.Images.Media.DATA));

- <span class="comment">//获取图片的添加到系统的毫秒数</span>

- <span class="keyword">long</span> times = cursor.getLong(cursor

- .getColumnIndex(MediaStore.Images.Media.DATE_ADDED));

- 
- GridItem mGridItem = <span class="keyword">new</span> GridItem(path, paserTimeToYMD(times, <span class="string">&#8220;yyyy年MM月dd日&#8221;</span>));

- nonHeaderIdList.add(mGridItem);

- 
- }

- cursor.close();

- 
- <span class="comment">//给GridView的item的数据生成HeaderId</span>

- List<GridItem> hasHeaderIdList = generateHeaderId(nonHeaderIdList);

- <span class="comment">//排序</span>

- Collections.sort(hasHeaderIdList, <span class="keyword">new</span> YMDComparator());

- mGridView.setAdapter(<span class="keyword">new</span> StickyGridAdapter(MainActivity.<span class="keyword">this</span>, hasHeaderIdList, mGridView));

- 
- }

- });

- }

- 
- 
- <span class="comment">/**</span>

- <span class="comment">     * 对GridView的Item生成HeaderId, 根据图片的添加时间的年、月、日来生成HeaderId</span>

- <span class="comment">     * 年、月、日相等HeaderId就相同</span>

- <span class="comment">     * @param nonHeaderIdList</span>

- <span class="comment">     * @return</span>

- <span class="comment">     */</span>

- <span class="keyword">private</span> List<GridItem> generateHeaderId(List<GridItem> nonHeaderIdList) {

- Map<String, Integer> mHeaderIdMap = <span class="keyword">new</span> HashMap<String, Integer>();

- <span class="keyword">int</span> mHeaderId = <span class="number">1</span>;

- List<GridItem> hasHeaderIdList;

- 
- <span class="keyword">for</span>(ListIterator<GridItem> it = nonHeaderIdList.listIterator(); it.hasNext();){

- GridItem mGridItem = it.next();

- String ymd = mGridItem.getTime();

- <span class="keyword">if</span>(!mHeaderIdMap.containsKey(ymd)){

- mGridItem.setHeaderId(mHeaderId);

- mHeaderIdMap.put(ymd, mHeaderId);

- mHeaderId ++;

- }<span class="keyword">else</span>{

- mGridItem.setHeaderId(mHeaderIdMap.get(ymd));

- }

- }

- hasHeaderIdList = nonHeaderIdList;

- 
- <span class="keyword">return</span> hasHeaderIdList;

- }

- 
- 
- <span class="annotation">@Override</span>

- <span class="keyword">protected</span> <span class="keyword">void</span> onDestroy() {

- <span class="keyword">super</span>.onDestroy();

- <span class="comment">//退出页面清除LRUCache中的Bitmap占用的内存</span>

- NativeImageLoader.getInstance().trimMemCache();

- }

- 
- 
- <span class="comment">/**</span>

- <span class="comment">     * 将毫秒数装换成pattern这个格式,我这里是转换成年月日</span>

- <span class="comment">     * @param time</span>

- <span class="comment">     * @param pattern</span>

- <span class="comment">     * @return</span>

- <span class="comment">     */</span>

- <span class="keyword">public</span> <span class="keyword">static</span> String paserTimeToYMD(<span class="keyword">long</span> time, String pattern ) {

- System.setProperty(<span class="string">&#8220;user.timezone&#8221;</span>, <span class="string">&#8220;Asia/Shanghai&#8221;</span>);

- TimeZone tz = TimeZone.getTimeZone(<span class="string">&#8220;Asia/Shanghai&#8221;</span>);

- TimeZone.setDefault(tz);

- SimpleDateFormat format = <span class="keyword">new</span> SimpleDateFormat(pattern);

- <span class="keyword">return</span> format.format(<span class="keyword">new</span> Date(time * 1000L));

- }

- 
- }

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

**[java]** [view plain](http://blog.csdn.net/xiaanming/article/details/20481185#)[copy](http://blog.csdn.net/xiaanming/article/details/20481185#)[![在CODE上查看代码片](https://code.csdn.net/assets/CODE_ico.png)](https://code.csdn.net/snippets/220204)[![派生到我的代码片](https://code.csdn.net/assets/ico_fork.svg)](https://code.csdn.net/snippets/220204/fork)
  <div>
    <embed id="ZeroClipboardMovie_7" src="http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf" type="application/x-shockwave-flash" width="18" height="18" align="middle" name="ZeroClipboardMovie_7">
    </embed>
  </div>
</div>
- <span class="keyword">package</span> com.example.stickyheadergridview;

- 
- <span class="keyword">import</span> java.util.Comparator;

- 
- <span class="keyword">public</span> <span class="keyword">class</span> YMDComparator <span class="keyword">implements</span> Comparator<GridItem> {

- 
- <span class="annotation">@Override</span>

- <span class="keyword">public</span> <span class="keyword">int</span> compare(GridItem o1, GridItem o2) {

- <span class="keyword">return</span> o1.getTime().compareTo(o2.getTime());

- }

- 
- }

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

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

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

项目源码,点击下载

 

💬 评论