之前实现过一次这种效果的ExpandableListView:https://www.jb51.net/article/38482.htm,带效果比较挫,最近,在参考联系人源码PinnedHeaderListView,以及网上各位大侠的源码,封装了一个效果最好,而且使用最简单的IphoneTreeView,下面先看看效果图:

首先让我们看看封装得比较完善的IphoneTreeView:

public class IphoneTreeView extends ExpandableListView implements

OnScrollListener, OnGroupClickListener {

public IphoneTreeView(Context context, AttributeSet attrs, int defStyle) {

super(context, attrs, defStyle);

registerListener();

}

public IphoneTreeView(Context context, AttributeSet attrs) {

super(context, attrs);

registerListener();

}

public IphoneTreeView(Context context) {

super(context);

registerListener();

}

/**

* Adapter 接口 . 列表必须实现此接口 .

*/

public interface IphoneTreeHeaderAdapter {

public static final int PINNED_HEADER_GONE = 0;

public static final int PINNED_HEADER_VISIBLE = 1;

public static final int PINNED_HEADER_PUSHED_UP = 2;

/**

* 获取 Header 的状态

*

* @param groupPosition

* @param childPosition

* @return

* PINNED_HEADER_GONE,PINNED_HEADER_VISIBLE,PINNED_HEADER_PUSHED_UP

* 其中之一

*/

int getTreeHeaderState(int groupPosition, int childPosition);

/**

* 配置 QQHeader, 让 QQHeader 知道显示的内容

*

* @param header

* @param groupPosition

* @param childPosition

* @param alpha

*/

void configureTreeHeader(View header, int groupPosition,

int childPosition, int alpha);

/**

* 设置组按下的状态

*

* @param groupPosition

* @param status

*/

void onHeadViewClick(int groupPosition, int status);

/**

* 获取组按下的状态

*

* @param groupPosition

* @return

*/

int getHeadViewClickStatus(int groupPosition);

}

private static final int MAX_ALPHA = 255;

private IphoneTreeHeaderAdapter mAdapter;

/**

* 用于在列表头显示的 View,mHeaderViewVisible 为 true 才可见

*/

private View mHeaderView;

/**

* 列表头是否可见

*/

private boolean mHeaderViewVisible;

private int mHeaderViewWidth;

private int mHeaderViewHeight;

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();

}

private void registerListener() {

setOnScrollListener(this);

setOnGroupClickListener(this);

}

/**

* 点击 HeaderView 触发的事件

*/

private void headerViewClick() {

long packedPosition = getExpandableListPosition(this

.getFirstVisiblePosition());

int groupPosition = ExpandableListView

.getPackedPositionGroup(packedPosition);

if (mAdapter.getHeadViewClickStatus(groupPosition) == 1) {

this.collapseGroup(groupPosition);

mAdapter.onHeadViewClick(groupPosition, 0);

} else {

this.expandGroup(groupPosition);

mAdapter.onHeadViewClick(groupPosition, 1);

}

this.setSelectedGroup(groupPosition);

}

private float mDownX;

private float mDownY;

/**

* 如果 HeaderView 是可见的 , 此函数用于判断是否点击了 HeaderView, 并对做相应的处理 , 因为 HeaderView

* 是画上去的 , 所以设置事件监听是无效的 , 只有自行控制 .

*/

@Override

public boolean onTouchEvent(MotionEvent ev) {

if (mHeaderViewVisible) {

switch (ev.getAction()) {

case MotionEvent.ACTION_DOWN:

mDownX = ev.getX();

mDownY = ev.getY();

if (mDownX <= mHeaderViewWidth && mDownY <= mHeaderViewHeight) {

return true;

}

break;

case MotionEvent.ACTION_UP:

float x = ev.getX();

float y = ev.getY();

float offsetX = Math.abs(x - mDownX);

float offsetY = Math.abs(y - mDownY);

// 如果 HeaderView 是可见的 , 点击在 HeaderView 内 , 那么触发 headerClick()

if (x <= mHeaderViewWidth && y <= mHeaderViewHeight

&& offsetX <= mHeaderViewWidth

&& offsetY <= mHeaderViewHeight) {

if (mHeaderView != null) {

headerViewClick();

}

return true;

}

break;

default:

break;

}

}

return super.onTouchEvent(ev);

}

