动手分析安卓仿QQ联系人列表TreeView控件

落花踏尽游何处,笑入胡姬酒肆中。这篇文章主要讲述动手分析安卓仿QQ联系人列表TreeView控件相关的知识,希望能为你提供帮助。
        因项目需要需要用到仿QQ联系人列表的控件样式,于是网上找到一个轮子(https://github.com/TealerProg/TreeView),工作完成现在简单分析一下这个源码。
【动手分析安卓仿QQ联系人列表TreeView控件】          一、 需要用到的知识如下:
      ①安卓事件分发机制:(http://blog.csdn.net/lvxiangan/article/details/9309927  或  http://gundumw100.iteye.com/blog/1052270)       ②安卓View绘制:http://blog.csdn.net/guolin_blog/article/details/16330267       ③安卓View坐标:http://blog.csdn.net/jason0539/article/details/42743531                   二、下面简单分析下       项目结构如下      

动手分析安卓仿QQ联系人列表TreeView控件

文章图片
      1、ITreeViewHeaderUpdater接口      
1 package com.markmao.treeview.widget; 2 3 import android.view.View; 4 5 /** 6* Update interface TreeView\'s header . 7* 8* @author markmjw 9* @date 2014-01-04 10*/ 11 public interface ITreeViewHeaderUpdater { 12/** Header Gone. */ 13public static final int STATE_GONE = 0x00; 14/** Header Visible. */ 15public static final int STATE_VISIBLE_ALL = 0x01; 16/** Header Push up. */ 17public static final int STATE_VISIBLE_PART = 0x02; 18 19/** 20* Get TreeView\'s header state. 21* 22* @param groupPosition The group position. 23* @param childPosition The child position. 24* @return {@link #STATE_GONE}, {@link #STATE_VISIBLE_ALL}, 25* {@link #STATE_VISIBLE_PART} 26*/ 27public int getHeaderState(int groupPosition, int childPosition); 28 29/** 30* Update TreeView\'s header. 31* 32* @param headerThe TreeView\'s header view. 33* @param groupPosition The group position. 34* @param childPosition The child position. 35* @param alphaThe header\'s alpha value. 36*/ 37public void updateHeader(View header, int groupPosition, int childPosition, int alpha); 38 39/** 40* The header view onClick. 41* 42* @param groupPosition The group position. 43* @param status{@link #STATE_GONE}, {@link #STATE_VISIBLE_ALL}, 44*{@link #STATE_VISIBLE_PART} 45*/ 46public void onHeaderClick(int groupPosition, int status); 47 48/** 49* Get the header\'s state on click. 50* 51* @param groupPosition The group position. 52* @return {@link #STATE_GONE}, {@link #STATE_VISIBLE_ALL}, 53* {@link #STATE_VISIBLE_PART} 54*/ 55public int getHeaderClickStatus(int groupPosition); 56 }

        主要定义了三个状态:STATE_GONE:HeaderView处于隐藏不显示状态,STATE_VISIBLE:HeaderView处于显示状态,STATE_VISIBLE_PART:HeaderView处于显示且需要向上推起的临界状态。
          2、BaseTreeViewAdapter实现ITreeViewHeaderUpdater接口
         
package com.markmao.treeview.widget; import android.util.Log; import android.util.SparseIntArray; import android.widget.BaseExpandableListAdapter; /** * The base adapter for TreeView. * * @author markmjw * @date 2014-01-04 */ public abstract class BaseTreeViewAdapter extends BaseExpandableListAdapter implements ITreeViewHeaderUpdater { protected TreeView mTreeView; protected SparseIntArray mGroupStatusArray; public BaseTreeViewAdapter(TreeView treeView) { mTreeView = treeView; mGroupStatusArray = new SparseIntArray(); }@Override public int getHeaderState(int groupPosition, int childPosition) { final int childCount = getChildrenCount(groupPosition); if (childPosition == childCount - 1) { return STATE_VISIBLE_PART; } else if (childPosition == -1 & & !mTreeView.isGroupExpanded(groupPosition)) { return STATE_GONE; } else { return STATE_VISIBLE_ALL; } }@Override public void onHeaderClick(int groupPosition, int status) { mGroupStatusArray.put(groupPosition, status); }@Override public int getHeaderClickStatus(int groupPosition) { return mGroupStatusArray.get(groupPosition, STATE_GONE); } }

        主要方法:getHeaderState方法,当childPosition==childCount-1条件的时候(及 HeaderView处于显示且需要向上推起的临界状态)时返回STATE_VISIBLE_PART
        3、TreeView
     
package com.markmao.treeview.widget; import android.content.Context; import android.graphics.Canvas; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.widget.AbsListView; import android.widget.AbsListView.OnScrollListener; import android.widget.ExpandableListAdapter; import android.widget.ExpandableListView; import android.widget.ExpandableListView.OnGroupClickListener; /** * This widget extends {@link android.widget.ExpandableListView}, just like TreeView(ios). * * @see android.widget.ExpandableListView * @author markmjw * @date 2014-01-03 */ public class TreeView extends ExpandableListView implements OnScrollListener, OnGroupClickListener { private static final int MAX_ALPHA = 255; private ITreeViewHeaderUpdater mUpdater; private View mHeaderView; private boolean mHeaderVisible; private int mHeaderWidth; private int mHeaderHeight; public TreeView(Context context) { super(context); init(); }public TreeView(Context context, AttributeSet attrs) { super(context, attrs); init(); }public TreeView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(); }private void init() { setSmoothScrollbarEnabled(true); setOnScrollListener(this); setOnGroupClickListener(this); }/** * Sets the list header view * * @param view */ public void setHeaderView(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(); }@Override public void setAdapter(ExpandableListAdapter adapter) { super.setAdapter(adapter); if(adapter instanceof ITreeViewHeaderUpdater) { mUpdater = (ITreeViewHeaderUpdater) adapter; } else { throw new IllegalArgumentException("The adapter must instanceof ITreeViewHeaderUpdater."); } }@Override public boolean onTouchEvent(MotionEvent ev) { // header view is visible if (mHeaderVisible) { float downX = 0; float downY = 0; switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: downX = ev.getX(); downY = ev.getY(); if (downX < = mHeaderWidth & & downY < = mHeaderHeight) { return true; } break; case MotionEvent.ACTION_UP: float x = ev.getX(); float y = ev.getY(); float offsetX = Math.abs(x - downX); float offsetY = Math.abs(y - downY); // the touch event under header view if (x < = mHeaderWidth & & y < = mHeaderHeight & & offsetX < = mHeaderWidth & & offsetY < = mHeaderHeight) { if (mHeaderView != null) { onHeaderViewClick(); }return true; } break; default: break; } }return super.onTouchEvent(ev); }@Override public boolean onGroupClick(ExpandableListView parent, View v, int groupPosition, long id) { int status = mUpdater.getHeaderClickStatus(groupPosition); switch (status) { case ITreeViewHeaderUpdater.STATE_GONE: mUpdater.onHeaderClick(groupPosition, ITreeViewHeaderUpdater.STATE_VISIBLE_ALL); break; case ITreeViewHeaderUpdater.STATE_VISIBLE_ALL: mUpdater.onHeaderClick(groupPosition, ITreeViewHeaderUpdater.STATE_GONE); break; case ITreeViewHeaderUpdater.STATE_VISIBLE_PART: // ignore break; default: break; }return false; }@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); if (mHeaderView != null) { measureChild(mHeaderView, widthMeasureSpec, heightMeasureSpec); mHeaderWidth = mHeaderView.getMeasuredWidth(); mHeaderHeight = 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 listPosition = getExpandableListPosition(getFirstVisiblePosition()); final int groupPos = ExpandableListView.getPackedPositionGroup(listPosition); final int childPos = ExpandableListView.getPackedPositionChild(listPosition); Log.v("TreeView--onlayout分析:",String.format("返回所选择的List %d,返回所选择的组项 %d,返回所选择的子项 %d",(int)listPosition,groupPos,childPos)); int state = mUpdater.getHeaderState(groupPos, childPos); if (mHeaderView != null & & mUpdater != null & & state != mOldState) { mOldState = state; mHeaderView.layout(0, 0, mHeaderWidth, mHeaderHeight); }updateHeaderView(groupPos, childPos); }@Override protected void dispatchDraw(Canvas canvas) { super.dispatchDraw(canvas); if (mHeaderVisible) { // draw header view drawChild(canvas, mHeaderView, getDrawingTime()); Log.v("TreeView--dispatchDraw分析:","重新绘制mHeaderView"); } }@Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { final long listPosition = getExpandableListPosition(firstVisibleItem); int groupPos = ExpandableListView.getPackedPositionGroup(listPosition); int childPos = ExpandableListView.getPackedPositionChild(listPosition); updateHeaderView(groupPos, childPos); }@Override public void onScrollStateChanged(AbsListView view, int scrollState) {}private void updateHeaderView(int groupPosition, int childPosition) { if (mHeaderView == null || mUpdater == null || ((ExpandableListAdapter) mUpdater) .getGroupCount() == 0) { return; }int state = mUpdater.getHeaderState(groupPosition, childPosition); switch (state) { case ITreeViewHeaderUpdater.STATE_GONE: { mHeaderVisible = false; break; }case ITreeViewHeaderUpdater.STATE_VISIBLE_ALL: { mUpdater.updateHeader(mHeaderView, groupPosition, childPosition, MAX_ALPHA); if (mHeaderView.getTop() != 0) { mHeaderView.layout(0, 0, mHeaderWidth, mHeaderHeight); }mHeaderVisible = true; break; }case ITreeViewHeaderUpdater.STATE_VISIBLE_PART: { // a part of header view visible View firstView = getChildAt(0); int bottom = null != firstView ? firstView.getBottom() : 0; int headerHeight = mHeaderView.getHeight(); int topY; int alpha; if (bottom < headerHeight) { topY = (bottom - headerHeight); alpha = MAX_ALPHA * (headerHeight + topY) / headerHeight; } else { topY = 0; alpha = MAX_ALPHA; }mUpdater.updateHeader(mHeaderView, groupPosition, childPosition, alpha); Log.v("TreeView--updateHeaderView分析:",String.format("bottom=%d,headerHeight=%d,topY=%d,getTop=%d,Header Push up",bottom,headerHeight,topY, mHeaderView.getTop())); if (mHeaderView.getTop() != topY) { mHeaderView.layout(0, topY, mHeaderWidth, mHeaderHeight + topY); } mHeaderVisible = true; break; } } }private void onHeaderViewClick() { long packedPosition = getExpandableListPosition(getFirstVisiblePosition()); int groupPosition = ExpandableListView.getPackedPositionGroup(packedPosition); int status = mUpdater.getHeaderClickStatus(groupPosition); if (ITreeViewHeaderUpdater.STATE_VISIBLE_ALL == status) { collapseGroup(groupPosition); mUpdater.onHeaderClick(groupPosition, ITreeViewHeaderUpdater.STATE_GONE); } else { expandGroup(groupPosition); mUpdater.onHeaderClick(groupPosition, ITreeViewHeaderUpdater.STATE_VISIBLE_ALL); }setSelectedGroup(groupPosition); } }

     
        ①、在setHeaderView方法中,调用requestLayout()方法,请求重新布局,该方法执行后,将分别调用onMeasure()、onLayout()和onDraw()方法         ②、onMeasuer方法,在该方法中将测量mHeaderView的宽度和高度, 并保存在mHeaderWidth和mHeaderHeight的成员变量中         ③、onLayout方法,在该方法中,处理mHeadderView的重新布局问题,当屏幕可见的第一个Group所在位置处的getHeaderState状态改变的时候则重新布局mHeaderView在屏幕的最顶端,并在此方法中调用updateHeaderView方法。         ④、updateHeaderView方法,   需要注意的是在此方法中,在STATE_VISIABLE_PART状态中,有段代码如下            
if (mHeaderView.getTop() != topY) { mHeaderView.layout(0, topY, mHeaderWidth, mHeaderHeight + topY); }

          这段代码起到了到达临界状态时候,起到推送mHeaderView上滑动的作用
        ⑤、onTouchEvent方法,TreeView中实现该方法,根据事件分发机制适时的捕获用户点击事件,调用onHeaderViewClick方法,起到点击GroupItem以展开和收起对应组的效果。
        三、效果图          
动手分析安卓仿QQ联系人列表TreeView控件

文章图片
动手分析安卓仿QQ联系人列表TreeView控件

文章图片
                 

    推荐阅读