RecyclerView下拉刷新好渠、上拉加載

最近應(yīng)公司項(xiàng)目需要文虏,動(dòng)手寫了一個(gè)下拉刷新和上拉加載的自定義控件。之所以沒有直接用網(wǎng)上的依賴庫坷备,一來是感覺會(huì)讓項(xiàng)目變得更臃腫挪拟,而大多數(shù)酷炫的效果,根本就用不到击你;二來,也在網(wǎng)上找過類似的demo谎柄,但是看完之后又都感覺太復(fù)雜丁侄,最終決定還是自己動(dòng)手寫。從最開始構(gòu)思朝巫、查閱資料到徹底完成鸿摇,差不多用了2天時(shí)間,代碼量總共才300來行劈猿,整體看著比較簡單拙吉。

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

已知bug:
當(dāng)設(shè)置layout_height=warp_content時(shí)佛舱,未滿一屏,仍然會(huì)執(zhí)行上拉加載操作

Paste_Image.png

由于我這邊沒法錄屏,所以只好借用劉小帥的動(dòng)圖请祖,部分代碼也參考了他的思路订歪,如有疑問,請電郵2647759254@qq.com肆捕。

根據(jù)效果刷晋,我們知道這里包含3部分:頭部,播放刷新動(dòng)畫慎陵;底部眼虱,播放加載動(dòng)畫;中間荆姆,展示數(shù)據(jù)蒙幻。正常情況,我們只需要展示中間的部分胆筒,當(dāng)手指下拉邮破,到列表的頭部時(shí),開始顯示頭部仆救,手指松開抒和,播放刷新動(dòng)畫,并加載數(shù)據(jù)彤蔽。數(shù)據(jù)加載完畢摧莽,動(dòng)畫停止,頭部隱藏顿痪。底部也是同樣的道理镊辕。

通過上面的分析,需要處理下面幾個(gè)問題:
1蚁袭、列表控件征懈,如:RecyclerView,是充滿父布局的揩悄,并且處于中間的位置卖哎,其頭部和底部分別有一個(gè)View
2、如何判斷RecyclerView是否滑動(dòng)到邊界
3删性、滑動(dòng)到邊界之后亏娜,開始展示頭部或者底部界面,而且它們的界面也會(huì)跟著手指滑動(dòng)蹬挺,進(jìn)行變化
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();
    }
}

通過第一點(diǎn)的分析巴帮,我決定采用組合控件的方式來寫幸缕。整個(gè)布局繼承RelativeLayout群发,在onAttachedToWindow()方法中,分別在頭部和底部添加一個(gè)View,RecyclerView則通過調(diào)用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);
}

在這個(gè)方法中熟妓,通過滑動(dòng)的距離差,來判斷是上拉還是下拉栏尚,而ViewCompat.canScrollVertically(view, -1)則是用來判斷RecyclerView是否滑動(dòng)到邊界了起愈。countDataHeight()這個(gè)方法,是用來計(jì)算RecyclerView中內(nèi)容的高度译仗,if(mParentHei - countDataHeight() > 50)這一句用于判斷抬虽,RecyclerView中的內(nèi)容,是否滿一屏纵菌,如果不滿一屏阐污,則會(huì)屏蔽上拉加載。

 @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); //計(jì)算滑動(dòng)的距離
            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);
}

這里有四個(gè)作用:
第一個(gè)if (slideStatus == PULL_DOWN_REFRESH)用于顯示頭部界面忱详,提示‘釋放刷新’围来,并重新設(shè)置頭部高度
第一個(gè)else if (mY < 0 && slideStatus == PULL_UP_LOAD)除了顯示底部界面之外,還需要通過if(!mOnLoadListener.canLoad())判斷是否已經(jīng)加載到底匈睁,沒有更多數(shù)據(jù)了监透。
第二個(gè)if (slideStatus == PULL_DOWN_REFRESH)是在釋放的情況下開始執(zhí)行刷新動(dòng)畫,并且執(zhí)行刷新數(shù)據(jù)的操作
第二個(gè)else if (slideStatus == PULL_UP_LOAD)
晚一些會(huì)抽空上傳代碼航唆,有需要的朋友胀蛮,也可以直接聯(lián)系我2647759254@qq.com

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末佛点,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子黎比,更是在濱河造成了極大的恐慌超营,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,000評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件阅虫,死亡現(xiàn)場離奇詭異演闭,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)颓帝,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評論 3 399
  • 文/潘曉璐 我一進(jìn)店門米碰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來窝革,“玉大人,你說我怎么就攤上這事吕座∨耙耄” “怎么了?”我有些...
    開封第一講書人閱讀 168,561評論 0 360
  • 文/不壞的土叔 我叫張陵吴趴,是天一觀的道長漆诽。 經(jīng)常有香客問我,道長锣枝,這世上最難降的妖魔是什么厢拭? 我笑而不...
    開封第一講書人閱讀 59,782評論 1 298
  • 正文 為了忘掉前任,我火速辦了婚禮撇叁,結(jié)果婚禮上供鸠,老公的妹妹穿的比我還像新娘。我一直安慰自己陨闹,他們只是感情好楞捂,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,798評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著正林,像睡著了一般泡一。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上觅廓,一...
    開封第一講書人閱讀 52,394評論 1 310
  • 那天鼻忠,我揣著相機(jī)與錄音,去河邊找鬼杈绸。 笑死帖蔓,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的瞳脓。 我是一名探鬼主播塑娇,決...
    沈念sama閱讀 40,952評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼劫侧!你這毒婦竟也來了埋酬?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,852評論 0 276
  • 序言:老撾萬榮一對情侶失蹤烧栋,失蹤者是張志新(化名)和其女友劉穎写妥,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體审姓,經(jīng)...
    沈念sama閱讀 46,409評論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡珍特,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,483評論 3 341
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了魔吐。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片扎筒。...
    茶點(diǎn)故事閱讀 40,615評論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡莱找,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出嗜桌,到底是詐尸還是另有隱情奥溺,我是刑警寧澤,帶...
    沈念sama閱讀 36,303評論 5 350
  • 正文 年R本政府宣布症脂,位于F島的核電站谚赎,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏诱篷。R本人自食惡果不足惜壶唤,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,979評論 3 334
  • 文/蒙蒙 一棕所、第九天 我趴在偏房一處隱蔽的房頂上張望闸盔。 院中可真熱鬧,春花似錦琳省、人聲如沸迎吵。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,470評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽击费。三九已至,卻和暖如春桦他,著一層夾襖步出監(jiān)牢的瞬間蔫巩,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,571評論 1 272
  • 我被黑心中介騙來泰國打工快压, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留圆仔,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,041評論 3 377
  • 正文 我出身青樓蔫劣,卻偏偏與公主長得像坪郭,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子脉幢,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,630評論 2 359

推薦閱讀更多精彩內(nèi)容