最近项目中遇到了可折叠ListView,但是要在滑动过程中有固定Title,方便用户可以点击全选或者收起展开的List的操作。
自定义ListView:
/**
* @author fanlitao
*/
public class PinnedHeaderExpandableListView extends ExpandableListView implements OnScrollListener,
OnGroupClickListener {
public static final int GROUP_COLLAPSE_STATUS = 0;
public static final int GROUP_EXPAND_STATUS = 1;
public PinnedHeaderExpandableListView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
registerListener();
}
public PinnedHeaderExpandableListView(Context context, AttributeSet attrs) {
super(context, attrs);
registerListener();
}
public PinnedHeaderExpandableListView(Context context) {
super(context);
registerListener();
}
public interface PinnedSectionedHeaderAdapter {
public static final int PINNED_HEADER_GONE = 0;
public static final int PINNED_HEADER_VISIBLE = 1;
public static final int PINNED_HEADER_PUSHED_UP = 2;
int getSectionHeaderState(int groupPosition, int childPosition);
void configureSectionHeader(View header, int groupPosition, int childPosition, int alpha);
void setGroupClickStatus(int groupPosition, int status);
int getGroupClickStatus(int groupPosition);
void onSectionHeaderCheckBoxClick(View header, int groupPosition);
}
private static final int MAX_ALPHA = 255;
private PinnedSectionedHeaderAdapter mAdapter;
private View mHeaderView;
private boolean mHeaderViewVisible;
private int mHeaderViewWidth;
private int mHeaderViewHeight;
private boolean mGroupEnable = true;
public void setSelectionHeaderView(View view) {
mHeaderView = view;
AbsListView.LayoutParams lp = new AbsListView.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
view.setLayoutParams(lp);
if (mHeaderView != null) {
setFadingEdgeLength(0);
}
requestLayout();
}
private void registerListener() {
setOnScrollListener(this);
setOnGroupClickListener(this);
}
private void headerViewClick() {
long packedPosition = getExpandableListPosition(getFirstVisiblePosition());
int groupPosition = ExpandableListView.getPackedPositionGroup(packedPosition);
if (mAdapter.getGroupClickStatus(groupPosition) == GROUP_EXPAND_STATUS) {
collapseGroup(groupPosition);
mAdapter.setGroupClickStatus(groupPosition, GROUP_COLLAPSE_STATUS);
}
else {
expandGroup(groupPosition);
mAdapter.setGroupClickStatus(groupPosition, GROUP_EXPAND_STATUS);
}
setSelectedGroup(groupPosition);
}
private float mDownX;
private float mDownY;
@Override
public boolean onTouchEvent(MotionEvent ev) {
if (mHeaderViewVisible) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
mDownX = ev.getX();
mDownY = ev.getY();
if (mDownX <= mHeaderViewWidth && mDownY <= mHeaderViewHeight) {
return true;
}
break;
case MotionEvent.ACTION_UP:
float x = ev.getX();
float y = ev.getY();
int checkbox = OptimUtils.dip2px(getContext(), 100);
float offsetX = Math.abs(x - mDownX);
float offsetY = Math.abs(y - mDownY);
if ((x <= mHeaderViewWidth - checkbox) && y <= mHeaderViewHeight
&& offsetX <= mHeaderViewWidth && offsetY <= mHeaderViewHeight) {
if (mHeaderView != null) {
headerViewClick();
}
return true;
}
if ((x > mHeaderViewWidth - checkbox) && y <= mHeaderViewHeight
&& (offsetX < checkbox) && offsetY <= mHeaderViewHeight) {
if (mHeaderView != null) {
long packedPosition = getExpandableListPosition(this
.getFirstVisiblePosition());
int groupPosition = ExpandableListView
.getPackedPositionGroup(packedPosition);
mAdapter.onSectionHeaderCheckBoxClick(mHeaderView, groupPosition);
}
return true;
}
break;
default:
break;
}
}
return super.onTouchEvent(ev);
}
@Override
public void setAdapter(ExpandableListAdapter adapter) {
super.setAdapter(adapter);
mAdapter = (PinnedSectionedHeaderAdapter) adapter;
}
@Override
public boolean onGroupClick(ExpandableListView parent, View v, int groupPosition, long id) {
if (mGroupEnable) {
if (mAdapter.getGroupClickStatus(groupPosition) == GROUP_COLLAPSE_STATUS) {
mAdapter.setGroupClickStatus(groupPosition, GROUP_EXPAND_STATUS);
parent.expandGroup(groupPosition);
parent.setSelectedGroup(groupPosition);
} else if (mAdapter.getGroupClickStatus(groupPosition) == GROUP_EXPAND_STATUS) {
mAdapter.setGroupClickStatus(groupPosition, GROUP_COLLAPSE_STATUS);
parent.collapseGroup(groupPosition);
}
}
return true;
}
public void setGroupClickEnable(boolean state) {
mGroupEnable = state;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (mHeaderView != null) {
measureChild(mHeaderView, widthMeasureSpec, heightMeasureSpec);
mHeaderViewWidth = mHeaderView.getMeasuredWidth();
mHeaderViewHeight = mHeaderView.getMeasuredHeight();
}
}
private int mOldState = -1;
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
final long flatPostion = getExpandableListPosition(getFirstVisiblePosition());
final int groupPos = ExpandableListView.getPackedPositionGroup(flatPostion);
final int childPos = ExpandableListView.getPackedPositionChild(flatPostion);
if (mAdapter != null) {
int state = mAdapter.getSectionHeaderState(groupPos, childPos);
if (mHeaderView != null && mAdapter != null && state != mOldState) {
mOldState = state;
mHeaderView.layout(0, 0, mHeaderViewWidth, mHeaderViewHeight);
}
}
configureHeaderView(groupPos, childPos);
}
public void configureHeaderView(int groupPosition, int childPosition) {
if (mHeaderView == null || mAdapter == null
|| ((ExpandableListAdapter) mAdapter).getGroupCount() == 0) {
return;
}
int state = mAdapter.getSectionHeaderState(groupPosition, childPosition);
switch (state) {
case PinnedSectionedHeaderAdapter.PINNED_HEADER_GONE: {
mHeaderViewVisible = false;
break;
}
case PinnedSectionedHeaderAdapter.PINNED_HEADER_VISIBLE: {
mAdapter.configureSectionHeader(mHeaderView, groupPosition, childPosition,
MAX_ALPHA);
if (mHeaderView.getTop() != 0) {
mHeaderView.layout(0, 0, mHeaderViewWidth, mHeaderViewHeight);
}
mHeaderViewVisible = true;
break;
}
case PinnedSectionedHeaderAdapter.PINNED_HEADER_PUSHED_UP: {
View firstView = getChildAt(0);
int bottom = firstView.getBottom();
int headerHeight = mHeaderView.getHeight();
int y;
int alpha;
if (bottom < headerHeight) {
y = (bottom - headerHeight);
alpha = MAX_ALPHA * (headerHeight + y) / headerHeight;
} else {
y = 0;
alpha = MAX_ALPHA;
}
mAdapter.configureSectionHeader(mHeaderView, groupPosition, childPosition, alpha);
if (mHeaderView.getTop() != y) {
mHeaderView.layout(0, y, mHeaderViewWidth, mHeaderViewHeight + y);
}
mHeaderViewVisible = true;
break;
}
}
}
@Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
if (mHeaderViewVisible) {
drawChild(canvas, mHeaderView, getDrawingTime());
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
int totalItemCount) {
final long flatPos = getExpandableListPosition(firstVisibleItem);
int groupPosition = ExpandableListView.getPackedPositionGroup(flatPos);
int childPosition = ExpandableListView.getPackedPositionChild(flatPos);
configureHeaderView(groupPosition, childPosition);
}
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
}
需要在这个ListView的Adapter中,实现SpinnedHeaderExpandableListView定义的接口。
以下是Adapter中的部分实现
@Override
public int getSectionHeaderState(int groupPosition, int childPosition) {
final int childCount = getChildrenCount(groupPosition);
if (childPosition == childCount - 1) {
return PINNED_HEADER_PUSHED_UP;
}
else if (childPosition == -1 && !mListView.isGroupExpanded(groupPosition)) {
return PINNED_HEADER_GONE;
}
else {
return PINNED_HEADER_VISIBLE;
}
}
@Override
public void configureSectionHeader(View header, int groupPosition, int childPosition, int alpha) {
TextView headerView = (TextView) header.findViewById(R.id.header_type);
ImageView headerIcon = (ImageView) header.findViewById(R.id.junk_icon_header);
GroupBean bean = (GroupBean) getGroup(groupPosition);
headerView.setText(bean.name);
headerIcon.setImageResource(bean.icon);
final ImageView checkView = (ImageView) header.findViewById(R.id.checkbox);
ImageView expandView = (ImageView) header.findViewById(R.id.expand_icon);
expandView.setSelected(bean.isExpand);
int size = mChilds.get(groupPosition).size();
if (size == 0) {
expandView.setVisibility(View.INVISIBLE);
checkView.setVisibility(View.INVISIBLE);
} else {
checkView.setVisibility(View.VISIBLE);
expandView.setVisibility(View.VISIBLE);
SparseArray<AppBean> set = mSelectSet.get(groupPosition);
if (mIsScanFinished) {
checkView.setSelected(set.size() == size);
} else {
checkView.setSelected(mDefaultCheckState);
}
}
}
private HashMap<Integer, Integer> groupStatusMap = new HashMap<Integer, Integer>();
@Override
public void setGroupClickStatus(int groupPosition, int status) {
groupStatusMap.put(groupPosition, status);
}
@Override
public int getGroupClickStatus(int groupPosition) {
if (groupStatusMap.containsKey(groupPosition)) {
return groupStatusMap.get(groupPosition);
}
else {
return 0;
}
}
这样我们就实现了仿QQ的ExpandableListView的效果。虽然还有一些问题,但是大体样子就该是这样。