因项目需要需要用到仿QQ联系人列表的控件样式,于是网上找到一个轮子(https://github.com/TealerProg/TreeView),工作完成现在简单分析一下这个源码。

  一、 需要用到的知识如下:

       
     二、下面简单分析下
    项目结构如下
    
     1、ITreeViewHeaderUpdater接口

 package com.markmao.treeview.widget;

 import android.view.View;

 /**
* Update interface TreeView's header .
*
* @author markmjw
* @date 2014-01-04
*/
public interface ITreeViewHeaderUpdater {
/** Header Gone. */
public static final int STATE_GONE = 0x00;
/** Header Visible. */
public static final int STATE_VISIBLE_ALL = 0x01;
/** Header Push up. */
public static final int STATE_VISIBLE_PART = 0x02; /**
* Get TreeView's header state.
*
* @param groupPosition The group position.
* @param childPosition The child position.
* @return {@link #STATE_GONE}, {@link #STATE_VISIBLE_ALL},
* {@link #STATE_VISIBLE_PART}
*/
public int getHeaderState(int groupPosition, int childPosition); /**
* Update TreeView's header.
*
* @param header The TreeView's header view.
* @param groupPosition The group position.
* @param childPosition The child position.
* @param alpha The header's alpha value.
*/
public void updateHeader(View header, int groupPosition, int childPosition, int alpha); /**
* The header view onClick.
*
* @param groupPosition The group position.
* @param status {@link #STATE_GONE}, {@link #STATE_VISIBLE_ALL},
* {@link #STATE_VISIBLE_PART}
*/
public void onHeaderClick(int groupPosition, int status); /**
* Get the header's state on click.
*
* @param groupPosition The group position.
* @return {@link #STATE_GONE}, {@link #STATE_VISIBLE_ALL},
* {@link #STATE_VISIBLE_PART}
*/
public int getHeaderClickStatus(int groupPosition);
}

主要定义了三个状态: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控件的更多相关文章

  1. WPF 自定义TreeView控件样式,仿QQ联系人列表

    一.前言 TreeView控件在项目中使用比较频繁,普通的TreeView并不能满足我们的需求.因此我们需要滴对TreeView进行改造.下面的内容将介绍仿QQ联系人TreeView样式及TreeVi ...

  2. Android自定义View,高仿QQ音乐歌词滚动控件!

    最近在以QQ音乐为样板做一个手机音乐播放器,源码下篇博文放出.今天我想聊的是这个QQ音乐播放器中歌词显示控件的问题,和小伙伴们一起来探讨怎么实现这个歌词滚动的效果.OK,废话不多说,先来看看效果图: ...

  3. VB TreeView控件使用详解

    来源:http://www.newxing.com/Tech/Program/VisualBasic/TreeView_587.html 三小时快速掌握TreeView树状控件的使用.能不能掌握控件的 ...

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

    本文主要讲述如何通过使用TreeView控件来实现树结构的显示,以及树节点的快速查找功能.并针对通用树结构的数据结构存储进行一定的分析和设计.通过文本能够了解如何存储层次结构的数据库设计,如何快速使用 ...

  5. Visual Studio 2010下ASPX页面的TreeView控件循环遍历

    如果维护一个老系统就总会遇到各种问题,而这次是TreeView的循环遍历.对于Visual Studio2010上aspx页面的TreeView控件,我感受到了什么叫集微软之大智慧.与二叉树型不一样. ...

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

    本篇我们来介绍Windows Phone 8.1 新特性中的列表选择控件. 在Windows Phone 8 时代,大家都会使用 LongListSelector 来实现列表选择控件,对数据进行分组显 ...

  7. C#Winform中treeView控件使用总结

    1.如何展开结点时改变图标(注意:不是选中时) 要在目录中使用图标首先要加入一个控件ImageList(命名为imageList1),然后可以按图片的index或名称引用图片. 然后需要在TreeVi ...

  8. WPF 将数据源绑定到TreeView控件出现界面卡死的情况

    首先来谈一下实现将自定义的类TreeMode绑定到TreeView控件上的一个基本的思路,由于每一个节点都要包含很多自定义的一些属性信息,因此我们需要将该类TreeMode进行封装,TreeView的 ...

  9. ComboBox中如何嵌套TreeView控件

      在ComboBox中嵌套TreeView控件,有时候我们在设计界面的时候,由于界面设计的需要,我们需要将TreeView控件嵌套在ComboBox中,因为TreeView控件实在是太占用地方了,要 ...

随机推荐

  1. android黑科技系列——解析公众号文章消息和链接文章消息自动打开原理

    一.辅助功能方案分析 关于WX的各种功能插件已经非常普遍了,而现在的插件都是依赖于Xposed框架进行的,所以个人觉得WX应该在这方便应对Xposed框架的使用防护,防止插件满天飞的现象,本文来介绍一 ...

  2. android中TextView内容竖向显示

    项目中遇到需要textview内容竖着排的需求,如图所示: 网上那些“教程”并不能达到需要的效果,发现有一个属性可以支持这种效果,android:ems=“*”,这是属性表示一行只显示*个字符. 具体 ...

  3. [Android]有关外部链接唤醒App需要注意的坑

    移动互联网发展到今天,一个移动app需要和各种各样的外部链接关联,它不再仅仅从手机的桌面启动,更多的将会从其他的应用.浏览器链接.短信.二维码或者微信分享等渠道启动,这里涉及到的是各种各样的营销渠道和 ...

  4. jQuery——事件操作

    事件绑定 1.简单事件绑定 $("button").click(function () {})//可重复绑定,不会被层叠 2.bind():不推荐使用 $("button ...

  5. jQuery——stop

    为什么要停止动画? 对同一个元素,如果拥有一个以上的动画对其加以作用,那么后面的动画会被放入一个动画队列中.动画队列的动画是在其上一个动画完成以后才会执行. 控制两个参数四种情况 1.第一个参数表示后 ...

  6. SQL基本操作——创建索引

    CREATE INDEX 语句用于在表中创建索引.在不读取整个表的情况下,索引使数据库应用程序可以更快地查找数据. 索引:您可以在表中创建索引,以便更加快速高效地查询数据.用户无法看到索引,它们只能被 ...

  7. Java程序员2016年终总结

    回顾2016年, 很庆幸,自己能在2016年年尾找到一份满意的web后台开发工作.这也是我学习编程以来第一份开发工作,我很是珍惜. 还记得大三接触了Java的JFrame编写的坦克大战之后,就对编程产 ...

  8. java主要集合类的数据结构

    1).ArrayList  ArrayList维护着一个对象数组.如果调用new ArrayList()后,它会默认初始一个size=10的数组.  每次add操作都要检查数组容量,如果不够,重新 ...

  9. tidyverse生态链

    一套完整的数据分析流程 , 如下图所示 从图中可以看到,整个流程包括读取数据,整洁数据,数据探索和交流部分.经过前两部分, 我们可以得到一个整理好的数据,它的每一行都是一个样本 , 每一列是一个变量. ...

  10. Java对象的创建及使用

    Java对象的创建及使用 对象是类的具体实例(instance),是真实存在的个体: