2011年9月13日

【原创】实现类似手机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 阅读(1725) 评论(1) 编辑

2011年8月31日

简单几句代码模仿手机QQ列表

手机QQ的可折叠列表在分组往上滑动时是可以停靠在最上方的,android默认的列表并没有提供相关的功能,其实可以简单的实现下,就是放一个View在最上面,在合适的时候显示出来就行了。

public class ExExpandableListView extends FrameLayout implements
        OnScrollListener, OnClickListener {

    private ExpandableListView mList;

    public ExExpandableListView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mList = new ExpandableListView(context, null);
        FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(-1, -1);
        mList.setLayoutParams(lp);
        mFixedHeader = new TextView(context);
        FrameLayout.LayoutParams lp1 = new FrameLayout.LayoutParams(-1,
                LayoutParams.WRAP_CONTENT);
        mFixedHeader.setLayoutParams(lp1);
        addView(mList);
        addView(mFixedHeader);

        mList.setOnScrollListener(this);
        mFixedHeader.setOnClickListener(this);
        
        mList.setFadingEdgeLength(0);
    }
    

    TextView mFixedHeader;
    

    public ExpandableListView getList() {
        return mList;
    }

    private int mCurrentGroup = -1;

    @Override
    public void onScroll(AbsListView view, int firstVisibleItem,
            int visibleItemCount, int totalItemCount) {

        if (mList == null || mList.getChildCount() <= 0) {
            return;
        }

        final long flatPos = mList.getExpandableListPosition(firstVisibleItem);
        final int groupPos = ExpandableListView.getPackedPositionGroup(flatPos);
        
        if(groupPos != mCurrentGroup){
            //只有在组改变时才去设置文字
            String text = mList.getExpandableListAdapter().getGroup(groupPos)
                    .toString();
            mFixedHeader.setText(text);
        }
        
        mCurrentGroup = groupPos;
        
        if (mList.isGroupExpanded(groupPos)) {
            if(mFixedHeader.getVisibility() != View.VISIBLE){
                mFixedHeader.setVisibility(View.VISIBLE);
            }
        } else {
            if(mFixedHeader.getVisibility() != View.GONE){
                mFixedHeader.setVisibility(View.GONE);
            }
        }
    }

    @Override
    public void onClick(View v) {
        if (v == mFixedHeader) {
            mFixedHeader.setVisibility(View.GONE);
            mList.collapseGroup(mCurrentGroup);
            mList.setSelectedGroup(mCurrentGroup);
        }
    }

}

只是简单的使用一个TextView去显示组的文字,还有很多细节没有考虑,比如说QQ的列表是当下面的组靠近停靠的组时是把停靠的那个视图推上去的。

posted @ 2011-08-31 23:09 douzifly 阅读(346) 评论(0) 编辑

2011年3月25日

卑鄙的搜狗输入法

我不明白为什么国软总喜欢用这样的招数,话说搜狗也算是输入法的领头军了。最近兴致勃勃的装了IE9,当我在地址栏输入关键字搜索的时候,却总是自动跳转到搜狗的搜索页面。我到IE的搜索提供程序管理界面却发现没有搜过相关的。而且我也设置了google为默认搜索提供程序。还是不凑效。难道,我的地址栏就这么被搜狗给“劫持”了?卸载搜狗,要我重启,选择暂不重启,再次使用IE地址栏搜索,仍然跳转到搜狗输入页面。重启电脑,一切恢复。其实你跳转到搜狗搜索就算了,恼人的是,你连屁都搜索不出来一个。你让我情何以堪。

posted @ 2011-03-25 12:23 douzifly 阅读(155) 评论(1) 编辑

2011年3月9日

