android自定义下拉刷新上拉更新的ListView

引言

之前做安卓项目,都没有绕过下拉刷新上拉更新的ListView这个需求,每次遇到这样的问题,都是从网上找开源的ListView或者用回以前用过的开源ListView来实现,现在大三出来实习了,公司项目又遇到这样的问题,因此决定自己动手自定义一个来用。

效果图

下拉刷新

下拉刷新

上拉更新

上拉更新

实现原理

简单的实现方法是,为ListView添加HeaderView和FooterView,然后将HeaderView和FooterView隐藏起来,等检测到下拉动作或上拉动作的时候,将相应的View显示出来。
本例中,HeaderView和FooterView采用不同的隐藏和显示方式,HeaderView通过setPadding方法隐藏在第一项中,而FooterView则是直接setVisibility(View.GONE);来隐藏的,初始化的代码如下:

 public void initView(Context context) {
        //关闭View的OverScroll
        setOverScrollMode(OVER_SCROLL_NEVER);
        //设置滑动监听
        setOnScrollListener(this);
        //加载头尾布局
        headerView = (RelativeLayout) LayoutInflater.from(context).inflate(R.layout.layout_header,this,false);
        footerView = (RelativeLayout) LayoutInflater.from(context).inflate(R.layout.layout_footer, this,false);
        progressWheel = (ProgressWheel) headerView.findViewById(R.id.progressWheel);
        progressBar = (ProgressBar) footerView.findViewById(R.id.progressBar);
        //测量头布局的高度
        measureView(headerView);
        headerViewHeight = headerView.getMeasuredHeight();
        //添加到ListView
        addHeaderView(headerView);
        addFooterView(footerView);
        //隐藏headerView到第一项里面
        headerView.setPadding(0, -headerViewHeight, 0, 0);
        //直接隐藏footerView
        footerView.setVisibility(View.GONE);
        //初始化默认状态量
        state = DONE;
        isRefreable = false;
        isRecord = false;
        isUpdatable = false;
    }

初始化界面之后,我们就可以开始检测上拉和下拉动作了,不过在检测之前,我们还要确定当前状态是否可以刷新或更新,令我们的ListView继承OnScrollListener,然后重写onScroll方法:

 @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
        View lastVisibleItemView = this.getChildAt(this.getChildCount() - 1);
        View firstVisibleItemView = this.getChildAt(0);
        if (firstVisibleItem == 0 && firstVisibleItemView 
                && firstVisibleItemView.getTop() == 0) {
            isRefreable = true;
        } else if ((firstVisibleItem + visibleItemCount) == totalItemCount
                && lastVisibleItemView != null) {
            isUpdatable = true;
        } else {
            isRefreable = false;
            isUpdatable = false;
        }
    }

做好准备之后,关键部分就是要重写onTouchEvent方法,来实现对用户的滑动监听。首先我们要把ListView的状态细分为7个,分别是:

private static final int DONE = 0; // 已完成状态
private static final int PULL_TO_REFRESH = 1; // 下拉刷新状态
private static final int OK_TO_REFRESH = 2; // 可以刷新的状态
private static final int REFRESHING = 3; // 正在刷新状态
private static final int PULL_TO_UPDATE = 4; // 上拉更新状态
private static final int OK_TO_UPDATE = 5; // 可以更新的状态
private static final int UPDATING = 6; // 正在更新状态

DONE(已完成状态):就是正常状态下的ListView。
PULL_TO_REFRESH(下拉刷新状态):下拉ListView时,HeaderView被拖出来但是又没有拖到可以刷新的状态。
OK_TO_REFRESH(可以刷新状态):下拉ListView时,手指移动距离大于HeaderView的高度,HeaderView被完整的拖出来的状态。
REFRESHING(正在刷新状态):当ListView处于可以刷新状态的时候手指释放了,就进入正在刷新状态,HeaderView被固定的显示出来。
上拉更新的状态同下拉刷新,就不一一阐述了。
重写OnTouchEvent的代码如下:

