RecyclerView下拉刷新、上拉加載

最近應公司項目需要,動手寫了一個下拉刷新和上拉加載的自定義控件。之所以沒有直接用網(wǎng)上的依賴庫,一來是感覺會讓項目變得更臃腫,而大多數(shù)酷炫的效果,根本就用不到;二來,也在網(wǎng)上找過類似的demo,但是看完之后又都感覺太復雜,最終決定還是自己動手寫。從最開始構思、查閱資料到徹底完成,差不多用了2天時間,代碼量總共才300來行,整體看著比較簡單。

包含以下功能:
1、數(shù)據(jù)不滿一屏,自動屏蔽上拉加載
2、數(shù)據(jù)加載完畢不再執(zhí)行加載動畫,而是提示end
3、支持RecyclerViewListView

已知bug:
當設置layout_height=warp_content時,未滿一屏,仍然會執(zhí)行上拉加載操作

Paste_Image.png

由于我這邊沒法錄屏,所以只好借用劉小帥的動圖,部分代碼也參考了他的思路,如有疑問,請電郵2647759254@qq.com

根據(jù)效果,我們知道這里包含3部分:頭部,播放刷新動畫;底部,播放加載動畫;中間,展示數(shù)據(jù)。正常情況,我們只需要展示中間的部分,當手指下拉,到列表的頭部時,開始顯示頭部,手指松開,播放刷新動畫,并加載數(shù)據(jù)。數(shù)據(jù)加載完畢,動畫停止,頭部隱藏。底部也是同樣的道理。

通過上面的分析,需要處理下面幾個問題:
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())判斷是否已經加載到底,沒有更多數(shù)據(jù)了。
第二個if (slideStatus == PULL_DOWN_REFRESH)是在釋放的情況下開始執(zhí)行刷新動畫,并且執(zhí)行刷新數(shù)據(jù)的操作
第二個else if (slideStatus == PULL_UP_LOAD)
晚一些會抽空上傳代碼,有需要的朋友,也可以直接聯(lián)系我2647759254@qq.com

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容