动手分析安卓仿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-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以展开和收起对应组的效果。

三、效果图

         

时间: 2024-08-27 20:20:04

动手分析安卓仿QQ联系人列表TreeView控件的相关文章

Android 利用ExpandableListView显示和查询仿QQ分组列表用户信息

在我们的项目开发过程中,经常会对用户的信息进行分组,即通过组来显示用户的信息,同时通过一定的查询条件来显示查询后的相关用户信息,并且通过颜色选择器来设置列表信息的背景颜色. 其中借鉴xiaanming:http://blog.csdn.net/xiaanming/article/details/12684155 下面来看看项目运行后的效果图以及代码结构图: 下面通过代码来实现整个效果. 1.主界面布局activity_main.xml <span style="font-size:18px

带中文索引的ListView 仿微信联系人列表

由于各种原因,项目经理和产品经理把我做的东西给否定了,所以决定分享出去. 主要功能: 1 .带中文索引的ListView 2.自定义顶部搜索视图,可以对返回按钮,搜索按钮添加事件监听,带动画的咧!~ 3.底部自定义视图,可以对Listview的adapter添加监听,并且回调选中的数目,另外其他的视图都是可以自己添加的 4.右侧的索引视图,根据通讯录的解析后的数据动态生成相关索引列表 5.Adapter的抽象类,想优化自己的Adapter可以看一下,例子中的adapter仅仅是特例特写. 6.分

IOS总结_可收缩分组表格(仿QQ联系人界面)

#import "yxpGroupTBVC.h" #define  DIC_EXPANDED @"expanded" //是否是展开 0收缩 1展开 #define  DIC_ARARRY @"array" #define  DIC_TITILESTRING @"title" #define  CELL_HEIGHT 40.0f @interfaceyxpGroupTBVC ()<UITableViewDataSourc

如何:使用TreeView控件实现树结构显示及快速查询

本文主要讲述如何通过使用TreeView控件来实现树结构的显示,以及树节点的快速查找功能.并针对通用树结构的数据结构存储进行一定的分析和设计.通过文本能够了解如何存储层次结构的数据库设计,如何快速使用TreeView控件生产树,以及如何快速查找树节点. 关键词:C# TreeView.树结构存储.树节点查找.层次结构 一.      概述: 树结构(层次结构)在项目的使用中特别常见,在不同项目中使用的控件可能不同(如:在Extjs中使用的是TreePanel控件,WinForm中可能用的是Tre

WinForms中TreeView控件的扩展与使用

EXE文件方便大家测试   源码下载 TreeView控件非常的好用,在我的公文系统中,使用TreeView控件选择接收公文的人员,支持单选,可多选 现提取出来,方便大家使用 涉及到的知识点 1:从Xml文件中加载内容显示到TreeView控件中 <?xml version="1.0" encoding="utf-8"?> <根目录> <组 名称="校长" 用户ID="1000"> <

TreeView控件的应用

下面展示的是一个用Treeview控件编辑的一个住房信息管理系统的简单domo,其中Form1为主界面,Form2象征性的展示每一个子项: Form1: namespace 用树型列表动态显示菜单 { partial class Form1 { /// <summary> /// 必需的设计器变量. /// </summary> private System.ComponentModel.IContainer components = null; /// <summary&g

ComboBox中如何嵌套TreeView控件

在ComboBox中嵌套TreeView控件,有时候我们在设计界面的时候,由于界面设计的需要,我们需要将TreeView控件嵌套在ComboBox中,因为TreeView控件实在是太占用地方了,要想实现这样的功能,我们需要修改ComboBox控件的模板,这里贴出相关的代码,并进行分析:既然要将TreeView嵌套到ComboBox中,那么我们必须要修改ComboBoxItem的模板,在这里我们修改 ComboBoxItem的控件模板,同时我们也需要修改TreeView的ItemTemplate模

Windows Phone 8.1 新特性 - 控件之列表选择控件

本篇我们来介绍Windows Phone 8.1 新特性中的列表选择控件. 在Windows Phone 8 时代,大家都会使用 LongListSelector 来实现列表选择控件,对数据进行分组显示.比如通讯录中,按照名字首字母进行分组,点击分组标题后跳转到该标题对应的分组. 而Windows Phone 8.1 中会利用 ListView 和 SemanticZoom 来实现,下面我们来看看实现过程. 首先我们来认识一下ListView 和 SemanticZoom: ListView 从

一个简单的可控的头像列表叠加控件

一个简单的可控的头像列表叠加控件 需求:评论/点赞头像横向排列,第N个叠加在第N+1个上面,并且N小于一个固定的数分析:1.假设N=4:头像列表最多显示4个,不足4个,有几个显示几个:2.头像第N个叠加在第N+1个上面,无法使用margin负数实现(叠加顺序不对) 1.控件实现代码要点:控件可以横向滑动,继承HorizontalScrollView:创建RelativeLayout存放头像集合: public class SDAvatarListLayout extends Horizontal