Android之IphoneTreeView带组指示器的ExpandableListView效果

之前实现过一次这种效果的ExpandableListView:http://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:


代码如下:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity" >
<com.way.iphonetreeview.IphoneTreeView
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" />
</RelativeLayout>

然后在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<Integer, Integer> 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<Integer, Integer>();
}
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.

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

(0)

相关推荐

  • Android ExpandableListView长按事件的完美解决办法

    关于ExpandableListView长按事件处理,网上很多都是使用将上下文菜单注册到ExpandableListView上实现长按事件. 这样做弊端显而易见,不够灵活,不能分别对父项.子项.父项之间.子项之间弹出内容做区分.下面来说我的解决方法,方法有点投机取巧.首先说明一点,使用我这种方法必须使用自定义的BaseExpandableListAdapter,至于为什么,具体后面讲到. ExpandableListView本身有继承自AdapterView的setOnItemLongClick

  • Android中ExpandableListView使用示例详解

    本文实例为大家分享了ExpandableListView使用示例,供大家参考,具体内容如下 MainActivity: public class Expandable_test extends Activity { private ExpandableListView listView; private Map<String, List<String>> dataset = new HashMap<>(); private String[] parentList = n

  • 分享Android中ExpandableListView控件使用教程

    本文采用一个Demo来展示Android中ExpandableListView控件的使用,如如何在组/子ListView中绑定数据源.直接上代码如下: 程序结构图: layout目录下的 main.xml 文件源码如下: <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android

  • Android ExpandableListView展开列表控件使用实例

    你是否觉得手机QQ上的好友列表那个控件非常棒? 不是..... 那也没关系,学多一点知识对自己也有益无害. 那么我们就开始吧. 展开型列表控件, 原名ExpandableListView 是普通的列表控件进阶版, 可以自由的把列表进行收缩, 非常的方便兼好看. 首先看看我完成的截图, 虽然界面不漂亮, 但大家可以自己去修改界面. 该控件需要一个主界面XML 一个标题界面XML及一个列表内容界面XML 首先我们来看看 mian.xml 主界面 复制代码 代码如下: //该界面非常简单, 只要一个E

  • Android中ExpandableListView的用法实例

    本文实例讲述了Android中ExpandableListView的用法,ExpandableListView是android中可以实现下拉list的一个控件,具体的实现方法如下: 首先:在layout的xml文件中定义一个ExpandableListView 复制代码 代码如下: <LinearLayout       android:id="@+id/linearLayout"      android:layout_width="fill_parent"

  • Android改变ExpandableListView的indicator图标实现方法

    本文实例讲述了Android改变ExpandableListView的indicator图标实现方法.分享给大家供大家参考,具体如下: 1)定义xml文件先,命名为expand_list_indicator.xml <?xml version="1.0" encoding="UTF-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"&

  • Android 关于ExpandableListView刷新问题的解决方法

    正文 首先是最基础的 ExpandableListView vList = (ExpandableListView) this.findViewById(R.id.list); EListAdapter adapter = new EListAdapter(getApplicationContext(), list); //list 是数据源 vList.setAdapter(adapter); //适配器就不写了, 都懂的 class EListAdapter extends BaseExpa

  • Android UI控件ExpandableListView基本用法详解

    ExpandableListView介绍  ExpandableListView的引入  ExpandableListView可以显示一个视图垂直滚动显示两级列表中的条目,这不同于列表视图(ListView).ExpandableListView允许有两个层次:一级列表中有二级列表.  比如在手机设置中,对于分类,有很好的效果.手机版QQ也是这样的效果. 使用ExpandableListView的整体思路 (1)给ExpandableListView设置适配器,那么必须先设置数据源. (2)数据

  • Android之带group指示器的ExpandableListView(自写)

    我们都知道Android缺省的ExpandableListView的group header无法固定在界面上,当向下滚动后,不能对当前显示的那些child 指示出它们归属于哪个group,在网上搜了很多关于仿手机QQ好友分组效果的ExpandableListView,发现都不尽如意,于是乎在别人的基础上改进了一点点,其实原理还是差不多的,只是增加了往上挤出去的动画效果,而且更加简单,只不过还是没有完全到达跟QQ一样的效果,希望有高手能实现更加逼真的效果,下面我们先看看效果图:  我这里没有把Ex

  • android使用ExpandableListView控件实现小说目录效果的例子

    今天给大家讲讲android的目录实现方法,就像大家看到的小说目录一样,android 提供了ExpandableListView控件可以实现二级列表展示效果,现在给大家讲讲这个控件的用法,下面是XML定义: 复制代码 代码如下: <?xml version="1.0" encoding="utf-8"?> <LinearLayout     xmlns:android="http://schemas.android.com/apk/re

随机推荐