RecyclerView下拉刷新、上拉加载

最近应公司项目需要,动手写了一个下拉刷新和上拉加载的自定义控件。之所以没有直接用网上的依赖库,一来是感觉会让项目变得更臃肿,而大多数酷炫的效果,根本就用不到;二来,也在网上找过类似的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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,657评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,662评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,143评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,732评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,837评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,036评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,126评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,868评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,315评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,641评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,773评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,859评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,584评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,676评论 2 351

推荐阅读更多精彩内容