Android自定义ViewGroup(二)-带悬停标题的ExpandableListView_第1页
Android自定义ViewGroup(二)-带悬停标题的ExpandableListView_第2页
Android自定义ViewGroup(二)-带悬停标题的ExpandableListView_第3页
Android自定义ViewGroup(二)-带悬停标题的ExpandableListView_第4页
Android自定义ViewGroup(二)-带悬停标题的ExpandableListView_第5页
已阅读5页,还剩3页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

Android自定义ViewGroup(二)——带悬停标题的ExpandableListView也就是说,在某一个分组内部滚动时,要求分组标题悬停,当滚出该分组范围时,把标题顶出去,悬停下一个分组的标题。正好看到一个比较有趣的思路,做了一个实现,在这里分享一下。代码结构如下,基本上是一个MVC的架构:既然是点击可收缩展开的列表,显然要用ExpandableListView,关于这个类的用法这里就不赘述了,网上一搜一大把,其实跟ListView的用法差不多,不过它帮你分了组,所以原来Adapter里的getView()就变成了getGroupView()和getChildView(),getCount()就变成了getGroupCount()等等。另外既然要支持收缩展开,必然会提供collapseGroup()和expandGroup()等接口。下面分析如何添加悬停标题,其实精华部分就一句话:悬停标题是画上去的,而不是加到viewhierarchy里去,具体根据滚动的情况确定如何画。首先我们来写一个DockingExapandableListView类,继承自ExpandableListView,包含一个View类型的成员变量mDockingHeader。一、重写onMeasure()和onLayout()方法[java]viewplaincopy在CODE上查看代码片派生到我的代码片@OverrideprotectedvoidonMeasure(intwidthMeasureSpec,intheightMeasureSpec){super.onMeasure(widthMeasureSpec,heightMeasureSpec);if(mDockingHeader!=null){measureChild(mDockingHeader,widthMeasureSpec,heightMeasureSpec);mDockingHeaderWidth=mDockingHeader.getMeasuredWidth();mDockingHeaderHeight=mDockingHeader.getMeasuredHeight();}}@OverrideprotectedvoidonLayout(booleanchanged,intl,intt,intr,intb){super.onLayout(changed,l,t,r,b);if(mDockingHeader!=null){mDockingHeader.layout(0,0,mDockingHeaderWidth,mDockingHeaderHeight);}}这个比较简单,就是测量一下这个标题视图的宽度和高度。二、重写dispatchDraw()方法上面提到,悬停标题是画上去的,而不是加到viewhierarchy里去的。因此,需要在完成其他子view的绘制之后,再把悬停标题栏画上去:[java]viewplaincopy在CODE上查看代码片派生到我的代码片@OverrideprotectedvoiddispatchDraw(Canvascanvas){super.dispatchDraw(canvas);if(mDockingHeaderVisible){//drawheaderviewinsteadofaddingintoviewhierarchydrawChild(canvas,mDockingHeader,getDrawingTime());}}三、根据滚动状态决定如何绘制悬停标题滚动到不同位置,悬停标题的显示是不同的,因此需要根据滚动状态定义一个状态机的切换。让DockingExpandableListView实现OnScrollListener接口,并重写onScroll()方法:[java]viewplaincopy在CODE上查看代码片派生到我的代码片@OverridepublicvoidonScroll(AbsListViewview,intfirstVisibleItem,intvisibleItemCount,inttotalItemCount){longpackedPosition=getExpandableListPosition(firstVisibleItem);intgroupPosition=getPackedPositionGroup(packedPosition);intchildPosition=getPackedPositionChild(packedPosition);//updateheaderviewbasedonfirstvisibleitem//IMPORTANT:refertogetPackedPositionChild()://Ifthisgroupdoesnotcontainachild,returns-1.Needtohandlethiscaseincontroller.updateDockingHeader(groupPosition,childPosition);}这里有几个比较有意思的方法,都是ExpandableListView自带的API:getExpandableListPosition():这个API获得一个所谓的packedposition,是一个64位的值,高32位表示group的ID,低32位表示在这个group内部的childID。getPackedPositionGroup():获取groupID,也就是高32位getPackedPositionChild():获取childID,也就是低32位注意我们给getExpandableListPosition()传的参数是firstVisibleItem,因此我们就得到了最上方的第一个可见项所属的group以及组内位置。接下来就是最为关键的updateDockingHeader()方法,根据状态机来确定如何绘制悬停标题。在看这个方法之前,我们先看一下有哪几种状态,定义在IDockingController里:[java]viewplaincopy在CODE上查看代码片派生到我的代码片publicinterfaceIDockingController{intDOCKING_HEADER_HIDDEN=1;intDOCKING_HEADER_DOCKING=2;intDOCKING_HEADER_DOCKED=3;intgetDockingState(intfirstVisibleGroup,intfirstVisibleChild);}一共3种状态,这些状态都是什么含义呢?参见下图:DOCKING_HEADER_HIDDEN:当分组没有展开,或者组里没有子项的时候,是不需要绘制悬停标题的DOCKING_HEADER_DOCKING:当滚动到上一个分组的最后一个子项时,需要把旧的标题“推”出去,“停靠”新的标题,所以这个状态命名为“docking”DOCKING_HEADER_DOCKED:新标题“停靠”完毕,在该分组内部滚动,称为“docked”状态基于这个状态机,我们来看一下updateDockingHeader()方法的实现:[java]viewplaincopy在CODE上查看代码片派生到我的代码片privatevoidupdateDockingHeader(intgroupPosition,intchildPosition){if(getExpandableListAdapter()==null){return;}if(getExpandableListAdapter()instanceofIDockingController){IDockingControllerdockingController=(IDockingController)getExpandableListAdapter();mDockingHeaderState=dockingController.getDockingState(groupPosition,childPosition);switch(mDockingHeaderState){caseIDockingController.DOCKING_HEADER_HIDDEN:mDockingHeaderVisible=false;break;caseIDockingController.DOCKING_HEADER_DOCKED:if(mListener!=null){mListener.onUpdate(mDockingHeader,groupPosition,isGroupExpanded(groupPosition));}//Headerviewmightbe"GONE"statusatthebeginning,sowemightnotbeable//togetitswidthandheightduringinitialmeasureprocedure.//Domanualmeasureandlayoutoperationshere.mDockingHeader.measure(MeasureSpec.makeMeasureSpec(mDockingHeaderWidth,MeasureSpec.AT_MOST),MeasureSpec.makeMeasureSpec(mDockingHeaderHeight,MeasureSpec.AT_MOST));mDockingHeader.layout(0,0,mDockingHeaderWidth,mDockingHeaderHeight);mDockingHeaderVisible=true;break;caseIDockingController.DOCKING_HEADER_DOCKING:if(mListener!=null){mListener.onUpdate(mDockingHeader,groupPosition,isGroupExpanded(groupPosition));}ViewfirstVisibleView=getChildAt(0);intyOffset;if(firstVisibleView.getBottom()<mDockingHeaderHeight){yOffset=firstVisibleView.getBottom()-mDockingHeaderHeight;}else{yOffset=0;}//TheyOffsetisalwaysnon-positive.Whenanewheaderviewis"docking",//previousheaderviewneedtobe"scrolledover".Thusweneedtodrawthe//oldheaderviewbasedonlastchild'sscrollamount.mDockingHeader.measure(MeasureSpec.makeMeasureSpec(mDockingHeaderWidth,MeasureSpec.AT_MOST),MeasureSpec.makeMeasureSpec(mDockingHeaderHeight,MeasureSpec.AT_MOST));mDockingHeader.layout(0,yOffset,mDockingHeaderWidth,mDockingHeaderHeight+yOffset);mDockingHeaderVisible=true;break;}}}其中,是否显示悬停标题是通过一个叫做mDockingHeaderVisible的boolean变量控制的,这个在上面的dispatchDraw()方法里也见到了。重点看“docking”状态的处理:通过计算第一个可见项的bottom和高度之间的差异,也就是这个yOffset,确定悬停标题在y轴方向的偏移量。这样在绘制悬停标题的时候,我们就只能看到一部分,造成一种被“推出去”的感觉。四、悬停标题状态机在刚刚提到的那个IDockingController接口里有一个方法叫getDockingState(),在updateDockingHeader()方法里就是通过调用这个方法来确定当前悬停标题的状态的。DockingExpandableListViewAdapter实现了该接口和方法,完成状态机状态转换:[java]viewplaincopy在CODE上查看代码片派生到我的代码片@OverridepublicintgetDockingState(intfirstVisibleGroup,intfirstVisibleChild){//Noneedtodrawheaderviewifthisgroupdoesnotcontainanychild&alsonotexpanded.if(firstVisibleChild==-1&&!mListView.isGroupExpanded(firstVisibleGroup)){returnDOCKING_HEADER_HIDDEN;}//Reachingcurrentgroup'slastchild,preparingfordockingnextgroupheader.if(firstVisibleChild==getChildrenCount(firstVisibleGroup)-1){returnIDockingController.DOCKING_HEADER_DOCKING;}//Scrollinginsidecurrentgroup,headerviewisdocked.returnIDockingController.DOCKING_HEADER_DOCKED;}逻辑非常简单清晰:如果当前group没有子项,并且也不是展开状态,就返回DOCKING_HEADER_HIDDEN状态,不绘制悬停标题;如果到达了当前group的最后一个子项,进入DOCKING_HEADER_DOCKING状态;其他情况,在当前group内部滚动,返回DOCKING_HEADER_DOCKED状态。五、Touch事件处理文章最前面提到过,这个标题视图是画上去,而不是添加到viewhierarchy里的,因此它是无法响应touch事件的!那就需要我们自己根据点击区域进行判断了,需要重写onInterceptTouchEvent()和onTouchEvent()方法:[java]viewplaincopy在CODE上查看代码片派生到我的代码片@OverridepublicbooleanonInterceptTouchEvent(MotionEventev){if(ev.getAction()==MotionEvent.ACTION_DOWN&&mDockingHeaderVisible){Rectrect=newRect();mDockingHeader.getDrawingRect(rect);if(rect.contains((int)ev.getX(),(int)ev.getY())&&mDockingHeaderState==IDockingController.DOCKING_HEADER_DOCKED){//Hitheaderviewarea,interceptthetoucheventreturntrue;}}returnsuper.onInterceptTouchEvent(ev);}//Note:Asheaderviewisdrawntothecanvasinsteadofaddingintoviewhierarchy,//it'suselesstosetitstouchorclickeventlistener.Needtohandletheseinput//eventscarefullybyourselves.@OverridepublicbooleanonTouchEvent(MotionEventev){if(mDockingHeaderVisible){Rectrect=newRect();mDockingHeader.getDrawingRect(rect);switch(ev.getAction()){caseMotionEvent.ACTION_DOWN:if(rect.contains((int)ev.getX(),(int)ev.getY())){//forbideventhandlingbylistview'sitemreturntrue;}break;caseMotionEvent.ACTION_UP:longflatPostion=getExpandableListPosition(getFirstVisiblePosition());intgroupPos=ExpandableListView.getPackedPositionGroup(flatPostion);if(rect.contains((int)ev.getX(),(int)ev.getY())&&mDHeaderState==IDockingController.DOCKING_HEADER_DOCKED){//handleheaderviewclickevent(dogroupexpansion&collapse)if(isGroupExpanded(groupPos)){collapseGroup(groupPos);}else{expandGroup(groupPos);}returntrue;}break;}}returnsuper.onTouchEvent(ev);}这部分实现比较简单易懂,如果当前是DOCKING_HEADER_DOCKED状态,并且点击区域命中了标题视图的drawingrect,那么就需要拦截to

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论