美文网首页listView,RecyclerView
RecyclerView下拉刷新、上拉加载

RecyclerView下拉刷新、上拉加载

作者: 呼吸的蜗牛 | 来源:发表于2016-11-01 23:33 被阅读466次

    最近应公司项目需要,动手写了一个下拉刷新和上拉加载的自定义控件。之所以没有直接用网上的依赖库,一来是感觉会让项目变得更臃肿,而大多数酷炫的效果,根本就用不到;二来,也在网上找过类似的demo,但是看完之后又都感觉太复杂,最终决定还是自己动手写。从最开始构思、查阅资料到彻底完成,差不多用了2天时间,代码量总共才300来行,整体看着比较简单。

    包含以下功能:
    1、数据不满一屏,自动屏蔽上拉加载
    2、数据加载完毕不再执行加载动画,而是提示end
    3、支持RecyclerViewListView

    已知bug:
    当设置layout_height=warp_content时,未满一屏,仍然会执行上拉加载操作

    Paste_Image.png

    由于我这边没法录屏,所以只好借用刘小帅的动图,部分代码也参考了他的思路,如有疑问,请电邮2647759254@qq.com

    根据效果,我们知道这里包含3部分:头部,播放刷新动画;底部,播放加载动画;中间,展示数据。正常情况,我们只需要展示中间的部分,当手指下拉,到列表的头部时,开始显示头部,手指松开,播放刷新动画,并加载数据。数据加载完毕,动画停止,头部隐藏。底部也是同样的道理。

    通过上面的分析,需要处理下面几个问题:
    1、列表控件,如:RecyclerView,是充满父布局的,并且处于中间的位置,其头部和底部分别有一个View
    2、如何判断RecyclerView是否滑动到边界
    3、滑动到边界之后,开始展示头部或者底部界面,而且它们的界面也会跟着手指滑动,进行变化
    4、刷新或者加载完毕之后,如何隐藏头部或底部界面

       @Override
       protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        if(headerView == null) {
            headerView = LayoutInflater.from(getContext()).inflate(R.layout.layout_header, null);
            headerIV = (LevelImageView) headerView.findViewById(R.id.iv_refresh);
            headerTV = (TextView) headerView.findViewById(R.id.tv_header);
            RelativeLayout.LayoutParams params = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
            params.addRule(ALIGN_PARENT_TOP);
            headerView.setLayoutParams(params);
            headerView.setVisibility(GONE);
            addView(headerView);
        }
        if(footerView == null) {
            footerView = LayoutInflater.from(getContext()).inflate(R.layout.layout_footer, null);
            footerIV = (LevelImageView) footerView.findViewById(R.id.iv_load);
            footerTV = (TextView) footerView.findViewById(R.id.tv_footer);
            RelativeLayout.LayoutParams params = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
            params.addRule(ALIGN_PARENT_BOTTOM);
            footerView.setLayoutParams(params);
            footerView.setVisibility(GONE);
            addView(footerView);
        }
        childView = getChildAt(0);
        if(childView != null) {
            RelativeLayout.LayoutParams params = (LayoutParams) childView.getLayoutParams();
            params.addRule(RelativeLayout.ABOVE, R.id.footer_ll);
            params.addRule(RelativeLayout.BELOW, R.id.header_ll);
            childView.setLayoutParams(params);
            childView.requestLayout();
        }
    }
    

    通过第一点的分析,我决定采用组合控件的方式来写。整个布局继承RelativeLayout,在onAttachedToWindow()方法中,分别在头部和底部添加一个View,RecyclerView则通过调用getChildAt(0)获取。

       @Override
       public boolean onInterceptTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                downY = (int) ev.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                moveY = (int) ev.getY();
                if (moveY - downY > 0 && !ViewCompat.canScrollVertically(childView, -1)) { //下拉
                    slideStatus = PULL_DOWN_REFRESH;
                    return true;
                } else if (moveY - downY < 0 && !ViewCompat.canScrollVertically(childView, 1)) {//上拉
                    int hei = countDataHeight();
                    if(mParentHei - countDataHeight() > 50) {
                        Log.e("mParentHei", "mParentHei" + mParentHei + "---childView" + countDataHeight());
                        return false;
                    } else {
                        slideStatus = PULL_UP_LOAD;
                        return true;
                    }
                }
                break;
        }
        return super.onInterceptTouchEvent(ev);
    }
    

    在这个方法中,通过滑动的距离差,来判断是上拉还是下拉,而ViewCompat.canScrollVertically(view, -1)则是用来判断RecyclerView是否滑动到边界了。countDataHeight()这个方法,是用来计算RecyclerView中内容的高度,if(mParentHei - countDataHeight() > 50)这一句用于判断,RecyclerView中的内容,是否满一屏,如果不满一屏,则会屏蔽上拉加载。

     @Override
     public boolean onTouchEvent(MotionEvent event) {
        if (isLoading || isRefreshing) return super.onTouchEvent(event);
        switch (event.getAction()) {
            case MotionEvent.ACTION_MOVE:
                mY = (int) (event.getY() - downY); //计算滑动的距离
                if (slideStatus == PULL_DOWN_REFRESH) {
                    headerView.setVisibility(VISIBLE);
                    mY = mY <= 150 ? mY : 150;
                    headerView.getLayoutParams().height = mY;
                    headerTV.setText("释放刷新");
                    headerView.requestLayout();
                } else if (mY < 0 && slideStatus == PULL_UP_LOAD) {
                    footerView.setVisibility(VISIBLE);
                    mY = Math.abs(mY);
                    mY = mY <= 150 ? mY : 150;
                    if (childView instanceof RecyclerView) {
                        RecyclerView mRecyclerView = (RecyclerView) childView;
                        mRecyclerView.smoothScrollToPosition(mRecyclerView.getAdapter().getItemCount() - 1);
                    } else if (childView instanceof ListView) {
                        ListView mListView = (ListView) childView;
                        mListView.smoothScrollToPosition(mListView.getAdapter().getCount() - 1);
                    }
                    footerView.getLayoutParams().height = mY;
                    footerView.requestLayout();
                    if(mOnLoadListener != null) {
                        if(!mOnLoadListener.canLoad()) {
                            footerTV.setText("--end--");
                            footerIV.setVisibility(GONE);
                        }
                    }
                }
                break;
            case MotionEvent.ACTION_UP:
                if (slideStatus == PULL_DOWN_REFRESH) {
                    if (mY > 40) {
                        headerView.getLayoutParams().height = 120;
                        headerView.requestLayout();
                        isRefreshing = true;
                        headerTV.setText("刷新中、、、");
                        startProgress(headerIV, PULL_DOWN_REFRESH);
                        if (mOnRefreshListener != null) {
                            mOnRefreshListener.onRefresh(animator);
                        }
                    } else {
                        headerView.setVisibility(GONE);
                    }
                } else if (slideStatus == PULL_UP_LOAD) {
                    if (mY > 40) {
                        if (mOnLoadListener != null) {
                            if(mOnLoadListener.canLoad()) {
                                footerView.getLayoutParams().height = 120;
                                footerView.requestLayout();
                                isLoading = true;
                                footerTV.setText("加载中、、、");
                                startProgress(footerIV, PULL_UP_LOAD);
                                mOnLoadListener.onLoad(animator);
                            } else {
                                footerView.setVisibility(GONE);
                            }
                        }
                    } else {
                        footerView.setVisibility(GONE);
                    }
                }
                break;
        }
        return super.onTouchEvent(event);
    }
    

    这里有四个作用:
    第一个if (slideStatus == PULL_DOWN_REFRESH)用于显示头部界面,提示‘释放刷新’,并重新设置头部高度
    第一个else if (mY < 0 && slideStatus == PULL_UP_LOAD)除了显示底部界面之外,还需要通过if(!mOnLoadListener.canLoad())判断是否已经加载到底,没有更多数据了。
    第二个if (slideStatus == PULL_DOWN_REFRESH)是在释放的情况下开始执行刷新动画,并且执行刷新数据的操作
    第二个else if (slideStatus == PULL_UP_LOAD)
    晚一些会抽空上传代码,有需要的朋友,也可以直接联系我2647759254@qq.com

    相关文章

      网友评论

        本文标题:RecyclerView下拉刷新、上拉加载

        本文链接:https://www.haomeiwen.com/subject/chfvuttx.html