@Override

public void setAdapter(ExpandableListAdapter adapter) {

super.setAdapter(adapter);

mAdapter = (IphoneTreeHeaderAdapter) adapter;

}

/**

*

* 点击了 Group 触发的事件 , 要根据根据当前点击 Group 的状态来

*/

@Override

public boolean onGroupClick(ExpandableListView parent, View v,

int groupPosition, long id) {

if (mAdapter.getHeadViewClickStatus(groupPosition) == 0) {

mAdapter.onHeadViewClick(groupPosition, 1);

parent.expandGroup(groupPosition);

parent.setSelectedGroup(groupPosition);

} else if (mAdapter.getHeadViewClickStatus(groupPosition) == 1) {

mAdapter.onHeadViewClick(groupPosition, 0);

parent.collapseGroup(groupPosition);

}

// 返回 true 才可以弹回第一行 , 不知道为什么

return true;

}

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

super.onMeasure(widthMeasureSpec, heightMeasureSpec);

if (mHeaderView != null) {

measureChild(mHeaderView, widthMeasureSpec, heightMeasureSpec);

mHeaderViewWidth = mHeaderView.getMeasuredWidth();

mHeaderViewHeight = 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 flatPostion = getExpandableListPosition(getFirstVisiblePosition());

final int groupPos = ExpandableListView

.getPackedPositionGroup(flatPostion);

final int childPos = ExpandableListView

.getPackedPositionChild(flatPostion);

int state = mAdapter.getTreeHeaderState(groupPos, childPos);

if (mHeaderView != null && mAdapter != null && state != mOldState) {

mOldState = state;

mHeaderView.layout(0, 0, mHeaderViewWidth, mHeaderViewHeight);

}

configureHeaderView(groupPos, childPos);

}

public void configureHeaderView(int groupPosition, int childPosition) {

if (mHeaderView == null || mAdapter == null

|| ((ExpandableListAdapter) mAdapter).getGroupCount() == 0) {

return;

}

int state = mAdapter.getTreeHeaderState(groupPosition, childPosition);

switch (state) {

case IphoneTreeHeaderAdapter.PINNED_HEADER_GONE: {

mHeaderViewVisible = false;

break;

}

case IphoneTreeHeaderAdapter.PINNED_HEADER_VISIBLE: {

mAdapter.configureTreeHeader(mHeaderView, groupPosition,

childPosition, MAX_ALPHA);

if (mHeaderView.getTop() != 0) {

mHeaderView.layout(0, 0, mHeaderViewWidth, mHeaderViewHeight);

}

mHeaderViewVisible = true;

break;

}

case IphoneTreeHeaderAdapter.PINNED_HEADER_PUSHED_UP: {

View firstView = getChildAt(0);

int bottom = firstView.getBottom();

// intitemHeight = firstView.getHeight();

int headerHeight = mHeaderView.getHeight();

int y;

int alpha;

if (bottom < headerHeight) {

y = (bottom - headerHeight);

alpha = MAX_ALPHA * (headerHeight + y) / headerHeight;

} else {

y = 0;

alpha = MAX_ALPHA;

}

mAdapter.configureTreeHeader(mHeaderView, groupPosition,

childPosition, alpha);

if (mHeaderView.getTop() != y) {

mHeaderView.layout(0, y, mHeaderViewWidth, mHeaderViewHeight

+ y);

}

mHeaderViewVisible = true;

break;

}

}

}

@Override

/**

* 列表界面更新时调用该方法(如滚动时)

*/

protected void dispatchDraw(Canvas canvas) {

super.dispatchDraw(canvas);

if (mHeaderViewVisible) {

// 分组栏是直接绘制到界面中,而不是加入到ViewGroup中

drawChild(canvas, mHeaderView, getDrawingTime());

}

}

@Override

