http://blog.youkuaiyun.com/java2009cgh/article/details/34836967
- <GridView android:id="@+id/grid"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:verticalSpacing="35px" <!-- grid元素之间的竖直间隔 -->
- android:horizontalSpacing="5px" <!--grid元素之间的水平间隔 -->
- android:numColumns="auto_fit" <!--表示有多少列,如果设置为auto_fit,将根据columnWidth和Spacing来自动计算 -->
- android:columnWidth="100px" <!-- 一般建议采用有像素密度无关的dip或者dp来表示-->
- android:stretchMode="columnWidth" <!--如何填满空余的位置,模拟器采用WVGA800*480,每排4列,有4*100+5*3=415,还余65px的空间,如果是columnWidth,则这剩余的65将分摊给4列,每列增加16/17px。如果采用SpacingWidth,则分摊给3个间隔空隙 -->
- android:gravity="center" />
Android GridView 多标题分组显示,上下拉刷新,convertview可重用
Android学习小Demo(12)利用StickyListHeaders来实现ListView的分组实现
很多情况下, 我们想要ListView上面展示的东西是可以分组的,比如联系人列表,国家列表啊,这样看起来数据的展现比较有层次感,而且也有助于我们快速定位到某一个具体的条目上, 具体效果请看下图:
这是前面TodoList小demo的MainActivity,主要是来展现用户添加的任务的,在原来的基础上添加了分组的效果。
接下来我们具体来讲一下这个效果是怎么实现的。
这是利用开源库StickyListHeaders(传送门:https://github.com/emilsjolander/StickyListHeaders )来实现的,这个实现的效果是基于ListView的,而其实也有关于GridView而实现的分组的效果,大家可以参考一下xiaanming的博客(他的文章名字都很长。。。):
Android 使用开源库StickyGridHeaders来实现带sections和headers的GridView显示本地图片效果
0)关于如何导进开源库,大家请参考: 如何导进开源库StickyListHeaders
1)然后,我们要想清楚一件事情,即分组的ListView,是包含两部分:Header 和 Item,所以相对应的我们也要为其定义两个Layout,如下:
1.1)task_header.xml
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@drawable/header_selector" > <TextView android:id="@+id/tvHeader" android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_gravity="start|left" android:padding="5dp" android:textColor="@android:color/white" android:textSize="17sp" android:textStyle="bold" /> </RelativeLayout>因为我们在Header上面只是展现一个日期,所以我们只需要一个TextView即可。
1.2)task_item.xml
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="32dp" android:descendantFocusability="blocksDescendants" android:padding="5dip"> <ImageView android:padding="5dp" android:layout_centerVertical="true" android:id="@+id/ivComplete" android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_alignParentLeft="true" android:layout_alignParentTop="true" android:contentDescription="@string/imageview_contentdesc" android:src="@drawable/handdraw_tick" android:visibility="gone" /> <TextView android:id="@+id/tvTitle" android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_toRightOf="@+id/ivComplete" android:gravity="left|center_vertical" android:padding="5dp" android:textSize="20sp" /> </RelativeLayout>在这里面,我们定义了每一个item要展现的布局,跟平常我们经常用的layout其实是一样的,大家接下来自定义的Adapter也就理解了。
2)第二步,跟平常绑定ListView一样,我们也需要自定义一个Adapter,称之为StickyListTaskAdapter。
我们来看一下 StickListTaskAdapter 完整的代码,如下:
public class StickListTaskAdapter extends BaseAdapter
implements SectionIndexer, StickyListHeadersAdapter{
private LayoutInflater layoutInflater;
private List<TodoTask> tasks;
private int[] sectionIndices;
private String[] sectionHeaders;
public StickListTaskAdapter(Context context, List<TodoTask> tasks) {
layoutInflater = LayoutInflater.from(context);
this.tasks = tasks;
sectionIndices = getSectionIndices();
sectionHeaders = getSectionHeaders();
}
public void refresh(List<TodoTask> tasks){
this.tasks = tasks;
sectionIndices = getSectionIndices();
sectionHeaders = getSectionHeaders();
notifyDataSetChanged();
}
private int[] getSectionIndices() {
List<Integer> sectionIndices = new ArrayList<Integer>();
String lastCreateDate = Helper.getFormatDate(tasks.get(0).getCreateTime());
sectionIndices.add(0);
for (int i = 1; i < tasks.size(); i++) {
String createDate = Helper.getFormatDate(tasks.get(i).getCreateTime());
if (!createDate.equals(lastCreateDate)) {
lastCreateDate = createDate;
sectionIndices.add(i);
}
}
int[] sections = new int[sectionIndices.size()];
for (int i = 0; i < sectionIndices.size(); i++) {
sections[i] = sectionIndices.get(i);
}
return sections;
}
private String[] getSectionHeaders() {
String[] sectionHeaders = new String[sectionIndices.length];
for (int i = 0; i < sectionIndices.length; i++) {
sectionHeaders[i] = Helper.getFormatDate(tasks.get(sectionIndices[i]).getCreateTime());
}
return sectionHeaders;
}
@Override
public int getCount() {
return tasks.size();
}
@Override
public Object getItem(int position) {
return tasks.get(position);
}
@Override
public long getItemId(int position) {
return tasks.get(position).getId();
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder;
if (convertView == null) {
viewHolder = new ViewHolder();
convertView = layoutInflater.inflate(R.layout.task_item, null);
viewHolder.ivComplete = (ImageView)convertView.findViewById(R.id.ivComplete);
viewHolder.tvTitle = (TextView) convertView.findViewById(R.id.tvTitle);
viewHolder.tvCreateTime = (TextView) convertView.findViewById(R.id.tvCreateTime);
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
}
if("Y".equals(tasks.get(position).getFlagCompleted())){
viewHolder.ivComplete.setVisibility(View.VISIBLE);
viewHolder.tvCreateTime.setText(Helper.getFormatDate(tasks.get(position).getCompleteTime()));
}else{
viewHolder.ivComplete.setVisibility(View.GONE);
viewHolder.tvCreateTime.setText(Helper.getFormatDate(tasks.get(position).getCreateTime()));
}
viewHolder.tvTitle.setText(tasks.get(position).getTitle());
return convertView;
}
@Override
public View getHeaderView(int position, View convertView, ViewGroup parent) {
HeaderViewHolder hvh;
if(convertView == null){
hvh = new HeaderViewHolder();
convertView = layoutInflater.inflate(R.layout.task_header, null);
hvh.tvHeader = (TextView) convertView.findViewById(R.id.tvHeader);
convertView.setTag(hvh);
}else{
hvh = (HeaderViewHolder)convertView.getTag();
}
hvh.tvHeader.setText(Helper.getFormatDate(tasks.get(position).getCreateTime()));
return convertView;
}
@Override
public long getHeaderId(int position) {
return Helper.changeStringDateToLong(Helper.getFormatDate(tasks.get(position).getCreateTime()));
}
@Override
public Object[] getSections() {
// TODO Auto-generated method stub
return sectionHeaders;
}
@Override
public int getPositionForSection(int sectionIndex) {
if (sectionIndex >= sectionIndices.length) {
sectionIndex = sectionIndices.length - 1;
} else if (sectionIndex < 0) {
sectionIndex = 0;
}
return sectionIndices[sectionIndex];
}
@Override
public int getSectionForPosition(int position) {
for (int i = 0; i < sectionIndices.length; i++) {
if (position < sectionIndices[i]) {
return i - 1;
}
}
return sectionIndices.length - 1;
}
class ViewHolder {
ImageView ivComplete;
TextView tvTitle;
TextView tvCreateTime;
}
class HeaderViewHolder{
TextView tvHeader;
}
}
首先我们定义了下面两个数组,并且需要在构造的时候初始化它们:
private int[] sectionIndices; private String[] sectionHeaders;通过构造函数,我们可以发现,我们传到这个Adapter的数据源只有一个ArrayList<TodoTask>,因为这才是真正的数据,我们分组也是基于这个数据源的。
但是我们要展现Header的,那么Header的数据是从哪里来的呢?所以我们在初始化的时候,就要去获得Header的数据。
大家可以看一下两个getSectionXXX的函数,可以看到在里面做了下面两件事情:
1)sectionIndices数组用来存放每一轮分组的第一个item的位置。
2)sectionHeaders数组用来存放每一个分组要展现的数据,因为能够分到同一组的item,它们肯定有一个相同且可以跟其它section区别开来的值,比如在上面,我是利用create_time来分成不同的组的,所以sectionHeaders存放的只是一个create_time。
不过大家在这里千万要注意:基于某个字段的分组,这个数据源必须是在这个字段上是有序的!
如果不是有序的,那么属于相同分组的数据就会被拆成几段了,而这个分组就没有意义了。
所以如果数据源不是有序的,那么我们在初始化获取分组的时候,也需要先将其变成有序的。
接下来,在我们平常继承BaseAdapter的情况下,我们都要去实现getView等功能,在上面也是一样的,但是我们这个Adapter还必须要实现另外两个接口:
1)StickyListHeadersAdapter
2 )SectionIndexer
我们先来看看StickyListHeaderAdapter的定义:
public interface StickyListHeadersAdapter extends ListAdapter {
View getHeaderView(int position, View convertView, ViewGroup parent);
long getHeaderId(int position);
}
这是开源库提供的接口,因为我们需要添加Header,所以我们必须在Adapter中也返回一个Header的View,这其实跟实现getView是一样的道理的,都挺好理解的。
所以在getHeaderView里面就会用到我们一开始新定义的那个task_header.xml了,同样的,为了实现优化,也会利用一个HeaderViewHolder。
另外一个接口就是SectionIndexer了,它有三个方法要实现,如下:
public interface SectionIndexer {
Object[] getSections();
int getPositionForSection(int sectionIndex);
int getSectionForPosition(int position);
}
看代码的实现,可以发现:
getSections:返回的其实就是Header上面要展示的数据,在这里其实就是sectionHeaders了,存放的是create_time的数据。
getPositionForSection:返回的是这个section数据在List<TodoTask>这个基础数据源中的位置,因为section中的数据其实也是从List<TodoTask>中获取到的。
getSectionForPosition:则是通过在基础数据源List<TodoTask>中的位置找出对应的Section中的数据,原因同上。
那么上面这两个函数的作用在哪?
大家有没有发现,当同一个分组的数据在滚动的时候,最上面的分组并不会变化,只有当滑到其它分组的时候,这个分组才会被新的分组给替换掉。这个效果实现的原理就在这里了,虽然我没有看过源代码,但是我认为,在每一个item滚动的时候,都会找出其对应的分组,然后显示在最上方,如果都是属于同一个分组的话,那么最上面的显示的当然一直都是这个分组对应的Header了。
综上所述,为了实现Sticky和分组的效果,我们就要在原来继承BaseAdapter的基础上再实现多两个接口,并实现对应的逻辑。
那么如何在Activity中使用呢?请看下面的代码:
在xml中定义:
<se.emilsjolander.stickylistheaders.StickyListHeadersListView
android:id="@+id/lvTasks"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/todo_bg"
android:clipToPadding="false"
android:divider="#44FFFFFF"
android:dividerHeight="1dp"
android:drawSelectorOnTop="true"
android:fastScrollEnabled="true"
android:overScrollMode="never"
android:padding="16dp"
android:scrollbarStyle="outsideOverlay" />
在MainActivity中使用:
lvTasks = (StickyListHeadersListView) findViewById(R.id.lvTasks); taskAdapter = new StickListTaskAdapter(this, tasks); lvTasks.setAdapter(taskAdapter); lvTasks.setDrawingListUnderStickyHeader(true); lvTasks.setAreHeadersSticky(true); lvTasks.setOnItemLongClickListener(onItemLongClickListener); lvTasks.setOnItemClickListener(onItemClickListener);而开源库中StickyListHeadersListView还提供了几个接口,可以让我们在Activity中去实现,不过这些就有待大家自己去慢慢学习了。
public class StickyListHeadersListView extends FrameLayout {
public interface OnHeaderClickListener {
public void onHeaderClick(StickyListHeadersListView l, View header,
int itemPosition, long headerId, boolean currentlySticky);
}
/**
* Notifies the listener when the sticky headers top offset has changed.
*/
public interface OnStickyHeaderOffsetChangedListener {
/**
* @param l The view parent
* @param header The currently sticky header being offset.
* This header is not guaranteed to have it's measurements set.
* It is however guaranteed that this view has been measured,
* therefor you should user getMeasured* methods instead of
* get* methods for determining the view's size.
* @param offset The amount the sticky header is offset by towards to top of the screen.
*/
public void onStickyHeaderOffsetChanged(StickyListHeadersListView l, View header, int offset);
}
/**
* Notifies the listener when the sticky header has been updated
*/
public interface OnStickyHeaderChangedListener {
/**
* @param l The view parent
* @param header The new sticky header view.
* @param itemPosition The position of the item within the adapter's data set of
* the item whose header is now sticky.
* @param headerId The id of the new sticky header.
*/
public void onStickyHeaderChanged(StickyListHeadersListView l, View header,
int itemPosition, long headerId);
}
结束。

版权声明:本文为博主原创文章,未经博主允许不得转载。
转载请注明本文出自xiaanming的博客(http://blog.youkuaiyun.com/xiaanming/article/details/20481185),请尊重他人的辛勤劳动成果,谢谢!
大家好!过完年回来到现在差不多一个月没写文章了,一是觉得不知道写哪些方面的文章,没有好的题材来写,二是因为自己的一些私事给耽误了,所以过完年的第一篇文章到现在才发表出来,2014年我还是会继续在优快云上面更新我的博客,欢迎大家关注一下,今天这篇文章主要的是介绍下开源库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
- package com.example.stickyheadergridview;
- /**
- * @blog http://blog.youkuaiyun.com/xiaanming
- *
- * @author xiaanming
- *
- */
- public class GridItem {
- /**
- * 图片的路径
- */
- private String path;
- /**
- * 图片加入手机中的时间,只取了年月日
- */
- private String time;
- /**
- * 每个Item对应的HeaderId
- */
- 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;
- }
- }
- 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;
- /**
- * 图片扫描器
- *
- * @author xiaanming
- *
- */
- public class ImageScanner {
- private Context mContext;
- public ImageScanner(Context context){
- this.mContext = context;
- }
- /**
- * 利用ContentProvider扫描手机中的图片,将扫描的Cursor回调到ScanCompleteCallBack
- * 接口的scanComplete方法中,此方法在运行在子线程中
- */
- public void scanImages(final ScanCompleteCallBack callback) {
- final Handler mHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- super.handleMessage(msg);
- callback.scanComplete((Cursor)msg.obj);
- }
- };
- new Thread(new Runnable() {
- @Override
- public void run() {
- //先发送广播扫描下整个sd卡
- 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, null, null, MediaStore.Images.Media.DATE_ADDED);
- //利用Handler通知调用线程
- Message msg = mHandler.obtainMessage();
- msg.obj = mCursor;
- mHandler.sendMessage(msg);
- }
- }).start();
- }
- /**
- * 扫描完成之后的回调接口
- *
- */
- public static interface ScanCompleteCallBack{
- public void scanComplete(Cursor cursor);
- }
- }
- 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;
- /**
- * 本地图片加载器,采用的是异步解析本地图片,单例模式利用getInstance()获取NativeImageLoader实例
- * 调用loadNativeImage()方法加载本地图片,此类可作为一个加载本地图片的工具类
- *
- * @blog http://blog.youkuaiyun.com/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(){
- //获取应用程序的最大内存
- final int maxMemory = (int) (Runtime.getRuntime().maxMemory());
- //用最大内存的1/8来存储图片
- final int cacheSize = maxMemory / 8;
- mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
- //获取每张图片的bytes
- @Override
- protected int sizeOf(String key, Bitmap bitmap) {
- return bitmap.getRowBytes() * bitmap.getHeight();
- }
- };
- }
- /**
- * 通过此方法来获取NativeImageLoader的实例
- * @return
- */
- public static NativeImageLoader getInstance(){
- return mInstance;
- }
- /**
- * 加载本地图片,对图片不进行裁剪
- * @param path
- * @param mCallBack
- * @return
- */
- public Bitmap loadNativeImage(final String path, final NativeImageCallBack mCallBack){
- return this.loadNativeImage(path, null, mCallBack);
- }
- /**
- * 此方法来加载本地图片,这里的mPoint是用来封装ImageView的宽和高,我们会根据ImageView控件的大小来裁剪Bitmap
- * 如果你不想裁剪图片,调用loadNativeImage(final String path, final NativeImageCallBack mCallBack)来加载
- * @param path
- * @param mPoint
- * @param mCallBack
- * @return
- */
- public Bitmap loadNativeImage(final String path, final Point mPoint, final NativeImageCallBack mCallBack){
- //先获取内存中的Bitmap
- Bitmap bitmap = getBitmapFromMemCache(path);
- final Handler mHander = new Handler(){
- @Override
- public void handleMessage(Message msg) {
- super.handleMessage(msg);
- mCallBack.onImageLoader((Bitmap)msg.obj, path);
- }
- };
- //若该Bitmap不在内存缓存中,则启用线程去加载本地的图片,并将Bitmap加入到mMemoryCache中
- if(bitmap == null){
- mImageThreadPool.execute(new Runnable() {
- @Override
- public void run() {
- //先获取图片的缩略图
- Bitmap mBitmap = decodeThumbBitmapForFile(path, mPoint == null ? 0: mPoint.x, mPoint == null ? 0: mPoint.y);
- Message msg = mHander.obtainMessage();
- msg.obj = mBitmap;
- mHander.sendMessage(msg);
- //将图片加入到内存缓存
- addBitmapToMemoryCache(path, mBitmap);
- }
- });
- }
- return bitmap;
- }
- /**
- * 往内存缓存中添加Bitmap
- *
- * @param key
- * @param bitmap
- */
- private void addBitmapToMemoryCache(String key, Bitmap bitmap) {
- if (getBitmapFromMemCache(key) == null && bitmap != null) {
- mMemoryCache.put(key, bitmap);
- }
- }
- /**
- * 根据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;
- }
- /**
- * 清除LruCache中的bitmap
- */
- public void trimMemCache(){
- mMemoryCache.evictAll();
- }
- /**
- * 根据View(主要是ImageView)的宽和高来获取图片的缩略图
- * @param path
- * @param viewWidth
- * @param viewHeight
- * @return
- */
- private Bitmap decodeThumbBitmapForFile(String path, int viewWidth, int viewHeight){
- BitmapFactory.Options options = new BitmapFactory.Options();
- //设置为true,表示解析Bitmap对象,该对象不占内存
- options.inJustDecodeBounds = true;
- BitmapFactory.decodeFile(path, options);
- //设置缩放比例
- options.inSampleSize = computeScale(options, viewWidth, viewHeight);
- //设置为false,解析Bitmap对象加入到内存中
- options.inJustDecodeBounds = false;
- Log.e(TAG, "get Iamge form file, path = " + path);
- return BitmapFactory.decodeFile(path, options);
- }
- /**
- * 根据View(主要是ImageView)的宽和高来计算Bitmap缩放比例。默认不缩放
- * @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;
- //假如Bitmap的宽度或高度大于我们设定图片的View的宽高,则计算缩放比例
- if(bitmapWidth > viewWidth || bitmapHeight > viewWidth){
- int widthScale = Math.round((float) bitmapWidth / (float) viewWidth);
- int heightScale = Math.round((float) bitmapHeight / (float) viewWidth);
- //为了保证图片不缩放变形,我们取宽高比例最小的那个
- inSampleSize = widthScale < heightScale ? widthScale : heightScale;
- }
- return inSampleSize;
- }
- /**
- * 加载本地图片的回调接口
- *
- * @author xiaanming
- *
- */
- public interface NativeImageCallBack{
- /**
- * 当子线程加载完了本地的图片,将Bitmap和图片路径回调在此方法中
- * @param bitmap
- * @param path
- */
- public void onImageLoader(Bitmap bitmap, String path);
- }
- }
我们看主界面的布局代码,里面只有一个自定义的StickyGridHeadersGridView控件
- <?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" />
在看主界面的代码之前我们先看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.stickyheadergridview.MyImageView.OnMeasureListener;
- import com.example.stickyheadergridview.NativeImageLoader.NativeImageCallBack;
- import com.tonicartos.widget.stickygridheaders.StickyGridHeadersSimpleAdapter;
- /**
- * StickyHeaderGridView的适配器,除了要继承BaseAdapter之外还需要
- * 实现StickyGridHeadersSimpleAdapter接口
- *
- * @blog http://blog.youkuaiyun.com/xiaanming
- *
- * @author xiaanming
- *
- */
- public class StickyGridAdapter extends BaseAdapter implements
- StickyGridHeadersSimpleAdapter {
- private List<GridItem> hasHeaderIdList;
- private LayoutInflater mInflater;
- private GridView mGridView;
- private Point mPoint = new Point(0, 0);//用来封装ImageView的宽和高的对象
- public StickyGridAdapter(Context context, List<GridItem> hasHeaderIdList,
- GridView mGridView) {
- mInflater = LayoutInflater.from(context);
- this.mGridView = mGridView;
- this.hasHeaderIdList = hasHeaderIdList;
- }
- @Override
- public int getCount() {
- return hasHeaderIdList.size();
- }
- @Override
- public Object getItem(int position) {
- return hasHeaderIdList.get(position);
- }
- @Override
- public long getItemId(int position) {
- return position;
- }
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- ViewHolder mViewHolder;
- if (convertView == null) {
- mViewHolder = new ViewHolder();
- convertView = mInflater.inflate(R.layout.grid_item, parent, false);
- mViewHolder.mImageView = (MyImageView) convertView
- .findViewById(R.id.grid_item);
- convertView.setTag(mViewHolder);
- //用来监听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() {
- @Override
- public 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;
- }
- @Override
- public 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;
- }
- /**
- * 获取HeaderId, 只要HeaderId不相等就添加一个Header
- */
- @Override
- public long getHeaderId(int position) {
- return hasHeaderIdList.get(position).getHeaderId();
- }
- public static class ViewHolder {
- public MyImageView mImageView;
- }
- public static class HeaderViewHolder {
- public TextView mTextView;
- }
- }
另外我们需要实现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中
- 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.ImageScanner.ScanCompleteCallBack;
- public class MainActivity extends Activity {
- private ProgressDialog mProgressDialog;
- /**
- * 图片扫描器
- */
- private ImageScanner mScanner;
- private GridView mGridView;
- /**
- * 没有HeaderId的List
- */
- private List<GridItem> nonHeaderIdList = new ArrayList<GridItem>();
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- mGridView = (GridView) findViewById(R.id.asset_grid);
- mScanner = new ImageScanner(this);
- mScanner.scanImages(new ScanCompleteCallBack() {
- {
- mProgressDialog = ProgressDialog.show(MainActivity.this, null, "正在加载...");
- }
- @Override
- public void scanComplete(Cursor cursor) {
- // 关闭进度条
- mProgressDialog.dismiss();
- if(cursor == null){
- return;
- }
- while (cursor.moveToNext()) {
- // 获取图片的路径
- String path = cursor.getString(cursor
- .getColumnIndex(MediaStore.Images.Media.DATA));
- //获取图片的添加到系统的毫秒数
- 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();
- //给GridView的item的数据生成HeaderId
- List<GridItem> hasHeaderIdList = generateHeaderId(nonHeaderIdList);
- //排序
- Collections.sort(hasHeaderIdList, new YMDComparator());
- mGridView.setAdapter(new StickyGridAdapter(MainActivity.this, hasHeaderIdList, mGridView));
- }
- });
- }
- /**
- * 对GridView的Item生成HeaderId, 根据图片的添加时间的年、月、日来生成HeaderId
- * 年、月、日相等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;
- }
- @Override
- protected void onDestroy() {
- super.onDestroy();
- //退出页面清除LRUCache中的Bitmap占用的内存
- NativeImageLoader.getInstance().trimMemCache();
- }
- /**
- * 将毫秒数装换成pattern这个格式,我这里是转换成年月日
- * @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));
- }
- }
主界面的代码主要是组装StickyGridHeadersGridView的数据,我们将扫描出来的图片的路径,时间的毫秒数解析成年月日的格式封装到GridItem中,然后将GridItem加入到List中,此时每个Item还没有生成headerId,我们需要调用generateHeaderId(),该方法主要是将同一天加入的系统的图片生成相同的HeaderId,这样子同一天加入的图片就在一个组中,当然你要改成同一个月的图片在一起,修改paserTimeToYMD()方法的第二个参数就行了,当Activity finish之后,我们利用NativeImageLoader.getInstance().trimMemCache()释放内存,当然我们还需要对GridView的数据进行排序,比如说headerId相同的item不连续,headerId相同的item就会生成多个sections(即多个分组),所以我们要利用YMDComparator使得在同一天加入的图片在一起,YMDComparator的代码如下
- package com.example.stickyheadergridview;
- import java.util.Comparator;
- public class YMDComparator implements Comparator<GridItem> {
- @Override
- public int compare(GridItem o1, GridItem o2) {
- return o1.getTime().compareTo(o2.getTime());
- }
- }
接下来我们运行下程序看看效果如何
今天的文章就到这里结束了,感谢大家的观看,上面还有一个类和一些资源文件没有贴出来,大家有兴趣研究下就直接下载项目源码,记住采用LruCache缓存图片的时候,cacheSize不要设置得过大,不然产生OOM的概率就更大些,我利用上面的程序测试显示600多张图片来回滑动,没有产生OOM,有问题不明白的同学可以在下面留言!
android listview多种布局 getViewTypeCount和getItemViewType
一般listview都是一种样式,但如果出现 多种样式 ,类似于这一条item是 文字 下一条是图片 在下一条是文字加图片,也许你会说 那就在那就在一个xml文件里面写三者样式呗。那如果是 一百个呢。。哇。。这维护起来 可是个大工程,我可不想加班。
先上图片看看样子:两种样式 只为了为了说明问题
那么方法来了,使用getViewTypeCount和getItemViewType
getViewTypeCount 方法返回的是 你有几种样式 返回时是 int类型
getItemViewType 返回值 是你某一种样式 的类型 是什么。。返回值也是 int类型 ,意思是 代表 A类型的 是 数字 1.。。就是这意思
两个方法很简单呢 直接上代码,基本没什么特别的,就是加了两个方法而已
package com.org.mmp.Play;
import java.util.List;
import com.org.mmp.R;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;
public class PlayAdapter extends BaseAdapter {
/**
* 标题的item
*/
public static final int ITEM_TITLE = 0;
/**
* 二级菜单的item
*/
public static final int ITEM_INTRODUCE = 1;
private List<ViewItem> mList;
private Context context;
private LayoutInflater inflater;
// 两个样式 两个holder。100就写100holder。。当然你何以把他抽离出来这里先只为了说明问题
class Holder1 {
TextView play_title;
Holder1(View view) {
play_title = (TextView) view.findViewById(R.id.play_title);
}
}
class Holder2 {
TextView play_introduce_title;
ImageView play_iv;
Holder2(View view) {
play_introduce_title = (TextView) view
.findViewById(R.id.play_introduce_title);
play_iv = (ImageView) view.findViewById(R.id.play_iv);
}
}
public PlayAdapter(Context context, List<ViewItem> mList) {
this.context = context;
this.mList = mList;
inflater = LayoutInflater.from(context);
}
@Override
public int getCount() {
// TODO Auto-generated method stub
System.out.println("mList.size()" + mList.size());
return mList.size();
}
@Override
public Object getItem(int arg0) {
// TODO Auto-generated method stub
return mList.get(arg0);
}
//返回 代表某一个样式 的 数值
@Override
public int getItemViewType(int position) {
// TODO Auto-generated method stub
return mList.get(position).type;
}
//两个样式 返回2
@Override
public int getViewTypeCount() {
// TODO Auto-generated method stub
return 2;
}
@Override
public long getItemId(int arg0) {
// TODO Auto-generated method stub
return arg0;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
int type = getItemViewType(position);
Holder1 holder1 = null;
Holder2 holder2 = null;
System.out.println("getView " + position + " " + convertView
+ " type = " + type);
if (convertView == null) {
//选择某一个样式。。
switch (type) {
case ITEM_TITLE:
convertView = inflater.inflate(R.layout.play_item_title, null);
holder1 = new Holder1(convertView);
holder1.play_title.setText(mList.get(position).name);
convertView.setTag(holder1);
break;
case ITEM_INTRODUCE:
convertView = inflater.inflate(R.layout.paly_item_introduce,
null);
holder2 = new Holder2(convertView);
holder2.play_introduce_title
.setText(mList.get(position).address);
convertView.setTag(holder2);
break;
default:
break;
}
} else {
switch (type) {
case ITEM_TITLE:
holder1 = (Holder1) convertView.getTag();
holder1.play_title.setText(mList.get(position).name);
break;
case ITEM_INTRODUCE:
holder2 = (Holder2) convertView.getTag();
holder2.play_introduce_title
.setText(mList.get(position).address);
break;
default:
break;
}
}
return convertView;
}
}
代码位置
点击打开链接