【原创】实现类似手机QQ的可折叠固定标题列表
在上篇文章中,简单的模拟了手机QQ列表的列表功能,实现了一级标题的固定功能,但是一级标题靠近时往上推到细节并没有实现。偶然发现android系统自带到联系人列表就是可以固定标题头到,并且实现了往上推动的效果,于是参考android源代码(PinnedHeaderListView)的实现,让ExpandableListView也有类似功能。
效果如下:
实现到原理类似于之前到文章:
/** * @author douzifly * * @Date 2011-9-13 */ /** * 可固定标题的ExpandableListView * * @author douzifly * @date 2011-9-13 */ public class PinnedExpandableListView extends ExpandableListView implements OnScrollListener { /** * 该ListView的Adapter必须实现该接口 * * @author LiuXiaoyuan@hh.com.cn * @date 2011-9-13 */ public interface PinnedExpandableListViewAdapter { /** * 固定标题状态:不可见 */ 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; public int getPinnedHeaderState(int groupPosition, int childPosition); public void configurePinnedHeader(View header, int groupPosition, int childPosition, int alpha); } private static final int MAX_ALPHA = 255; private PinnedExpandableListViewAdapter mAdapter; private View mHeaderView; private boolean mHeaderVisible; private int mHeaderViewWidth; private int mHeaderViewHeight; private OnClickListener mPinnedHeaderClickLisenter; public void setOnPinnedHeaderClickLisenter(OnClickListener listener) { mPinnedHeaderClickLisenter = listener; } public PinnedExpandableListView(Context context, AttributeSet attrs) { super(context, attrs); setOnScrollListener(this); } public void setPinnedHeaderView(View view) { mHeaderView = view; if (mHeaderView != null) { setFadingEdgeLength(0); } requestLayout(); } @Override public void setAdapter(ExpandableListAdapter adapter) { super.setAdapter(adapter); mAdapter = (PinnedExpandableListViewAdapter) adapter; } @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 l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); final long flatPostion = getExpandableListPosition(getFirstVisiblePosition()); final int groupPos = ExpandableListView.getPackedPositionGroup(flatPostion); final int childPos = ExpandableListView.getPackedPositionChild(flatPostion); int state = mAdapter.getPinnedHeaderState(groupPos, childPos); //只有在状态改变时才layout,这点相当重要,不然可能导致视图不断的刷新 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) { return; } final int state = mAdapter.getPinnedHeaderState(groupPosition, childPosition); switch (state) { case PinnedExpandableListViewAdapter.PINNED_HEADER_GONE: { mHeaderVisible = false; break; } case PinnedExpandableListViewAdapter.PINNED_HEADER_VISIBLE: { mAdapter.configurePinnedHeader(mHeaderView, groupPosition, childPosition, MAX_ALPHA); if (mHeaderView.getTop() != 0) { mHeaderView.layout(0, 0, mHeaderViewWidth, mHeaderViewHeight); } mHeaderVisible = true; break; } case PinnedExpandableListViewAdapter.PINNED_HEADER_PUSHED_UP: { final View firstView = getChildAt(0); if (firstView == null) { break; } 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.configurePinnedHeader(mHeaderView, groupPosition, childPosition, alpha); if (mHeaderView.getTop() != y) { mHeaderView.layout(0, y, mHeaderViewWidth, mHeaderViewHeight + y); } mHeaderVisible = true; break; } default: break; } } @Override protected void dispatchDraw(Canvas canvas) { super.dispatchDraw(canvas); //由于HeaderView并没有添加到ExpandableListView的子控件中,所以要draw他 if (mHeaderVisible) { drawChild(canvas, mHeaderView, getDrawingTime()); } } private float mDownX; private float mDownY; private static final float FINGER_WIDTH = 20; @Override public boolean onTouchEvent(MotionEvent ev) { if (mHeaderVisible) { 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(); float offsetX = Math.abs(x - mDownX); float offsetY = Math.abs(y - mDownY); // 如果在固定标题内点击了,那么触发事件 if (x <= mHeaderViewWidth && y <= mHeaderViewHeight && offsetX <= FINGER_WIDTH && offsetY <= FINGER_WIDTH) { if (mPinnedHeaderClickLisenter != null) { mPinnedHeaderClickLisenter.onClick(mHeaderView); } return true; } break; default: break; } } return super.onTouchEvent(ev); } @Override public void onScrollStateChanged(AbsListView view, int scrollState) { } @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); Log.d("TEST", "configure in onScroll"); configureHeaderView(groupPosition, childPosition); } }
一般情况下,通过继承该类来使用,列表标题当前的状态(可见,不可见,正在推动)由Adapter来决定,所以Adapter里面的代码就相当重要了。以下是我的一个实现:
@Override public int getPinnedHeaderState(int groupPosition, int childPosition) { final int childCount = getChildrenCount(groupPosition); if(childPosition == childCount - 1){ return PINNED_HEADER_PUSHED_UP; }else if(childPosition == -1 && !BlockListView.this.isGroupExpanded(groupPosition)){ return PINNED_HEADER_GONE; }else { return PINNED_HEADER_VISIBLE; } } @Override public void configurePinnedHeader(View header, int groupPosition, int childPosition, int alpha) { TextView pinned = (TextView) header; pinned.setText((String)getGroup(groupPosition)); }
当然需要在子类初始化的地方加上对HeaderView的配置
txtPinned = new TextView(context); txtPinned.setTextSize(mGroupTextSize); txtPinned.setBackgroundResource(mGroupBgDrawableId); AbsListView.LayoutParams lp = new AbsListView.LayoutParams(-1, -2); txtPinned.setLayoutParams(lp); txtPinned.setTextColor(Color.WHITE); setPinnedHeaderView(txtPinned); setOnPinnedHeaderClickLisenter(new OnClickListener() { @Override public void onClick(View v) { final long flatPos = getExpandableListPosition(getFirstVisiblePosition()); final int groupPos = ExpandableListView.getPackedPositionGroup(flatPos); collapseGroup(groupPos); } });
这样,上图中的效果就实现了。
需要注意的就是HeaderView并没有加入到ExpandableListView的子控件中,所以要重写dispatchDraw函数,并在里面绘制HeaderView,这样导致一个问题就是直接在HeaderView上面设置的click监听函数无效,所以需要重写onTouchEvent来模拟在HeaderView上的OnClick事件。
转载请注明出处。