public void onScroll(AbsListView view, int firstVisibleItem,

int visibleItemCount, int totalItemCount) {

final long flatPos = getExpandableListPosition(firstVisibleItem);

int groupPosition = ExpandableListView.getPackedPositionGroup(flatPos);

int childPosition = ExpandableListView.getPackedPositionChild(flatPos);

configureHeaderView(groupPosition, childPosition);

}

@Override

public void onScrollStateChanged(AbsListView view, int scrollState) {

}

}

使用起来也是比较简单的,先在布局文件中声明activity_main.xml:

xmlns:tools="http://schemas.android.com/tools"

android:layout_width="match_parent"

android:layout_height="match_parent"

tools:context=".MainActivity" >

android:id="@+id/iphone_tree_view"

android:layout_width="match_parent"

android:layout_height="match_parent"

android:background="@android:color/transparent"

android:cacheColorHint="@android:color/transparent"

android:divider="@null"

android:transcriptMode="normal" />

然后在MainActivity中调用,为了缩减代码,我把Adapter作为内部类放在MainActivity中了:

public class MainActivity extends Activity {

private LayoutInflater mInflater;

private IphoneTreeView iphoneTreeView;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

initView();

}

private void initView() {

// TODO Auto-generated method stub

mInflater = LayoutInflater.from(this);

iphoneTreeView = (IphoneTreeView) findViewById(R.id.iphone_tree_view);

iphoneTreeView.setHeaderView(getLayoutInflater().inflate(

R.layout.list_head_view, iphoneTreeView, false));

iphoneTreeView.setGroupIndicator(null);

iphoneTreeView.setAdapter(new IphoneTreeViewAdapter());

}

public class IphoneTreeViewAdapter extends BaseExpandableListAdapter

implements IphoneTreeHeaderAdapter {

// Sample data set. children[i] contains the children (String[]) for

// groups[i].

private HashMap groupStatusMap;

private String[] groups = { "第一组", "第二组", "第三组", "第四组" };

private String[][] children = {

{ "Way", "Arnold", "Barry", "Chuck", "David", "Afghanistan",

"Albania", "Belgium", "Lily", "Jim", "LiMing", "Jodan" },

{ "Ace", "Bandit", "Cha-Cha", "Deuce", "Bahamas", "China",

"Dominica", "Jim", "LiMing", "Jodan" },

{ "Fluffy", "Snuggles", "Ecuador", "Ecuador", "Jim", "LiMing",

"Jodan" },

{ "Goldy", "Bubbles", "Iceland", "Iran", "Italy", "Jim",

"LiMing", "Jodan" } };

public IphoneTreeViewAdapter() {

// TODO Auto-generated constructor stub

groupStatusMap = new HashMap();

}

public Object getChild(int groupPosition, int childPosition) {

return children[groupPosition][childPosition];

}

public long getChildId(int groupPosition, int childPosition) {

return childPosition;

}

public int getChildrenCount(int groupPosition) {

return children[groupPosition].length;

}

public Object getGroup(int groupPosition) {

return groups[groupPosition];

}

public int getGroupCount() {

return groups.length;

}

public long getGroupId(int groupPosition) {

return groupPosition;

}

public boolean isChildSelectable(int groupPosition, int childPosition) {

return true;

}

public boolean hasStableIds() {

return true;

}

@Override

public View getChildView(int groupPosition, int childPosition,

boolean isLastChild, View convertView, ViewGroup parent) {

// TODO Auto-generated method stub

if (convertView == null) {

convertView = mInflater.inflate(R.layout.list_item_view, null);

}

TextView tv = (TextView) convertView

.findViewById(R.id.contact_list_item_name);

tv.setText(getChild(groupPosition, childPosition).toString());

TextView state = (TextView) convertView

.findViewById(R.id.cpntact_list_item_state);

state.setText("爱生活...爱Android...");

return convertView;

}

@Override

public View getGroupView(int groupPosition, boolean isExpanded,

View convertView, ViewGroup parent) {

// TODO Auto-generated method stub

if (convertView == null) {

convertView = mInflater.inflate(R.layout.list_group_view, null);

}

TextView groupName = (TextView) convertView

.findViewById(R.id.group_name);

groupName.setText(groups[groupPosition]);

ImageView indicator = (ImageView) convertView

.findViewById(R.id.group_indicator);

TextView onlineNum = (TextView) convertView

.findViewById(R.id.online_count);

onlineNum.setText(getChildrenCount(groupPosition) + "/"

+ getChildrenCount(groupPosition));

if (isExpanded) {

indicator.setImageResource(R.drawable.indicator_expanded);

} else {

indicator.setImageResource(R.drawable.indicator_unexpanded);

}

return convertView;

}

@Override

public int getTreeHeaderState(int groupPosition, int childPosition) {

final int childCount = getChildrenCount(groupPosition);

if (childPosition == childCount - 1) {

return PINNED_HEADER_PUSHED_UP;

} else if (childPosition == -1

&& !iphoneTreeView.isGroupExpanded(groupPosition)) {

return PINNED_HEADER_GONE;

} else {

return PINNED_HEADER_VISIBLE;

}

}

@Override

public void configureTreeHeader(View header, int groupPosition,

int childPosition, int alpha) {

// TODO Auto-generated method stub

((TextView) header.findViewById(R.id.group_name))

.setText(groups[groupPosition]);

((TextView) header.findViewById(R.id.online_count))

.setText(getChildrenCount(groupPosition) + "/"

+ getChildrenCount(groupPosition));

}

@Override

public void onHeadViewClick(int groupPosition, int status) {

// TODO Auto-generated method stub

groupStatusMap.put(groupPosition, status);

}

@Override

public int getHeadViewClickStatus(int groupPosition) {

if (groupStatusMap.containsKey(groupPosition)) {

return groupStatusMap.get(groupPosition);

} else {

return 0;

}

}

}

}

