Android:SwipeRefreshView嵌套DockingExpandableListView 悬停标题外加上拉刷新下拉加载

我这个是参考之前的哥们做的,跟我们项目需求很类似,有些出入,我做了些改动,并嵌套了上了刷新和下拉加载功能,嵌套过程终于到了些问题,并都已解决。这是原始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);
        }
    }

 

 

三、重写dispatchDraw()方法

悬停标题是画上去的,而不是加到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;
    }@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);
    } @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);
            }
        });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的数据源我就不贴了,还有不明白的可以看下面源码,也可以给我私信,希望我的版本能帮你解决问题,谢谢!!

 

 

 

 

 

 

 

 

 

 

 

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值