Android之IphoneTreeView带组指示器的ExpandableListView效果
在正在显示的最上面的组的标签位置添加一个和组视图完全一样的视图,作为组标签。这个标签的位置要随着列表的滑动不断变化,以保持总是显示在最上方,并且该消失的时候就消失
之前实现过一次这种效果的ExpandableListView:www.jb51.net/article/38482.htm,带效果比较挫,最近,在参考联系人源码PinnedHeaderListView,以及网上各位大侠的源码,封装了一个效果最好,而且使用最简单的IphoneTreeView,下面先看看效果图:
首先让我们看看封装得比较完善的IphoneTreeView:
复制代码代码如下:
publicclassIphoneTreeViewextendsExpandableListViewimplements
OnScrollListener,OnGroupClickListener{
publicIphoneTreeView(Contextcontext,AttributeSetattrs,intdefStyle){
super(context,attrs,defStyle);
registerListener();
}
publicIphoneTreeView(Contextcontext,AttributeSetattrs){
super(context,attrs);
registerListener();
}
publicIphoneTreeView(Contextcontext){
super(context);
registerListener();
}
/
Adapter接口.列表必须实现此接口.
/
publicinterfaceIphoneTreeHeaderAdapter{
publicstaticfinalintPINNED_HEADER_GONE=0;
publicstaticfinalintPINNED_HEADER_VISIBLE=1;
publicstaticfinalintPINNED_HEADER_PUSHED_UP=2;
/
获取Header的状态
@paramgroupPosition
@paramchildPosition
@return
PINNED_HEADER_GONE,PINNED_HEADER_VISIBLE,PINNED_HEADER_PUSHED_UP
其中之一
/
intgetTreeHeaderState(intgroupPosition,intchildPosition);
/
配置QQHeader,让QQHeader知道显示的内容
@paramheader
@paramgroupPosition
@paramchildPosition
@paramalpha
/
voidconfigureTreeHeader(Viewheader,intgroupPosition,
intchildPosition,intalpha);
/
设置组按下的状态
@paramgroupPosition
@paramstatus
/
voidonHeadViewClick(intgroupPosition,intstatus);
/
获取组按下的状态
@paramgroupPosition
@return
/
intgetHeadViewClickStatus(intgroupPosition);
}
privatestaticfinalintMAX_ALPHA=255;
privateIphoneTreeHeaderAdaptermAdapter;
/
用于在列表头显示的View,mHeaderViewVisible为true才可见
/
privateViewmHeaderView;
/
列表头是否可见
/
privatebooleanmHeaderViewVisible;
privateintmHeaderViewWidth;
privateintmHeaderViewHeight;
publicvoidsetHeaderView(Viewview){
mHeaderView=view;
AbsListView.LayoutParamslp=newAbsListView.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
view.setLayoutParams(lp);
if(mHeaderView!=null){
setFadingEdgeLength(0);
}
requestLayout();
}
privatevoidregisterListener(){
setOnScrollListener(this);
setOnGroupClickListener(this);
}
/
点击HeaderView触发的事件
/
privatevoidheaderViewClick(){
longpackedPosition=getExpandableListPosition(this
.getFirstVisiblePosition());
intgroupPosition=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);
}
privatefloatmDownX;
privatefloatmDownY;
/
如果HeaderView是可见的,此函数用于判断是否点击了HeaderView,并对做相应的处理,因为HeaderView
是画上去的,所以设置事件监听是无效的,只有自行控制.
/
@Override
publicbooleanonTouchEvent(MotionEventev){
if(mHeaderViewVisible){
switch(ev.getAction()){
caseMotionEvent.ACTION_DOWN:
mDownX=ev.getX();
mDownY=ev.getY();
if(mDownX<=mHeaderViewWidth&&mDownY<=mHeaderViewHeight){
returntrue;
}
break;
caseMotionEvent.ACTION_UP:
floatx=ev.getX();
floaty=ev.getY();
floatoffsetX=Math.abs(x-mDownX);
floatoffsetY=Math.abs(y-mDownY);
//如果HeaderView是可见的,点击在HeaderView内,那么触发headerClick()
if(x<=mHeaderViewWidth&&y<=mHeaderViewHeight
&&offsetX<=mHeaderViewWidth
&&offsetY<=mHeaderViewHeight){
if(mHeaderView!=null){
headerViewClick();
}
returntrue;
}
break;
default:
break;
}
}
returnsuper.onTouchEvent(ev);
}
@Override
publicvoidsetAdapter(ExpandableListAdapteradapter){
super.setAdapter(adapter);
mAdapter=(IphoneTreeHeaderAdapter)adapter;
}
/
点击了Group触发的事件,要根据根据当前点击Group的状态来
/
@Override
publicbooleanonGroupClick(ExpandableListViewparent,Viewv,
intgroupPosition,longid){
if(mAdapter.getHeadViewClickStatus(groupPosition)==0){
mAdapter.onHeadViewClick(groupPosition,1);
parent.expandGroup(groupPosition);
parent.setSelectedGroup(groupPosition);
}elseif(mAdapter.getHeadViewClickStatus(groupPosition)==1){
mAdapter.onHeadViewClick(groupPosition,0);
parent.collapseGroup(groupPosition);
}
//返回true才可以弹回第一行,不知道为什么
returntrue;
}
@Override
protectedvoidonMeasure(intwidthMeasureSpec,intheightMeasureSpec){
super.onMeasure(widthMeasureSpec,heightMeasureSpec);
if(mHeaderView!=null){
measureChild(mHeaderView,widthMeasureSpec,heightMeasureSpec);
mHeaderViewWidth=mHeaderView.getMeasuredWidth();
mHeaderViewHeight=mHeaderView.getMeasuredHeight();
}
}
privateintmOldState=-1;
@Override
protectedvoidonLayout(booleanchanged,intleft,inttop,intright,
intbottom){
super.onLayout(changed,left,top,right,bottom);
finallongflatPostion=getExpandableListPosition(getFirstVisiblePosition());
finalintgroupPos=ExpandableListView
.getPackedPositionGroup(flatPostion);
finalintchildPos=ExpandableListView
.getPackedPositionChild(flatPostion);
intstate=mAdapter.www.sm136.comgetTreeHeaderState(groupPos,childPos);
if(mHeaderView!=null&&mAdapter!=null&&state!=mOldState){
mOldState=state;
mHeaderView.layout(0,0,mHeaderViewWidth,mHeaderViewHeight);
}
configureHeaderView(groupPos,childPos);
}
publicvoidconfigureHeaderView(intgroupPosition,intchildPosition){
if(mHeaderView==null||mAdapter==null
||((ExpandableListAdapter)mAdapter).getGroupCount()==0){
return;
}
intstate=mAdapter.getTreeHeaderState(groupPosition,childPosition);
switch(state){
caseIphoneTreeHeaderAdapter.PINNED_HEADER_GONE:{
mHeaderViewVisible=false;
break;
}
caseIphoneTreeHeaderAdapter.PINNED_HEADER_VISIBLE:{
mAdapter.configureTreeHeader(mHeaderView,groupPosition,
childPosition,MAX_ALPHA);
if(mHeaderView.getTop()!=0){
mHeaderView.layout(0,0,mHeaderViewWidth,mHeaderViewHeight);
}
mHeaderViewVisible=true;
break;
}
caseIphoneTreeHeaderAdapter.PINNED_HEADER_PUSHED_UP:{
ViewfirstView=getChildAt(0);
intbottom=firstView.getBottom();
//intitemHeight=firstView.getHeight();
intheaderHeight=mHeaderView.getHeight();
inty;
intalpha;
if(bottom 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
/
列表界面更新时调用该方法(如滚动时)
/
protectedvoiddispatchDraw(Canvascanvas){
super.dispatchDraw(canvas);
if(mHeaderViewVisible){
//分组栏是直接绘制到界面中,而不是加入到ViewGroup中
drawChild(canvas,mHeaderView,getDrawingTime());
}
}
@Override
publicvoidonScroll(AbsListViewview,intfirstVisibleItem,
intvisibleItemCount,inttotalItemCount){
finallongflatPos=getExpandableListPosition(firstVisibleItem);
intgroupPosition=ExpandableListView.getPackedPositionGroup(flatPos);
intchildPosition=ExpandableListView.getPackedPositionChild(flatPos);
configureHeaderView(groupPosition,childPosition);
}
@Override
publicvoidonScrollStateChanged(AbsListViewview,intscrollState){
}
}
使用起来也是比较简单的,先在布局文件中声明activity_main.xml:
复制代码代码如下:
xmlns:tools="schemas.android/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中了:
复制代码代码如下:
publicclassMainActivityextendsActivity{
privateLayoutInflatermInflater;
privateIphoneTreeViewiphoneTreeView;
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.www.visa158.comactivity_main);
initView();
}
privatevoidinitView(){
//TODOAuto-generatedmethodstub
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(newIphoneTreeViewAdapter());
}
publicclassIphoneTreeViewAdapterextendsBaseExpandableListAdapter
implementsIphoneTreeHeaderAdapter{
//Sampledataset.children[i]containsthechildren(String[])for
//groups[i].
privateHashMapgroupStatusMap;
privateString[]groups={"第一组","第二组","第三组","第四组"};
privateString[][]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"}};
publicIphoneTreeViewAdapter(){
//TODOAuto-generatedconstructorstub
groupStatusMap=newHashMap();
}
publicObjectgetChild(intgroupPosition,intchildPosition){
returnchildren[groupPosition][childPosition];
}
publiclonggetChildId(intgroupPosition,intchildPosition){
returnchildPosition;
}
publicintgetChildrenCount(intgroupPosition){
returnchildren[groupPosition].length;
}
publicObjectgetGroup(intgroupPosition){
returngroups[groupPosition];
}
publicintgetGroupCount(){
returngroups.length;
}
publiclonggetGroupId(intgroupPosition){
returngroupPosition;
}
publicbooleanisChildSelectable(intgroupPosition,intchildPosition){
returntrue;
}
publicbooleanhasStableIds(){
returntrue;
}
@Override
publicViewgetChildView(intgroupPosition,intchildPosition,
booleanisLastChild,ViewconvertView,ViewGroupparent){
//TODOAuto-generatedmethodstub
if(convertView==null){
convertView=mInflater.inflate(R.layout.list_item_view,null);
}
TextViewtv=(TextView)convertView
.findViewById(R.id.www.hunanwang.net.nettact_list_item_name);
tv.setText(getChild(groupPosition,childPosition).toString());
TextViewstate=(TextView)convertView
.findViewById(R.id.cpntact_list_item_state);
state.setText("爱生活...爱Android...");
returnconvertView;
}
@Override
publicViewgetGroupView(intgroupPosition,booleanisExpanded,
ViewconvertView,ViewGroupparent){
//TODOAuto-generatedmethodstub
if(convertView==null){
convertView=mInflater.inflate(R.layout.list_group_view,null);
}
TextViewgroupName=(TextView)convertView
.findViewById(R.id.group_name);
groupName.setText(groups[groupPosition]);
ImageViewindicator=(ImageView)convertView
.findViewById(R.id.group_indicator);
TextViewonlineNum=(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);
}
returnconvertView;
}
@Override
publicintgetTreeHeaderState(intgroupPosition,intchildPosition){
finalintchildCount=getChildrenCount(groupPosition);
if(childPosition==childCount-1){
returnPINNED_HEADER_PUSHED_UP;
}elseif(childPosition==-1
&&!iphoneTreeView.isGroupExpanded(groupPosition)){
returnPINNED_HEADER_GONE;
}else{
returnPINNED_HEADER_VISIBLE;
}
}
@Override
publicvoidconfigureTreeHeader(Viewheader,intgroupPosition,
intchildPosition,intalpha){
//TODOAuto-generatedmethodstub
((TextView)header.findViewById(R.id.group_name))
.setText(groups[groupPosition]);
((TextView)header.findViewById(R.id.online_count))
.setText(getChildrenCount(groupPosition)+"/"
+getChildrenCount(groupPosition));
}
@Override
publicvoidonHeadViewClick(intgroupPosition,intstatus){
//TODOAuto-generatedmethodstub
groupStatusMap.put(groupPosition,status);
}
@Override
publicintgetHeadViewClickStatus(intgroupPosition){
if(groupStatusMap.containsKey(groupPosition)){
returngroupStatusMap.get(groupPosition);
}else{
return0;
}
}
}
}
好了,简单的一个例子就完成了,
总结一下:
原理:在正在显示的最上面的组的标签位置添加一个和组视图完全一样的视图,作为组标签。这个标签的位置要随着列表的滑动不断变化,以保持总是显示在最上方,并且该消失的时候就消失。给这个标签添加点击事件,实现打开和关闭分组的功能。
组标签总是显示在上方,这是通过不断的调整其在布局中的位置来实现的。这个调整的过程,在初始化的时候,在onLayout方法中实现一次,后面都是在滚动过程中,根据对滚动状态的监听来实现的。
实例化要添加的标签的时候(在外面实现,即使调用setTreeHeaderView之前),parent要设为该ExpandableListView.
要学习以及好好理解这个,最好的方法是将添加进来的组标签设为半透明,便于观察整个过程。
|
|