【原创】实现类似手机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事件。  


   

转载请注明出处。
     

 

  

posted @ 2011-09-13 19:18  douzifly  阅读(4598)  评论(3编辑  收藏  举报