Android:SwipeRefreshView嵌套DockingExpandableListView 悬停标题外加上拉刷新下拉加载
来源:互联网 发布:被猎头找到 知乎 编辑:程序博客网 时间:2024/05/22 07:11
我这个是参考之前的哥们做的,跟我们项目需求很类似,有些出入,我做了些改动,并嵌套了上了刷新和下拉加载功能,嵌套过程终于到了些问题,并都已解决。这是原始DockingExpandableListView悬停标题文章的链接
http://blog.csdn.net/turkeycock/article/details/53471262
下面给大家看我的演示图:
注明下,我这个是默认展开,且不可折叠的,我把折叠点击事件禁止了,如有需要,找不到的可以联系我
一· SwipeRefreshView嵌套DockingExpandableListView的滑动冲突问题
这个问题已经在定义的SwipeRefreshView解决了,重写listview的SetOnScrollListener
mListView.setOnScrollListener(new AbsListView.OnScrollListener() { @Override public void onScrollStateChanged(AbsListView view, int scrollState) { // 移动过程中判断时候能下拉加载更多 if (canLoadMore()) { // 加载数据 loadData(); } } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { //解决SwipeRefreshView嵌套DockingExpandableListView引起的滑动冲突和父标题悬停滚动效果失效问题 boolean enable = false; if (mListView != null && mListView.getChildCount() > 0) { // check if the first item of the list is visible boolean firstItemVisible = mListView.getFirstVisiblePosition() == 0; // check if the top of the first item is visible boolean topOfFirstItemVisible = mListView.getChildAt(0).getTop() == 0; // enabling or disabling the refresh layout enable = firstItemVisible && topOfFirstItemVisible; } setEnabled(enable);//设置SwipeRefreshView是否获取焦点 //当listview重新获得焦点的时候,绘制滚动悬停效果 long packedPosition = mListView.getExpandableListPosition(firstVisibleItem); int groupPosition = mListView.getPackedPositionGroup(packedPosition); int childPosition = mListView.getPackedPositionChild(packedPosition); // update header view based on first visible item // IMPORTANT: refer to getPackedPositionChild(): // If this group does not contain a child, returns -1. Need to handle this case in controller. mListView.updateDockingHeader(groupPosition, childPosition); } });
二·重写onMeasure()和onLayout()方法
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); if (mDockingHeader != null) { measureChild(mDockingHeader, widthMeasureSpec, heightMeasureSpec); mDockingHeaderWidth = mDockingHeader.getMeasuredWidth(); mDockingHeaderHeight = mDockingHeader.getMeasuredHeight(); } } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); if (mDockingHeader != null) { mDockingHeader.layout(0, 0, mDockingHeaderWidth, mDockingHeaderHeight); } }
悬停标题是画上去的,而不是加到view hierarchy里去的。因此,需要在完成其他子view的绘制之后,再把悬停标题栏画上去:
@Override protected void dispatchDraw(Canvas canvas) { super.dispatchDraw(canvas); if (mDockingHeaderVisible) { // draw header view instead of adding into view hierarchy drawChild(canvas, mDockingHeader, getDrawingTime()); } }
四、根据滚动状态决定如何绘制悬停标题
这个问题我写到SwipeRefreshView中了
滚动到不同位置,悬停标题的显示是不同的,因此需要根据滚动状态定义一个状态机的切换。让SwipeRefreshView中的listview对象实现OnScrollListener监听,并重写onScroll()方法:
最为关键的updateDockingHeader()方法,根据状态机来确定如何绘制悬停标题。在看这个方法之前,我们先看一下有哪几种状态,定义在IDockingController里:
public interface IDockingController { int DOCKING_HEADER_HIDDEN = 1; int DOCKING_HEADER_DOCKING = 2; int DOCKING_HEADER_DOCKED = 3; int getDockingState(int firstVisibleGroup, int firstVisibleChild);}
一共3种状态,含义参见下图:
DOCKING_HEADER_HIDDEN:当分组没有展开,或者组里没有子项的时候,是不需要绘制悬停标题的
DOCKING_HEADER_DOCKING:当滚动到上一个分组的最后一个子项时,需要把旧的标题“推”出去,“停靠”新的标题,所以这个状态命名为“docking”
DOCKING_HEADER_DOCKED:新标题“停靠”完毕,在该分组内部滚动,称为“docked”状态
基于这个状态机,我们来看一下updateDockingHeader()方法的实现:
private void updateDockingHeader(int groupPosition, int childPosition) { if (getExpandableListAdapter() == null) { return; } if (getExpandableListAdapter() instanceof IDockingController) { IDockingController dockingController = (IDockingController)getExpandableListAdapter(); mDockingHeaderState = dockingController.getDockingState(groupPosition, childPosition); switch (mDockingHeaderState) { case IDockingController.DOCKING_HEADER_HIDDEN: mDockingHeaderVisible = false; break; case IDockingController.DOCKING_HEADER_DOCKED: if (mListener != null) { mListener.onUpdate(mDockingHeader, groupPosition, isGroupExpanded(groupPosition)); } // Header view might be "GONE" status at the beginning, so we might not be able // to get its width and height during initial measure procedure. // Do manual measure and layout operations here. mDockingHeader.measure( MeasureSpec.makeMeasureSpec(mDockingHeaderWidth, MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(mDockingHeaderHeight, MeasureSpec.AT_MOST)); mDockingHeader.layout(0, 0, mDockingHeaderWidth, mDockingHeaderHeight); mDockingHeaderVisible = true; break; case IDockingController.DOCKING_HEADER_DOCKING: if (mListener != null) { mListener.onUpdate(mDockingHeader, groupPosition, isGroupExpanded(groupPosition)); } View firstVisibleView = getChildAt(0); int yOffset; if (firstVisibleView.getBottom() < mDockingHeaderHeight) { yOffset = firstVisibleView.getBottom() - mDockingHeaderHeight; } else { yOffset = 0; } // The yOffset is always non-positive. When a new header view is "docking", // previous header view need to be "scrolled over". Thus we need to draw the // old header view based on last child's scroll amount. 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实现了该接口和方法,完成状态机状态转换:
@Override public int getDockingState(int firstVisibleGroup, int firstVisibleChild) { // No need to draw header view if this group does not contain any child & also not expanded. if (firstVisibleChild == -1 && !mListView.isGroupExpanded(firstVisibleGroup)) { return DOCKING_HEADER_HIDDEN; } // Reaching current group's last child, preparing for docking next group header. if (firstVisibleChild == getChildrenCount(firstVisibleGroup) - 1) { return IDockingController.DOCKING_HEADER_DOCKING; } // Scrolling inside current group, header view is docked. return IDockingController.DOCKING_HEADER_DOCKED; }
逻辑非常简单清晰:
如果当前group没有子项,并且也不是展开状态,就返回DOCKING_HEADER_HIDDEN状态,不绘制悬停标题;
如果到达了当前group的最后一个子项,进入DOCKING_HEADER_DOCKING状态;
其他情况,在当前group内部滚动,返回DOCKING_HEADER_DOCKED状态。
六、Touch事件处理
文章最前面提到过,这个标题视图是画上去,而不是添加到view hierarchy里的,因此它是无法响应touch事件的!那就需要我们自己根据点击区域进行判断了,需要重写onInterceptTouchEvent()和onTouchEvent()方法,又因为我这里需求是不折叠的,所以我把点击事件都禁掉了:
@Override public boolean onInterceptTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN && mDockingHeaderVisible) { Rect rect = new Rect(); mDockingHeader.getDrawingRect(rect); if (rect.contains((int)ev.getX(), (int)ev.getY()) && mDockingHeaderState == IDockingController.DOCKING_HEADER_DOCKED) { // Hit header view area, intercept the touch event return false; } } return super.onInterceptTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent ev) { if (mDockingHeaderVisible) { Rect rect = new Rect(); mDockingHeader.getDrawingRect(rect); switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: if (rect.contains((int)ev.getX(), (int)ev.getY())) { // forbid event handling by list view's item return true; } break; case MotionEvent.ACTION_UP: long flatPostion = getExpandableListPosition(getFirstVisiblePosition()); int groupPos = ExpandableListView.getPackedPositionGroup(flatPostion); if (rect.contains((int)ev.getX(), (int)ev.getY()) && mDockingHeaderState == IDockingController.DOCKING_HEADER_DOCKED) { // handle header view click event (do group expansion & collapse)// if (isGroupExpanded(groupPos)) {// collapseGroup(groupPos);// } else {// expandGroup(groupPos);// } return false; } break; } } return super.onTouchEvent(ev); }
七、更新标题视图内容
前面已经完成了悬停标题状态机的控制,但是具体标题栏上应该怎么显示(比如变更标题文字、显示收缩展开图标等等),需要用户来处理。因此定义了一个IDockingHeaderUpdateListener接口,用户需要实现onUpdate()方法,根据当前的group ID以及收缩展开状态决定如何更新悬停标题视图:
public interface IDockingHeaderUpdateListener { void onUpdate(View headerView, int groupPosition, boolean expanded);}我在DemoDockingAdapterDataSource加了一个方法,更新标题可以这样动态设置:
public String getGroupname(int groupPosition) { if (mGroups.get(groupPosition) != null) { return mGroups.get(groupPosition); } return null; }然后在MainActivity中调用,这样也去分了分页加载的不同父级标题,父级标题名字如果相同,默认是不显示的:
listView.setDockingHeader(headerView, new IDockingHeaderUpdateListener() { @Override public void onUpdate(View headerView, int groupPosition, boolean expanded) { String groupTitle = listData.getGroupname(groupPosition); TextView titleView = (TextView) headerView.findViewById(R.id.group_view_title); titleView.setText(groupTitle); } });
Adapter的数据源我就不贴了,还有不明白的可以看下面源码,也可以给我私信,希望我的版本能帮你解决问题,谢谢!!
- Android:SwipeRefreshView嵌套DockingExpandableListView 悬停标题外加上拉刷新下拉加载
- Android ListView 下拉刷新 上拉加载
- android 上拉刷新,下拉加载更多
- android listview 上拉加载 下拉刷新
- Android下拉刷新,上拉加载。
- Android--listview下拉刷新,上拉加载
- Android-PullToRefresh下拉刷新,上拉加载
- Android下拉刷新,上拉加载
- android 下拉刷新,上拉加载
- Android 上拉刷新下拉加载
- android上拉刷新下拉加载
- Android 自定义下拉刷新上拉加载
- android上拉加载+下拉刷新
- android listview上拉加载,下拉刷新
- Android下拉刷新上拉加载记录
- Android 上拉加载下拉刷新
- android最新上拉加载,下拉刷新
- Android ListView下拉刷新上拉加载
- 人工智能领域的几个的技术大牛博客
- 安装Docker Compose
- 2017hdu新生赛 1006 稿件整理
- Struts2基本的执行过程
- MissingResourceException: Can't find resource for bundle java....错误解决
- Android:SwipeRefreshView嵌套DockingExpandableListView 悬停标题外加上拉刷新下拉加载
- iOS 内存管理规则中的例外
- D3.js学习中总结的那些金句
- JS正则表达式元字符
- Mysql主键 外键笔记:一起来简单谈谈sql语句
- jsp中使用<jsp:include />在切换tomcat版本时遇到的问题
- ireport各个版本的下载地址分享
- 日期格式化yyyy年MM月dd日aHH:mm
- 转载:基于java的网络爬虫框架(实现京东数据的爬取,并将插入数据库)