使用:
PinnedHeaderListView+SectionedBaseAdapter
这是一个开源的框架。
这两个java类可以直接复制到自己的项目里使用。
效果图:
public class PinnedHeaderListView extends ListView implements OnScrollListener {
private OnScrollListener mOnScrollListener;
public static interface PinnedSectionedHeaderAdapter {
public boolean isSectionHeader(int position);
public int getSectionForPosition(int position);
public View getSectionHeaderView(int section, View convertView, ViewGroup parent);
public int getSectionHeaderViewType(int section);
public int getCount();
}
private PinnedSectionedHeaderAdapter mAdapter;
private View mCurrentHeader;
private int mCurrentHeaderViewType = 0;
private float mHeaderOffset;
private boolean mShouldPin = true;
private int mCurrentSection = 0;
private int mWidthMode;
private int mHeightMode;
public PinnedHeaderListView(Context context) {
super(context);
super.setOnScrollListener(this);
}
public PinnedHeaderListView(Context context, AttributeSet attrs) {
super(context, attrs);
super.setOnScrollListener(this);
}
public PinnedHeaderListView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
super.setOnScrollListener(this);
}
public void setPinHeaders(boolean shouldPin) {
mShouldPin = shouldPin;
}
@Override
public void setAdapter(ListAdapter adapter) {
mCurrentHeader = null;
mAdapter = (PinnedSectionedHeaderAdapter) adapter;
super.setAdapter(adapter);
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if (mOnScrollListener != null) {
mOnScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount);
}
if (mAdapter == null || mAdapter.getCount() == 0 || !mShouldPin || (firstVisibleItem < getHeaderViewsCount())) {
mCurrentHeader = null;
mHeaderOffset = 0.0f;
for (int i = firstVisibleItem; i < firstVisibleItem + visibleItemCount; i++) {
View header = getChildAt(i);
if (header != null) {
header.setVisibility(VISIBLE);
}
}
return;
}
firstVisibleItem -= getHeaderViewsCount();
int section = mAdapter.getSectionForPosition(firstVisibleItem);
int viewType = mAdapter.getSectionHeaderViewType(section);
mCurrentHeader = getSectionHeaderView(section, mCurrentHeaderViewType != viewType ? null : mCurrentHeader);
ensurePinnedHeaderLayout(mCurrentHeader);
mCurrentHeaderViewType = viewType;
mHeaderOffset = 0.0f;
for (int i = firstVisibleItem; i < firstVisibleItem + visibleItemCount; i++) {
if (mAdapter.isSectionHeader(i)) {
View header = getChildAt(i - firstVisibleItem);
float headerTop = header.getTop();
float pinnedHeaderHeight = mCurrentHeader.getMeasuredHeight();
header.setVisibility(VISIBLE);
if (pinnedHeaderHeight >= headerTop && headerTop > 0) {
mHeaderOffset = headerTop - header.getHeight();
} else if (headerTop <= 0) {
header.setVisibility(INVISIBLE);
}
}
}
invalidate();
}
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
if (mOnScrollListener != null) {
mOnScrollListener.onScrollStateChanged(view, scrollState);
}
}
private View getSectionHeaderView(int section, View oldView) {
boolean shouldLayout = section != mCurrentSection || oldView == null;
View view = mAdapter.getSectionHeaderView(section, oldView, this);
if (shouldLayout) {
// a new section, thus a new header. We should lay it out again
ensurePinnedHeaderLayout(view);
mCurrentSection = section;
}
return view;
}
private void ensurePinnedHeaderLayout(View header) {
if (header.isLayoutRequested()) {
int widthSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(), mWidthMode);
int heightSpec;
ViewGroup.LayoutParams layoutParams = header.getLayoutParams();
if (layoutParams != null && layoutParams.height > 0) {
heightSpec = MeasureSpec.makeMeasureSpec(layoutParams.height, MeasureSpec.EXACTLY);
} else {
heightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
}
header.measure(widthSpec, heightSpec);
header.layout(0, 0, header.getMeasuredWidth(), header.getMeasuredHeight());
}
}
@Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
if (mAdapter == null || !mShouldPin || mCurrentHeader == null)
return;
int saveCount = canvas.save();
canvas.translate(0, mHeaderOffset);
canvas.clipRect(0, 0, getWidth(), mCurrentHeader.getMeasuredHeight()); // needed
// for
// <
// HONEYCOMB
mCurrentHeader.draw(canvas);
canvas.restoreToCount(saveCount);
}
@Override
public void setOnScrollListener(OnScrollListener l) {
mOnScrollListener = l;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mWidthMode = MeasureSpec.getMode(widthMeasureSpec);
mHeightMode = MeasureSpec.getMode(heightMeasureSpec);
}
public void setOnItemClickListener(OnItemClickListener listener) {
super.setOnItemClickListener(listener);
}
public static abstract class OnItemClickListener implements AdapterView.OnItemClickListener {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int rawPosition, long id) {
SectionedBaseAdapter adapter;
if (adapterView.getAdapter().getClass().equals(HeaderViewListAdapter.class)) {
HeaderViewListAdapter wrapperAdapter = (HeaderViewListAdapter) adapterView.getAdapter();
adapter = (SectionedBaseAdapter) wrapperAdapter.getWrappedAdapter();
} else {
adapter = (SectionedBaseAdapter) adapterView.getAdapter();
}
int section = adapter.getSectionForPosition(rawPosition);
int position = adapter.getPositionInSectionForPosition(rawPosition);
if (position == -1) {
onSectionClick(adapterView, view, section, id);
} else {
onItemClick(adapterView, view, section, position, id);
}
}
public abstract void onItemClick(AdapterView<?> adapterView, View view, int section, int position, long id);
public abstract void onSectionClick(AdapterView<?> adapterView, View view, int section, long id);
}
}
public abstract class SectionedBaseAdapter extends BaseAdapter implements PinnedHeaderListView.PinnedSectionedHeaderAdapter {
private static int HEADER_VIEW_TYPE = 0;
private static int ITEM_VIEW_TYPE = 0;
/** * Holds the calculated values of @{link getPositionInSectionForPosition} */
private SparseArray<Integer> mSectionPositionCache;
/** * Holds the calculated values of @{link getSectionForPosition} */
private SparseArray<Integer> mSectionCache;
/** * Holds the calculated values of @{link getCountForSection} */
private SparseArray<Integer> mSectionCountCache;
/** * Caches the item count */
private int mCount;
/** * Caches the section count */
private int mSectionCount;
public SectionedBaseAdapter() {
super();
mSectionCache = new SparseArray<Integer>();
mSectionPositionCache = new SparseArray<Integer>();
mSectionCountCache = new SparseArray<Integer>();
mCount = -1;
mSectionCount = -1;
}
@Override
public void notifyDataSetChanged() {
mSectionCache.clear();
mSectionPositionCache.clear();
mSectionCountCache.clear();
mCount = -1;
mSectionCount = -1;
super.notifyDataSetChanged();
}
@Override
public void notifyDataSetInvalidated() {
mSectionCache.clear();
mSectionPositionCache.clear();
mSectionCountCache.clear();
mCount = -1;
mSectionCount = -1;
super.notifyDataSetInvalidated();
}
@Override
public final int getCount() {
if (mCount >= 0) {
return mCount;
}
int count = 0;
for (int i = 0; i < internalGetSectionCount(); i++) {
count += internalGetCountForSection(i);
count++; // for the header view
}
mCount = count;
return count;
}
@Override
public final Object getItem(int position) {
return getItem(getSectionForPosition(position), getPositionInSectionForPosition(position));
}
@Override
public final long getItemId(int position) {
return getItemId(getSectionForPosition(position), getPositionInSectionForPosition(position));
}
@Override
public final View getView(int position, View convertView, ViewGroup parent) {
if (isSectionHeader(position)) {
return getSectionHeaderView(getSectionForPosition(position), convertView, parent);
}
return getItemView(getSectionForPosition(position), getPositionInSectionForPosition(position), convertView, parent);
}
@Override
public final int getItemViewType(int position) {
if (isSectionHeader(position)) {
return getItemViewTypeCount() + getSectionHeaderViewType(getSectionForPosition(position));
}
return getItemViewType(getSectionForPosition(position), getPositionInSectionForPosition(position));
}
@Override
public final int getViewTypeCount() {
return getItemViewTypeCount() + getSectionHeaderViewTypeCount();
}
public final int getSectionForPosition(int position) {
// first try to retrieve values from cache
Integer cachedSection = mSectionCache.get(position);
if (cachedSection != null) {
return cachedSection;
}
int sectionStart = 0;
for (int i = 0; i < internalGetSectionCount(); i++) {
int sectionCount = internalGetCountForSection(i);
int sectionEnd = sectionStart + sectionCount + 1;
if (position >= sectionStart && position < sectionEnd) {
mSectionCache.put(position, i);
return i;
}
sectionStart = sectionEnd;
}
return 0;
}
public int getPositionInSectionForPosition(int position) {
// first try to retrieve values from cache
Integer cachedPosition = mSectionPositionCache.get(position);
if (cachedPosition != null) {
return cachedPosition;
}
int sectionStart = 0;
for (int i = 0; i < internalGetSectionCount(); i++) {
int sectionCount = internalGetCountForSection(i);
int sectionEnd = sectionStart + sectionCount + 1;
if (position >= sectionStart && position < sectionEnd) {
int positionInSection = position - sectionStart - 1;
mSectionPositionCache.put(position, positionInSection);
return positionInSection;
}
sectionStart = sectionEnd;
}
return 0;
}
public final boolean isSectionHeader(int position) {
int sectionStart = 0;
for (int i = 0; i < internalGetSectionCount(); i++) {
if (position == sectionStart) {
return true;
} else if (position < sectionStart) {
return false;
}
sectionStart += internalGetCountForSection(i) + 1;
}
return false;
}
public int getItemViewType(int section, int position) {
return ITEM_VIEW_TYPE;
}
public int getItemViewTypeCount() {
return 1;
}
public int getSectionHeaderViewType(int section) {
return HEADER_VIEW_TYPE;
}
public int getSectionHeaderViewTypeCount() {
return 1;
}
public abstract Object getItem(int section, int position);
public abstract long getItemId(int section, int position);
public abstract int getSectionCount();
public abstract int getCountForSection(int section);
public abstract View getItemView(int section, int position, View convertView, ViewGroup parent);
public abstract View getSectionHeaderView(int section, View convertView, ViewGroup parent);
private int internalGetCountForSection(int section) {
Integer cachedSectionCount = mSectionCountCache.get(section);
if (cachedSectionCount != null) {
return cachedSectionCount;
}
int sectionCount = getCountForSection(section);
mSectionCountCache.put(section, sectionCount);
return sectionCount;
}
private int internalGetSectionCount() {
if (mSectionCount >= 0) {
return mSectionCount;
}
mSectionCount = getSectionCount();
return mSectionCount;
}
}
MainActivity.java
public class MainActivity extends AppCompatActivity {
private PinnedHeaderListView listView;
//声明三个list,存放不同header下的item
private List<itemBean> itemsforheader1;
private List<itemBean> itemsforheader2;
private List<itemBean> itemsforheader3;
//声明一个List存放每个itemlist的Key值
private List<Integer> tags;
//使用map,根据每个header存放不同的itemlist
private Map<Integer, List<itemBean>> mMap;
//每个header下面都有一个自己的itemlist,所以我们使用map来存放这些itemlist
//map是以键值对的形式存储数据的,所以每个itemlist我们都需要一个对应的key
//这个key值,我们使用tags来存储
private LHDAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//初始化
listView = (PinnedHeaderListView) findViewById(R.id.mylistview);
//实例化Items数据源
initdata();
//实例化适配器
adapter = new LHDAdapter(this, tags, mMap);
//设置适配器
listView.setAdapter(adapter);
}
//实例化了15个item数据
private void initdata() {
tags = new ArrayList<Integer>();
itemsforheader1 = new ArrayList<itemBean>();
itemsforheader2 = new ArrayList<itemBean>();
itemsforheader3 = new ArrayList<itemBean>();
mMap = new HashMap<Integer, List<itemBean>>();
for (int i = 0; i < 3; i++) {
tags.add(i);
}
for (int i = 0; i < 2; i++) {
itemsforheader1.add(new itemBean("我是一个粉刷匠" + i));
}
for (int i = 0; i < 5; i++) {
itemsforheader2.add(new itemBean("我是一个铁匠" + i));
}
for (int i = 0; i < 15; i++) {
itemsforheader3.add(new itemBean("我是一个木匠" + i));
}
//将这谢数据存放在3个header里
mMap.put(tags.get(0), itemsforheader1);
mMap.put(tags.get(1), itemsforheader2);
mMap.put(tags.get(2), itemsforheader3);
}
}
/** * Created by LHD on 2016/7/9. */
public class itemBean {
private String itemname;
public itemBean(String itemname) {
this.itemname = itemname;
}
public String getItemname() {
return itemname;
}
public void setItemname(String itemname) {
this.itemname = itemname;
}
}
LHDAdapter.java
/** * Created by LHD on 2016/7/9. */
public class LHDAdapter extends SectionedBaseAdapter {
private Context mcontext;
private Map<Integer, List<itemBean>> mMap;
private List<Integer> mtags;
private LayoutInflater inflater;
public LHDAdapter(Context mcontext, List<Integer> tags, Map<Integer, List<itemBean>> maps) {
this.mcontext = mcontext;
this.mtags = tags;
this.mMap = maps;
inflater = LayoutInflater.from(mcontext);
}
@Override
public Object getItem(int section, int position) {
//返回对应header下的item的position
Log.i("LHD", "getItem->section : " + section);
//根据header的位置拿出对应的key,section的值就是header的位置
//根据key拿出对应的itemlist然后根据position拿出对应的item
return mMap.get(mtags.get(section)).get(position);
}
@Override
public long getItemId(int section, int position) {
Log.i("LHD", "getItemId: " + section + " position:" + position);
return 0;
}
//这个返回header的数量,我们一共有3个header所以是3
@Override
public int getSectionCount() {
Log.i("LHD", "getSectionCount: " + mtags.size());
return mtags.size();
}
//这个返回每个header的子item的数量
@Override
public int getCountForSection(int section) {
Log.i("LHD", "getCountForSection: " + mMap.get(section).size());
return mMap.get(mtags.get(section)).size();
}
@Override
public View getItemView(final int section, final int position, View convertView, ViewGroup parent) {
//初始化每个holder
ViewHolder vh = null;
if (convertView == null) {
vh = new ViewHolder();
convertView = inflater.inflate(R.layout.itemview, null);
vh.textView = (TextView) convertView.findViewById(R.id.tv_item);
vh.fm = (FrameLayout) convertView.findViewById(R.id.f2);
convertView.setTag(vh);
} else {
vh = (ViewHolder) convertView.getTag();
}
vh.textView.setText(mMap.get(section).get(position).getItemname());
if (section == 1 && mMap.get(1).size() > 0) {
//position>0就说明header2里的item不为空
if (position == mMap.get(section).size() - 1) {
Log.i("LHD", "section==" + section);
vh.fm.setVisibility(View.VISIBLE);
} else {
vh.fm.setVisibility(View.GONE);
}
}
convertView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(mcontext, "点击了header" + section + "的第" + position + "个item", Toast.LENGTH_SHORT).show();
}
});
return convertView;
}
//本来我在这个函数里根据不同的section加载不同的header布局
//效果是可以实现,但是在向下滑动的时候就会出现bug,没法实现header2顶替header1
//这个Bug是和源码有关的,而且在header滑动到顶部以后就不支持点击事件了
@Override
public View getSectionHeaderView(final int section, View convertView, ViewGroup parent) {
RelativeLayout view = null;
if (convertView == null) {
view = (RelativeLayout) inflater.inflate(R.layout.header, null);
} else {
view = (RelativeLayout) convertView;
}
//有数据的时候才显示对应的headerview
TextView textview = (TextView) view.findViewById(R.id.header_title);
if (mMap.get(section).size() > 0) {
view.setVisibility(View.VISIBLE);
textview.setText("标题" + mtags.get(section));
} else {
view.setVisibility(View.GONE);
}
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(mcontext, "点击了header" + section, Toast.LENGTH_SHORT).show();
}
});
return view;
}
//item的viewholder
private class ViewHolder {
private TextView textView;
private FrameLayout fm;
}
}
代码注释的很详细啦
代码下载:
listview分组 - 下载频道 - CSDN.NET
http://download.csdn.net/detail/baidu_31093133/9571748