好了,简单的一个例子就完成了,

总结一下:

原理: 在正在显示的最上面的组的标签位置添加一个和组视图完全一样的视图,作为组标签。这个标签的位置要随着列表的滑动不断变化,以保持总是显示在最上方,并且该消失的时候就消失。给这个标签添加点击事件,实现打开和关闭分组的功能。

组标签总是显示在上方,这是通过不断的调整其在布局中的位置来实现的。这个调整的过程,在初始化的时候,在 onLayout 方法中实现一次,后面都是在滚动过程中,根据对滚动状态的监听来实现的。

实例化要添加的标签的时候(在外面实现,即使调用 setTreeHeaderView之前),parent 要设为该ExpandableListView.

要学习以及好好理解这个,最好的方法是将添加进来的组标签设为半透明,便于观察整个过程。

源码下载

android iphone treeview,Android之IphoneTreeView带组指示器的ExpandableListView效果相关推荐

  1. iPhone 重量 android,iPhone不如Android可靠?数据告诉你这是真的!

    原标题:iPhone不如Android可靠?数据告诉你这是真的! 在很多人眼中,都认为iPhone使用起来更加流畅,性能更强.而安卓设备总是会使用一段时间开始卡顿,各种小毛病也接踵而至. 而近年来,i ...

  2. iphone和android,iPhone 和 Android 产品经理有哪些异同?

    牛比立体@知乎的回答: 相同点: 1.都以为自己了解用户 2.都以为自己了解手机 3.都认为自己的方向有前途 4.都用iPhone手机 5.都不用自己的产品 6.都是SB+苦B 不同点: 1.iPho ...

  3. android 8.0的imei简书,Android 8.0通知栏渠道,渠道组的适配和使用

    Android 8.0通知栏渠道,渠道组的适配和使用 推广一下 博客 android 8.0开始引入了渠道组的概念,app可以对通知类型进行细分,比如划分为:广告消息,私聊消息,群聊消息等. 但其实到 ...

  4. android手机照片传苹果电脑版,为知笔记Android/iPhone客户端图片传到电脑客户端的方法介绍...

    为知笔记是一款时下流行的支持多平台的云笔记软件,随时随地记录和查看有价值的信息,非常方便.当我们在用为知笔记电脑客户端编辑笔记时,常有从手机相册中取图片素材的需求,下面小编就来教教大家为知笔记手机传图 ...

  5. Charles最新破解版苹果iphone安卓android手机抓包分析教程笔记

    Charles最新破解版苹果iphone安卓android手机抓包分析教程笔记 中间遇到各种问题导致最终没法看到抓包信息,一个坑一个坑的埋,终于成功抓包小程序. 梳理了下可以尽量减少栽坑的安装过程,如 ...

  6. 最新内测版Android qq,腾讯体验中心发布Android/iPhone QQ 6.5.3 测试版

    腾讯体验中心又同时发布了Android/iPhone QQ 6.5.3测试版体验,上一个测试版同时发布于5月4日,时隔80多天迎来了新版升级,本次更新了按条件查找陌生人时可按年龄或职业筛选,全新短视频 ...

  7. 疯狂Android讲义第三版完整带目录

    疯狂Android讲义第三版完整带目录 下载地址: https://pan.baidu.com/s/1TBgJqJ5gpyg8UXIKxk2UMA 扫码下面二维码关注公众号回复100014 获取分享码 ...

  8. DIY 新浪微博Android客户端 ----用Android手机发送来自iPhone的微博

    今天看到一个哥们发布的DIY新浪微博客户端,自己试了下,确实好用,赫赫,给大家分享下 链接 http://hi.baidu.com/aqtata817/blog/item/776246021f81b6 ...

  9. iphone6s 刷android,iPhone也能刷安卓,只限两款手机,国外大神骚操作

    原标题:iPhone也能刷安卓,只限两款手机,国外大神骚操作 近日,有外国团队打破了iPhone与Android的界限,成功地在iPhone上跑起了Android系统!该团队推出了Project Sa ...

