android 下拉刷新+底部加载更多 view组件封装

前:本文为QiaoJim原创,转载请附原文链接,谢谢合作!

http://blog.csdn.net/qiao_jim/article/details/79076518

-----------------------------------------------------------------------------------------------

本篇主要封装一个自定义view,主要为了满足:1、下拉刷新;2、底部加载更多;3、支持许多自定义属性

本片主要讲解封装思路,想快速使用的请戳:android 下拉刷新+底部加载更多 QJPageReloadView使用

源码GitHub地址:QiaoJim/QJViews

需要读者对于android touch事件分发拦截机制有一定的了解,如果不是很懂,推荐一个博客:

Android 编程下 Touch 事件的分发和消费机制

-----------------------------------------------------------------------------------------------

一、整体思路

1、QJPageReloadView包含:顶部下拉刷新view(QJHeaderView)+listview+底部加载更多view(TextView)

2、(简单考虑事件拦截)

当listview已到达顶部,继续下滑,则拦截事件,加载下拉刷新view(QJHeaderView);

当listview已到达底部,继续上滑,则拦截事件,加载底部的加载更多view;

其他,不拦截,任其传递给listview处理

3、在拦截后是下拉刷新动作时,回调onRefresh()方法,自定义刷新逻辑;

4、在拦截后是加载更多动作时,回调onLoadMore()方法,自定义加载更多逻辑(根据已显示的totalCount可支持分页加载);


二、加载3个view

我们的view包含3个view,我们继承线性布局,然后竖向一次添加3个view即可。

我们在构造方法里加载自定义属性后(略),便可在onFinishInflate()方法中,调用加载我们3个view的方法(如下)。

@Override
protected void onFinishInflate() {
    super.onFinishInflate();
    initChildView();
}

/*
* 实例子view,依次添加
* 1.顶部下拉刷新view
* 2.中间的listview
* 3.底部加载更多view*/
private void initChildView() {
    setOrientation(VERTICAL);
    addHeaderView();//顶部下拉刷新view
    addListView();
    addFooterView();//底部加载更多view
}
QJHeaderView这里我包含1个圆形进度条和1个 textview,textview在不同的状态下,显示不同的提示文字,如下拉刷新,释放立即刷新。

中间就是一个listview,底部是1个textview,显示如加载更多,正在加载等提示信息。均调用addView()方法即可。

(由于这里主要将思路,具体内部一些细节就展示了,感兴趣可以参照GitHub源码)


三、touch事件的拦截

首先事件分发默认即可,因为我们将3个view封装成一个大的view(即viewgroup),所以默认就会把事件分发给我们QJPageReloadView的onInterceptTouchEvent()方法。

onInterceptTouchEvent()中,我们需要根据当前的touch事件,判断当前的动作,进而判断是否拦截该事件。

(具体何时拦截,第一部分整体思路已简单说明,下面给出源码)

    /*
    * 1.列表到达顶部,且继续下拉,则拦截事件,加载下拉刷新view,返回true
    * 2.列表到达底部,且继续上滑,则拦截事件,加载加载更多view,返回true
    * 3.其他不拦截,给子listview处理。返回false,交给子 view 的 dispatchTouchEvent()*/
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        boolean intercept = false;
        boolean listViewScrolling = false;

        float curX = 0, curY = 0;

        //滑动事件判定
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
//                Log.e(TAG, "======== onInterceptTouchEvent ==========\tdown");
                curX = preX = ev.getX();
                curY = preY = ev.getY();
                break;

            case MotionEvent.ACTION_MOVE:
//                Log.e(TAG, "======== onInterceptTouchEvent ==========\tmove");
                curX = ev.getX();
                curY = ev.getY();

                //加载更多view不可见时,全屏幕监听滑动事件
                if (!footerViewVisible()) {
                    if (curY - preY > MIN_DELTA_Y) {
                        listViewScrolling = true;
                        moveDown = true;
                    } else if (preY - curY > MIN_DELTA_Y) {
                        listViewScrolling = true;
                        moveUp = true;
                    }
                }
                // 加载更多view可见时,监听view区域外的滑动事件。
                // 若touch区域在加载更多的view区域,不拦截事件,应该为view的点击事件
                else if (!touchInFooterView(ev)) {
                    if (curY - preY > MIN_DELTA_Y) {
                        listViewScrolling = true;
                        moveDown = true;
                    } else if (preY - curY > MIN_DELTA_Y) {
                        listViewScrolling = true;
                        moveUp = true;
                    }
                }

                break;
        }
//        Log.e(TAG, "======== onInterceptTouchEvent ==========\n下滑手势:" + moveDown + "        上滑手势:" + moveUp);

        if (listViewArriveTop() && moveDown && !loading && refreshEnable) {
            curAction = QJViewAction.ACTION_REFRESH;
            intercept = true;
        } else if (listViewArriveBottom() && moveUp && !loading && loadMoreEnable) {
            curAction = QJViewAction.ACTION_LOAD_MORE;
            intercept = true;
        } else {
            curAction = QJViewAction.ACTION_UNDEFINED;

            if (listViewScrolling && !loading)
                resetFooterView(QJViewState.CLEAR);
        }