在onTouchEvent中处理任意时间的长按事件

     android提供了GestureDetector类来处理一些常用的手势操作,比如说 onLongPress,onFling 等。但这里不使用GestureDetector,而是直接在自定义View重写的onTouchEvent中进行处理。

      欲实现的效果是:当手机按住屏幕时,如果在指定的时间内没有移动(如500毫秒),那么进入长按模式,此时手指在屏幕上移动都算作长按模式。如果手机按住屏幕就立马移动,那么就算作移动模式。

      MotionEvent 类提供了记录当前坐标的函数(getX(),getY())和当前事件产生的时间的函数(getEventTime())以及按下时间(getDowntime())。MotionEvent同时也提供了当前的操作类型,按下(ACTION_DOWN)、 移动 (ACTION_MOVE)、弹起 (ACTION_UP)。有了这些参数,我们便可以轻易的实现想要的效果了。

      大概思路如下:在按下时记录x,y坐标以及按下时间,当第一次移动的时候获取移动的时间,如果大于指定的长按时间,那么进入长按模式,否则就是普通的移动模式。很容易,在模拟器里面实现了这个效果,但是当在真机里面运行时,却无法实现这样的效果。原来模拟器点击的时候能够保证在不移动鼠标的情况下不触发ACTION_MOVE,但是真机却很敏感,几乎在ACTION_DOWN后的几毫秒之后就立马不停的ACTION_MOVE了。想了一下,其实只要稍微变通下变可以在真机上也实现相同的效果了。那就是判断ACTION_MOVE后的坐标和ACTION_DOWN的坐标的偏移值是否小于我们指定的偏移像素,如果在指定值内,那么认为没有移动。于是有了如下这个函数。

/**
   * 判断是否有长按动作发生
   * @param lastX 按下时X坐标
   * @param lastY 按下时Y坐标
   * @param thisX 移动时X坐标
   * @param thisY 移动时Y坐标
   * @param lastDownTime 按下时间
   * @param thisEventTime 移动时间
   * @param longPressTime 判断长按时间的阀值
   */
  private boolean isLongPressed(float lastX,float lastY,
                   float thisX,float thisY,
                   long lastDownTime,long thisEventTime,
                   long longPressTime){
    float offsetX = Math.abs(thisX - lastX);
    float offsetY = Math.abs(thisY - lastY);
    long intervalTime = thisEventTime - lastDownTime;
    if(offsetX <=10 && offsetY<=10 && intervalTime >= longPressTime){
      return true;
    }
    return false;
  }
   在ACTION_DOWN的时候,记录下lastX,lastY和lastDownTime,在ACTION_MOVE的时候判断当前是否为长按模式(类标志变量的方式),如果不是,那么获取当前的thisX,thisY和thisEventTime调用函数进行判断。最后别忘记在ACTION_UP里将长按标志值为FALSE。ACTION_DOWN里面这样处理:
//检测是否长按,在非长按时检测
if(!mIsLongPressed){
    mIsLongPressed = isLongPressed(mLastMotionX, mLastMotionY, x, y, lastDownTime,eventTime,500);
}
if(mIsLongPressed){
   //长按模式所做的事
}else{
   //移动模式所做的事
}

转载请注明出处,douzifly@gmail.com版权所有。

posted @ 2011-03-09 17:53 douzifly 阅读(830) 评论(1) 编辑

2011年1月19日

解决Eclipse智能提示时特别卡的现象

在.之后输入setXXX导致界面特别的卡,本以为是Eclipse 3.6.1和ADT不兼容造成的,后面换了Eclipse 3.5.2问题依然。于是到属性里一看,果然发现了问题。

mistake

注意图中的红色圈住部分,该路径已经不再存在,把路径修改为正确的后,不再卡了。

posted @ 2011-01-19 16:18 douzifly 阅读(254) 评论(0) 编辑

2011年1月9日

Android 笔记之为性能而设计

摘要: 小弟才疏学浅,本文为Android官网镜像http://androidappdocs.appspot.comguide/practices/design/performance.html的翻译,目的在于自己方便查看,也为懒人弄个方便。如有错误,敬请指正。 Android程序将运行在计算能力,存储和电量都有限制的设备上。所以Android程序应该是高效的。即便你的应用程序已经运行的够快,耗电量也是你...阅读全文

posted @ 2011-01-09 23:42 douzifly 阅读(277) 评论(0) 编辑

2011年1月5日

HELLO WROLD

摘要: printf(“%s”,“HELLO WORLD,HELLO Cnblogs”,)阅读全文

posted @ 2011-01-05 23:57 douzifly 阅读(39) 评论(1) 编辑