最新文章

  1. jQuery.delegate() 函数详解
  2. JavaScript中 var reEmail=/^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/; //邮箱检测
  3. DFT实际应用-User-Defined Test Points Example
  4. ICML 2020 | Google提出最强生成式摘要预训练模型——天马
  5. Linux远程复制命令SCP
  6. mysql数据库密码错误_MySQL数据库经典错误六 数据库密码忘记的问题
  7. pyqt5 qscrollarea到达_PyQt5如何用QPainter在QScrollArea上画线?
  8. 华为发布麒麟990系列芯片,余承东评论友商5G芯片:苹果没有 三星PPT
  9. SQL Server 2012 各版本功能比较
  10. 第十六章:SpringCloud Config 配置自动刷新
  11. linux下 Tomcat 端口号修改
  12. Ubuntu下mysql可视化_ubuntu上mysql有可视化界面吗 ubuntu mysql 图形界面
  13. VS运行应用程序无法正常启动(0xc000007b)。DirectX9.0无法修复
  14. Nvivo的使用学习
  15. easyui datagrid 点击其它 单元格,不让头列 checkbook 选中
  16. ECharts Title文字前添加图片
  17. oracle 授权同义词权限不足,创建同义词ora-01031权限不足
  18. PC傻瓜式安装黑苹果并打造成全能逆向工作站
  19. GlboalMapper20如何把mbt转为tif
  20. 堆和栈的区别以及特点

热门文章

  1. 2020-11-16-软工课程设计-黄金点游戏微信开发
  2. 知名高校博士:我改了这2个地方,一开始被秒拒的论文很快就成功发表了~
  3. 单片机:步进电机(内含:1 步进电机简介+2 步进电机工作原理+ 3 步进电机技术指标 +4. 软件设计+5.原始代码+6.实验现象)
  4. 蓝牙android rssi测距,蓝牙RSSI测距开发实践记录
  5. 教你如何从Google Map爬数据(切片)
  6. 常用计算机存储器类型,计算机常用的辅存储器有哪些
  7. 跑通AttnGAN (Python 3, Pytorch 1.0) -- coco
  8. adobe audition2021 mac中文直装版
  9. 【算法面试必刷JAVA版三】链表中的节点每k个一组翻转
  10. STM32的异常“、“中断”和“事件”区别和理解