Reprinted please indicate this article from xiaanming blog (http://blog.csdn.net/xiaanming/article/details/20481185), please respect others' hard work results, thank you!
Hello everyone! I have not written any articles for almost a month since I came back from the end of the year. First, I feel that I do not know what articles to write, and I don't have a good subject to write. Second, I am delayed due to my own private affairs, so the first article of the year has only been published now. In 2014, I will continue to update my blog on CSDN. Please pay attention to it, today, this article mainly introduces the use of the open-source library StickyGridHeaders. StickyGridHeaders is an Android library for custom GridView with sections and headers. sections is the separation between the GridView items, headers is the title fixed on the top of the GridView, similar to the effects of some Android mobile phone contacts.
First, create an Android project StickyHeaderGridView, and
Com. tonicartos. widget. the stickygridheaders package is the source code of the open source library StickyGridHeaders, com. example. the stickyheadergridview package is the code for implementing this function, and there are quite a few classes. I will introduce them one by one.
GridItem is used to encapsulate the data of each Item in StickyGridHeadersGridView. It contains the path of the local image, the time when the image is added to the mobile phone system, and the headerId.
Package com. example. stickyheadergridview;/*** @ blog http://blog.csdn.net/xiaanming *** @ author xiaanming ***/public class GridItem {/*** image path */private String path; /*** the time when the image is added to the mobile phone. Only the year, month, and day */private String time is used./*** the HeaderId corresponding to each Item */private int headerId; public GridItem (String path, String time) {super (); this. path = path; this. time = time;} public String getPath () {return path;} public void setPath (String path) {this. path = path;} public String getTime () {return time;} public void setTime (String time) {this. time = time;} public int getHeaderId () {return headerId;} public void setHeaderId (int headerId) {this. headerId = headerId ;}}
The path of the image and the time when the image is added can be directly obtained through ContentProvider, but the headerId needs to be generated according to the logic.
Package com. example. stickyheadergridview; import android. content. contentResolver; import android. content. context; import android. content. intent; import android. database. cursor; import android.net. uri; import android. OS. environment; import android. OS. handler; import android. OS. message; import android. provider. mediaStore;/*** image scanner ** @ author xiaanming **/public class imagemask {private Context mContext; public imagemask (Context context) {this. mContext = context;}/*** use ContentProvider to scan the image in the mobile phone and call back the scanned Cursor to the ScanCompleteCallBack * method, this method is run in the Child thread */public void scanImages (final ScanCompleteCallBack callback) {final Handler mHandler = new Handler () {@ Overridepublic void handleMessage (Message msg) {super. handleMessage (msg); callback. scanComplete (Cursor) msg. obj) ;}}; new Thread (new Runnable () {@ Overridepublic void run () {// first send a broadcast to scan the entire SD card mContext. sendBroadcast (new Intent (Intent. ACTION_MEDIA_MOUNTED, Uri. parse ("file: //" + Environment. getExternalStorageDirectory (); Uri mImageUri = MediaStore. images. media. EXTERNAL_CONTENT_URI; ContentResolver mContentResolver = mContext. getContentResolver (); Cursor mCursor = mContentResolver. query (mImageUri, null, MediaStore. images. media. DATE_ADDED); // use Handler to notify the calling thread of Message msg = mHandler. obtainMessage (); msg. obj = mCursor; mHandler. sendMessage (msg );}}). start ();}/*** callback interface after scanning **/public static interface ScanCompleteCallBack {public void scanComplete (Cursor cursor );}}
ImageScanner is an image scanner class. This class uses ContentProvider to scan pictures on mobile phones. We can scan images on mobile phones by calling scanImages, call back the scanned Cursor to the ScanCompleteCallBack method of the scanComplete interface. This operation runs in the Child thread because the image scan is time-consuming, before scanning an image, we need to send a broadcast to scan the external media library. Why? If we add an image to the SD card, the image has already been added, however, the Library has not been updated synchronously. If the library is not synchronized, we cannot see the newly added images. Of course, we can restart the system to update the library, but this is not desirable, so we can directly send the broadcast to synchronize the media library.
Package com. example. stickyheadergridview; import java. util. concurrent. executorService; import java. util. concurrent. executors; import android. graphics. bitmap; import android. graphics. bitmapFactory; import android. graphics. point; import android. OS. handler; import android. OS. message; import android. support. v4.util. lruCache; import android. util. log;/*** the local image loader parses local images asynchronously. In Singleton mode, getInstance () is used to obtain NativeImageLoad. Er instance * calls the loadNativeImage () method to load the local image, this class can be used as a tool class to load local images ** @ blog http://blog.csdn.net/xiaanming ** @ author xiaanming **/public class NativeImageLoader {private static final String TAG = NativeImageLoader. class. getSimpleName (); private static NativeImageLoader mInstance = new NativeImageLoader (); private static LruCache <String, Bitmap> mMemoryCache; private ExecutorService mImageThreadPool = Executors. NewFixedThreadPool (1); private NativeImageLoader () {// obtain the maximum memory of the application. final int maxMemory = (int) (Runtime. getRuntime (). maxMemory (); // use 1/8 of the maximum memory to store images. final int cacheSize = maxMemory/8; mMemoryCache = new LruCache <String, Bitmap> (cacheSize) {// get bytes @ Overrideprotected int sizeOf (String key, Bitmap bitmap) {return bitmap. getRowBytes () * bitmap. getHeight () ;};}/*** use this method to obtain the NativeImageLoader instance * @ re Turn */public static NativeImageLoader getInstance () {return mInstance;}/*** load the local image, do not crop the image * @ param path * @ param mCallBack * @ return */public Bitmap loadNativeImage (final String path, final NativeImageCallBack mCallBack) {return this. loadNativeImage (path, null, mCallBack);}/*** this method is used to load local images. The mPoint here is used to encapsulate the width and height of ImageView, we will crop Bitmap based on the size of the ImageView control * if you do not want to crop the image, call loadNativeImage (final String path, f Inal implements mCallBack) to load * @ param path * @ param mPoint * @ param mCallBack * @ return */public Bitmap loadNativeImage (final String path, final Point mPoint, final NativeImageCallBack mCallBack) {// obtain BitmapBitmap bitmap = getBitmapFromMemCache (path); final Handler mHander = new Handler () {@ Overridepublic void handleMessage (Message msg) {super. handleMessage (msg); mCallBack. onImageLoader (Bitmap) msg. obj, path) ;}}; // If the Bitmap is not in the memory cache, the thread is enabled to load the local image, add Bitmap to the mMemoryCache. if (bitmap = null1_mimagethreadpool.exe cute (new Runnable () {@ Overridepublic void run () {// obtain the thumbnail Bitmap mBitmap = decodeThumbBitmapForFile (path, mPoint = null? 0: mPoint. x, mPoint = null? 0: mPoint. y); Message msg = mHander. obtainMessage (); msg. obj = mBitmap; mHander. sendMessage (msg); // Add the image to the memory cache. addBitmapToMemoryCache (path, mBitmap) ;}}return bitmap ;} /*** add Bitmap to the memory cache ** @ param key * @ param bitmap */private void addBitmapToMemoryCache (String key, Bitmap bitmap) {if (getBitmapFromMemCache (key) = null & bitmap! = Null) {mMemoryCache. put (key, bitmap) ;}}/*** get the image in memory based on the key * @ param key * @ return */private Bitmap getBitmapFromMemCache (String key) {Bitmap bitmap = mMemoryCache. get (key); if (bitmap! = Null) {Log. I (TAG, "get image for LRUCache, path =" + key) ;}return bitmap;}/*** clear bitmap */public void trimMemCache () {mMemoryCache in LruCache. evictAll ();}/*** according to View (mainly ImageView) to obtain the thumbnail of an image * @ param path * @ param viewWidth * @ param viewHeight * @ return */private Bitmap decodeThumbBitmapForFile (String path, int viewWidth, int viewHeight) {BitmapFactory. options options = new BitmapFactory. option S (); // if it is set to true, it indicates parsing the Bitmap object, which does not occupy the memory options. inJustDecodeBounds = true; BitmapFactory. decodeFile (path, options); // set the zoom ratio options. inSampleSize = computeScale (options, viewWidth, viewHeight); // set to false and resolve the Bitmap object to the memory options. inJustDecodeBounds = false; Log. e (TAG, "get Iamge form file, path =" + path); return BitmapFactory. decodeFile (path, options);}/*** calculate the Bitmap scaling ratio based on the width and height of the View (mainly ImageView. By default, no scaling * @ param options * @ param width * @ param height */private int computeScale (BitmapFactory. options options, int viewWidth, int viewHeight) {int inSampleSize = 1; if (viewWidth = 0 | viewWidth = 0) {return inSampleSize;} int bitmapWidth = options. outWidth; int bitmapHeight = options. outHeight; // if the width or height of the Bitmap is greater than the width and height of the image's View, calculate the zoom ratio if (bitmapWidth> viewWidth | bitmapHeight> viewWidth) {int widthSc Ale = Math. round (float) bitmapWidth/(float) viewWidth); int heightScale = Math. round (float) bitmapHeight/(float) viewWidth); // to ensure that the image is not scaled or deformed, The inSampleSize = widthScale The NativeImageLoader class is a singleton class that provides local image loading, memory cache, cropping, and other logic. This class uses asynchronous loading when loading local images, loading Large images is also time-consuming, so we use the sub-thread method to load images. For the image cache mechanism, we use LruCache, we use 1/8 of the app memory allocated to the mobile phone to cache images. The memory allocated to the image cache should not be too large. OOM may also occur if it is too large, this class uses my previous article Android to use ContentProvider to scan pictures on the mobile phone and display local image effects in imitation. Here I will not describe it too much. If you are interested, you can check out the article, however, a method trimMemCache () is added to clear the memory used by LruCache.
Let's look at the layout code of the main interface. There is only one custom StickyGridHeadersGridView control.
<?xml version="1.0" encoding="utf-8"?><com.tonicartos.widget.stickygridheaders.StickyGridHeadersGridView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/asset_grid" android:layout_width="match_parent" android:layout_height="match_parent" android:clipToPadding="false" android:columnWidth="90dip" android:horizontalSpacing="3dip" android:numColumns="auto_fit" android:verticalSpacing="3dip" />
Before reading the code on the main interface, Let's first look at the code of StickyGridAdapter.
Package com. example. stickyheadergridview; import java. util. list; import android. content. context; import android. graphics. bitmap; import android. graphics. point; import android. view. layoutInflater; import android. view. view; import android. view. viewGroup; import android. widget. baseAdapter; import android. widget. gridView; import android. widget. imageView; import android. widget. textView; import com. example. stickyhea Dergridview. myImageView. onMeasureListener; import com. example. stickyheadergridview. nativeImageLoader. nativeImageCallBack; import com. tonicartos. widget. stickygridheaders. handler;/*** StickyHeaderGridView adapter, in addition to inheriting the BaseAdapter, you also need to implement the * StickyGridHeadersSimpleAdapter interface ** @ blog http://blog.csdn.net/xiaanming ** @ author xiaanming **/public class stickygriadapter dextends BaseAd Apter implementsStickyGridHeadersSimpleAdapter {private List <GridItem> hasHeaderIdList; private LayoutInflater mInflater; private GridView mGridView; private Point mPoint = new Point (0, 0 ); // It is used to encapsulate the public StickyGridAdapter (Context context, List <GridItem> hasHeaderIdList, GridView mGridView) {mInflater = LayoutInflater. from (context); this. mGridView = mGridView; this. hasHeaderIdList = hasHea DerIdList;} @ Overridepublic int getCount () {return hasHeaderIdList. size () ;}@ Overridepublic Object getItem (int position) {return hasHeaderIdList. get (position) ;}@ Overridepublic long getItemId (int position) {return position ;}@ Overridepublic View getView (int position, View convertView, ViewGroup parent) {ViewHolder mViewHolder; if (convertView = null) {mViewHolder = new ViewHolder (); convertView = mInfl Ater. inflate (R. layout. grid_item, parent, false); mViewHolder. mImageView = (MyImageView) convertView. findViewById (R. id. grid_item); convertView. setTag (mViewHolder); // used to listen to the width and height of ImageView mViewHolder. mImageView. setOnMeasureListener (new OnMeasureListener () {@ Override public void onMeasureSize (int width, int height) {mPoint. set (width, height) ;}}) ;}else {mViewHolder = (ViewHolder) convertView. getTag () ;} String path = hasHeaderIdList. get (position ). getPath (); mViewHolder. mImageView. setTag (path); Bitmap bitmap = NativeImageLoader. getInstance (). loadNativeImage (path, mPoint, new NativeImageCallBack () {@ Overridepublic void onImageLoader (Bitmap bitmap, String path) {ImageView mImageView = (ImageView) mGridView. findViewWithTag (path); if (bitmap! = Null & mImageView! = Null) {mImageView. setImageBitmap (bitmap) ;}}); if (bitmap! = Null) {mViewHolder. mImageView. setImageBitmap (bitmap);} else {mViewHolder. mImageView. setImageResource (R. drawable. friends_sends_pictures_no);} return convertView;} @ Overridepublic View getHeaderView (int position, View convertView, ViewGroup parent) {HeaderViewHolder mHeaderHolder; if (convertView = null) {mHeaderHolder = new HeaderViewHolder (); convertView = mInflater. inflate (R. layout. header, parent, false); mHeaderHolder. mTextView = (TextView) convertView. findViewById (R. id. header); convertView. setTag (mHeaderHolder);} else {mHeaderHolder = (HeaderViewHolder) convertView. getTag ();} mHeaderHolder. mTextView. setText (hasHeaderIdList. get (position ). getTime (); return convertView;}/*** get the HeaderId. If the HeaderId is not equal, add a Header */@ Overridepublic long getHeaderId (int position) {return hasHeaderIdList. get (position ). getHeaderId ();} public static class ViewHolder {public MyImageView mImageView;} public static class HeaderViewHolder {public TextView mTextView ;}}
In addition to inheriting BaseAdapter, you also need to implement the StickyGridHeadersSimpleAdapter interface. to inherit from BaseAdapter, You need to implement getCount (), getItem (int position), getItemId (int position), getView (int position, View convertView, viewGroup parent) these four methods are implemented in the same way as we usually do. The main difference is to take a look at the getView () method, we set the Tag of the image path of each item to the ImageView, and then use NativeImageLoader to load the local image. The ImageView used here is still a custom MyImageView, this custom ImageView mainly implements callback of the width and height of the measurement to onMeasureSize () after MyImageView is measured. Then, we can crop the image according to the size of MyImageView.
In addition, we need to implement the getHeaderId (int position), getHeaderView (int position, View convertView, ViewGroup parent), and getHeaderId (int position) Methods of the StickyGridHeadersSimpleAdapter interface to return the headerId and getHeaderView () the method is to generate sections and headers. If the headerId of an item is different from the HeaderId of another item, the getHeaderView method is called to generate a sections to differentiate different groups, it will generate a headers at the top according to the headerId of firstVisibleItem. Therefore, the key is how to generate the headerId of each Item. The headerId generation method is in MainActivity.
Package com. example. stickyheadergridview; import java. text. simpleDateFormat; import java. util. arrayList; import java. util. collections; import java. util. date; import java. util. hashMap; import java. util. list; import java. util. listIterator; import java. util. map; import java. util. timeZone; import android. app. activity; import android. app. progressDialog; import android. database. cursor; import android. OS. bundle; import Android. provider. mediaStore; import android. widget. gridView; import com. example. stickyheadergridview. imageworkflow. scanCompleteCallBack; public class MainActivity extends Activity {private ProgressDialog mProgressDialog;/*** image scanner */private image1_malog; private GridView mGridView; /*** List without HeaderId */private List <GridItem> nonHeaderIdList = new ArrayList <GridItem> (); @ Overrideprotected vo Id onCreate (Bundle savedInstanceState) {super. onCreate (savedInstanceState); setContentView (R. layout. activity_main); mGridView = (GridView) findViewById (R. id. asset_grid); mnames = new imagenames (this); mnames. scanImages (new ScanCompleteCallBack () {mProgressDialog = ProgressDialog. show (MainActivity. this, null, "loading... ") ;}@ Overridepublic void scanComplete (Cursor cursor) {// close the progress bar mProgressDialog. d Ismiss (); if (cursor = null) {return;} while (cursor. moveToNext () {// obtain the image path String path = cursor. getString (cursor. getColumnIndex (MediaStore. images. media. DATA); // The number of milliseconds to add the retrieved image to the system long times = cursor. getLong (cursor. getColumnIndex (MediaStore. images. media. DATE_ADDED); GridItem mGridItem = new GridItem (path, paserTimeToYMD (times, "yyyy MM dd"); nonHeaderIdList. add (mGridItem);} cursor. close (); // send Generate HeaderIdList <GridItem> hasHeaderIdList = generateHeaderId (nonHeaderIdList); // sort Collections. sort (hasHeaderIdList, new YMDComparator (); mGridView. setAdapter (new StickyGridAdapter (MainActivity. this, hasHeaderIdList, mGridView) ;}};}/*** generate HeaderId for the items in the GridView, headerId * is generated based on the year, month, and day when the image is added. The HeaderId * @ param nonHeaderIdList * @ return */private List <GridItem> generateHeaderId (List <GridItem> nonHeaderIdList) {Map <String, Integer> mHeaderIdMap = new HashMap <String, Integer> (); int mHeaderId = 1; List <GridItem> hasHeaderIdList; for (ListIterator <GridItem> it = nonHeaderIdList. listIterator (); it. hasNext ();) {GridItem mGridItem = it. next (); String ymd = mGridItem. getTime (); if (! MHeaderIdMap. containsKey (ymd) {mGridItem. setHeaderId (mHeaderId); mHeaderIdMap. put (ymd, mHeaderId); mHeaderId ++;} else {mGridItem. setHeaderId (mHeaderIdMap. get (ymd) ;}} hasHeaderIdList = nonHeaderIdList; return hasHeaderIdList ;}@ Overrideprotected void onDestroy () {super. onDestroy (); // exit the page to clear the memory occupied by Bitmap in LRUCache NativeImageLoader. getInstance (). trimMemCache ();}/*** replace the number of milliseconds with the pattern format, here is the conversion to year, month, day * @ param time * @ param pattern * @ return */public static String paserTimeToYMD (long time, String pattern) {System. setProperty ("user. timezone "," Asia/Shanghai "); TimeZone tz = TimeZone. getTimeZone ("Asia/Shanghai"); TimeZone. setDefault (tz); SimpleDateFormat format = new SimpleDateFormat (pattern); return format. format (new Date (time * 1000L ));}}
The Code on the main interface is mainly to assemble the data of StickyGridHeadersGridView. We will resolve the scanned image path to the GridItem format in milliseconds, then add GridItem to the List. At this time, no headerId is generated for each Item. We need to call generateHeaderId () to generate the same HeaderId for images added to the system on the same day, in this way, the images added to the same day are in the same group. Of course, you need to change the images for the same month, and modify the second parameter of the paserTimeToYMD () method, after the Activity is finished, we use NativeImageLoader. getInstance (). trimMemCache () releases the memory. Of course, we also need to sort the data in the GridView. For example, the items with the same headerId are not continuous, multiple Functions (multiple groups) are generated for items with the same headerId. Therefore, we need to use YMDComparator to bring together images added to the same day. The YMDComparator code is as follows:
package com.example.stickyheadergridview;import java.util.Comparator;public class YMDComparator implements Comparator<GridItem> {@Overridepublic int compare(GridItem o1, GridItem o2) {return o1.getTime().compareTo(o2.getTime());}}
Of course, this article does not use YMDComparator, because when I use ContentProvider to retrieve images, it is sorted according to the time added to the system. Sorting is only for general data.
Next, run the program to see how it works.
This is the end of today's article. Thank you for watching this article. There is also a class and some resource files that have not been posted. If you are interested in studying it, download the project Source Code directly, remember, when using LruCache to cache images, do not set the cacheSize to be too large. Otherwise, the probability of OOM generation will be greater. I tested the above program to show that over 600 images slide back and forth without generating OOM, if you have any questions, leave a message below!
Project source code, click to download