@Override
    public boolean onTouchEvent(MotionEvent ev) {
        //如果当前状态是正在刷新或正在更新,则返回
        if (state == REFRESHING || state == UPDATING) {
            return super.onTouchEvent(ev);
        }
        switch (ev.getAction()) {
            //当用户按下手指
            case MotionEvent.ACTION_DOWN:
                //如果没有记录过初始Y坐标,则记录
                if (!isRecord) {
                    isRecord = true;
                    startY = ev.getY();
                }
                break;
            //当用户拖动手指
            case MotionEvent.ACTION_MOVE:
                //再次判断是否记录了初始Y坐标,若没记录则记录
                if (!isRecord) {
                    isRecord = true;
                    startY = ev.getY();
                }
                //计算出y的偏移量
                offsetY = ev.getY() - startY;
                //如果处于已完成状态
                if (state == DONE && isRecord) {
                    if (offsetY < 0) {
                        if (isUpdatable) {
                            // 将状态改为上拉更新状态
                            state = PULL_TO_UPDATE;
                            //显示尾部
                            footerView.setVisibility(View.VISIBLE);
                        }
                    } else {
                        if (isRefreable) {
                            //将状态改为下拉更新状态
                            state = PULL_TO_REFRESH;
                        }
                    }
                }
                //如果可以更新,并且处在上拉状态或可以更新状态中
                if (state == PULL_TO_UPDATE || state == OK_TO_UPDATE && isUpdatable && isRecord) {
                    //如果偏移量<0 (即手指往上拖动)
                    if (offsetY < 0) {
                        //根据偏移量计算出进度条的进度
                        int progress = Math.abs((int) (offsetY / RATIO));
                        //如果进度条的值超过了120(100为刚好合并的状态),就将状态量设置为
                        if (progress > 120) {
                            progress = 120;
                            state = OK_TO_UPDATE;
                        } else {
                            state = PULL_TO_UPDATE;
                        }
                        //根据进度值绘制进度条(让进度条跟随手指的拖动变化)
                        progressBar.setProgress(progress);
                    }
                }
                //如果可以刷新,并且处于下拉状态或者可以刷新状态中
                if (state == PULL_TO_REFRESH || state == OK_TO_REFRESH && isRefreable && isRecord) {
                    setSelection(0);
                    //如果当前滑动的距离小于headerView的高度
                    if (offsetY / RATIO < headerViewHeight) {
                        state = PULL_TO_REFRESH;
                    } else {
                        state = OK_TO_REFRESH;
                    }
                    headerView.setPadding(0, (int) (-headerViewHeight + offsetY
                            / RATIO), 0, 0);
                }
                break;
            // 当用户手指抬起时
            case MotionEvent.ACTION_UP:
                // 如果当前状态为下拉未到刷新位置状态,则不刷新
                if (state == PULL_TO_REFRESH) {
                    //将状态设置为已完成
                    state = DONE;
                    // 隐藏headerView
                    headerView.setPadding(0, -headerViewHeight, 0, 0);
                }
                // 如果当前状态为可以刷新状态
                if (state == OK_TO_REFRESH) {
                    //将状态设置为正在刷新
                    state = REFRESHING;
                    //将headerView放到刚好刷新的位置
                    headerView.setPadding(0,0,0,0);
                }
                // 如果当前状态为上拉未到刷新位置状态,则不更新
                if (state == PULL_TO_UPDATE) {
                    //将状态设置为已完成
                    state = DONE;
                    // 隐藏footerView
                    footerView.setVisibility(View.GONE);
                    //重置progressBar
                    progressBar.setProgress(0);
                    progressBar.stopAnimation();
                }
                //如果当前状态为可以更新状态
                if (state == OK_TO_UPDATE) {
                    //将状态设置为正在刷新
                    state = UPDATING;
                    //开启进度条动画
                    progressBar.startAnimation();
                }
                //将记录y坐标的isRecord改为false,以便于下一次手势的执行
                isRecord = false;
                break;
        }
        return super.onTouchEvent(ev);
    }

另外不要忘了做回调接口

 private OnRefreshUpdateListener listener; //回调接口

    public interface OnRefreshUpdateListener {
        void onRefresh();

        void onUpdate();
    }

    public void setOnRefreshUpdateListener(OnRefreshUpdateListener onRefreshUpdateListener) {
        listener = onRefreshUpdateListener;
    }

    /**
     * 刷新完毕之后,主线程调用该方法隐藏头部布局
     */
    public void setRefreshComplete() {
        //将状态量设置为已完成
        state = DONE;
        //隐藏头部布局
        headerView.setPadding(0, -headerViewHeight, 0, 0);
    }

    /**
     * 更新完毕之后,主线程调用该方法隐藏尾部布局
     */
    public void setUpdateComplete() {
        //将状态量设置为已完成
        state = DONE;
        //隐藏尾部布局
        footerView.setVisibility(GONE);
        //停止进度条动画
        progressBar.stopAnimation();
    }

完整代码

完整代码我上传到了Github上,注释都写好了,研究起来很方便
Github地址

关于HeaderView中的ProgressWheel,我是用的一个开源的Material Design风格的LoadingView,Github地址是:
ProgressWheel

关于FooterView中的ProgressBar,是我自定义控件实现的,有兴趣的可以看看,原理不难。

备注

由于添加了HeaderView,在计算position的时候,第0项是HeaderView,而第1项才是用户传进去的数组的第0项,在ListView.OnItemClickListener事件中,需对position进行-1操作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值