首页 > 代码库 > 动手分析安卓仿QQ联系人列表TreeView控件

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

       因项目需要需要用到仿QQ联系人列表的控件样式,于是网上找到一个轮子(https://github.com/TealerProg/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
       
     二、下面简单分析下
    项目结构如下
    技术分享
     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-0410  */11 public interface ITreeViewHeaderUpdater {12     /** Header Gone. */13     public static final int STATE_GONE = 0x00;14     /** Header Visible. */15     public static final int STATE_VISIBLE_ALL = 0x01;16     /** Header Push up. */17     public 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      */27     public int getHeaderState(int groupPosition, int childPosition);28 29     /**30      * Update TreeView‘s header.31      *32      * @param header        The TreeView‘s header view.33      * @param groupPosition The group position.34      * @param childPosition The child position.35      * @param alpha         The header‘s alpha value.36      */37     public 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      */46     public 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      */55     public 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控件