//        if (intercept) {
//            Log.e(TAG, "======== onInterceptTouchEvent ==========\n拦截:" + intercept);
//        }

        // 重置上滑和下拉标记
        resetActionTag();
        // 改变pre的 X,Y 坐标
        resetPreCoordinate(curX, curY);

        return intercept;
    }

虽然有不少判断,大体上看主要的:

(1)ACTION_MOVE时,判断是上滑还是下滑,这是moveUp和moveDown标记(有一个滑动最小值,防止点击事件的误判);

(2)51和54行,对listview到达位置的判断和滑动动作结合的判断,设置当前动作,并控制是否拦截intercept标记;

51行:listview到达顶部,且是下滑动作,设置当前动作为下拉刷新;

54行:listview到达底部,且是上滑动作,设置当前动作为加载更多;

(3)当底部加载更多的view已经显示时,touch在FooterView区域时,我们不应该拦截。因为此时期望是点击底部的加载更多view。


四、touch事件的处理

onTouchEvent()中我们主要要处理ACTION_MOVE和ACTION_UP/CANCEL这2类事件。


1、ACTION_MOVE时,主要针对顶部的下拉刷新view,即判断为下拉刷新动作,继续下拉,我们期望我们的HeaderView

高度也随着下拉的距离而增加。并且如果用户又滑动回去,我们期望判断这是一个放弃下拉刷新的动作。所以我们需要进

行相应处理和控制。我们在ACTION_MOVE时,调用handleMoving()方法(如下)。

   /*
    * 处理滑动事件,调节view的位置*/
    private boolean handleMoving(MotionEvent event) {

        float curX = event.getX();
        float curY = event.getY();
        float deltaY = curY - preY;//下滑y轴的增量

        //当前动作为下拉刷新时,才操作下拉刷新的view
        if (curAction == QJViewAction.ACTION_REFRESH) {
            //测量加载更多view的高度
            int headerViewHeight = measureHeaderViewHeight(deltaY);
            //记录是否放弃下拉刷新操作
            cancelRefresh = headerViewHeight < refreshMinHeight;
        }

        resetPreCoordinate(curX, curY);
        return true;
    }
其中先记录y轴下滑距离的增量,并传入measureHeaderViewHeight()方法,方法就是改变顶部下拉刷新view的高度,细节略。

然后判断下拉总距离(同时也是HeaderView的高度)是否大于最小的临界值,如果小,这判定为放弃下拉刷新。否则释放手指后,开始刷新方法的回调。


2、ACTION_UP/CANCEL时,即一次动作完成,我们需要根据当前动作,对相应的view做出一些操作。下面是主要的逻辑:

(1)下拉刷新动作时,在没有放弃下拉刷新时,我们回调接口的onRefresh()方法,同时设置HeaderView的状态为Loading。

(2)加载更多时,如果用户设置了“自动加载”为true,则直接回调onLoadMore()方法,同时设置FooterView的状态为Loading。如果用户没有设置自动加载,则显示加载更多view即可。当用户点击该view时,再回调onLoadMore()方法即可

(3)下面的demo有控制,如果已经在加载数据,可忽略新的加载动作,具体参考下面loading标记的控制。

    /*
    * 处理拦截下来的一次touch事件*/
    private boolean handleOnceTouch() {

        boolean handled = false;

        //回调接口不为null,确定回调函数
        if (curAction == QJViewAction.ACTION_REFRESH) {
            if (qjPageReloadViewListener != null) {
                //没有放弃下拉刷新动作。若手动滑上去,则判定为不刷新
                //若已经正在刷新,则屏蔽此次动作
                if (!cancelRefresh && !loading) {
                    qjPageReloadViewListener.onStart();
                    QJReloadTask.newInstance(this).execute(QJViewAction.ACTION_REFRESH);
                    loading = true;
                    resetHeaderView(QJViewState.LOADING);
                }
            }
            handled = true;
        } else if (curAction == QJViewAction.ACTION_LOAD_MORE) {

            if (qjPageReloadViewListener != null) {
                if (autoLoadMore && !loading) {
                    qjPageReloadViewListener.onStart();
                    QJReloadTask.newInstance(this).execute(QJViewAction.ACTION_LOAD_MORE);
                    loading = true;
                    resetFooterView(QJViewState.LOADING);
                } else if (!loading)
                    resetFooterView(QJViewState.CREATE);
            }
            handled = true;
        }
        return handled;
    }

五、向外部提供使用方法


可能主要有(基于我的封装):

1、设置回调接口的实现

2、设置listview的adapter

3、设置一些自定义属性,如autoLoad等

4、获取列表已包含的个数

5、更新列表数据







猜你喜欢

转载自blog.csdn.net/Qiao_Jim/article/details/79076518
今日推荐