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


           因项目需要需要用到仿QQ联系人列表的控件样式,于是网上找到一个轮子(https://github.com/TealerProg/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. */
    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以展开和收起对应组的效果。

           三、效果图
             
                    
  • 相关阅读:
    介绍Asta4D
    Mac下terminal的常用命令
    Mac下的终端(Terminal)简介
    SCA简介及配置示例
    抽象
    自助式微软BI工具PowerPivot简介!
    C编译: 使用gdb调试
    operamasksui2.0 +MVC4.0+EF5.0实战 当EntityFramework遇上Json,引爆 循环引用 这颗雷
    ASP.NET MVC分部类的使用
    GWT入门教程
  • 原文地址:https://www.cnblogs.com/wolipengbo/p/7235023.html
Copyright © 2020-2023  润新知