集下拉刷新、自動加載和側(cè)滑菜單的RecyclerView基本實(shí)現(xiàn)原理

前言

現(xiàn)在這個(gè)功能的框架也挺多的了谷饿。之所以要寫是因?yàn)檫@個(gè)框架是自己親手實(shí)現(xiàn)的惶我。說起來有點(diǎn)小激動,這是我正經(jīng)寫出來的第一個(gè)框架博投。對于"不要重復(fù)造輪子"這句話绸贡,我一直不是太認(rèn)同,得從不同的維度看毅哗。如果從使用上來看听怕,當(dāng)然沒必要重復(fù)造輪子,白白費(fèi)時(shí)費(fèi)力不劃算黎做。但是如果從個(gè)人學(xué)習(xí)的角度來看的話叉跛,重復(fù)造輪子不但應(yīng)該去做,而且很有必要蒸殿。只會使用輪子對個(gè)人的成長幫助不大。你得知道它是怎么工作的,它為什么能夠這樣工作宏所,然后更進(jìn)一步的話酥艳,看看我還能不能改進(jìn)它?而學(xué)習(xí)輪子效果最好的方法爬骤,我認(rèn)為就是自己再造一個(gè)輪子充石。說白了你來山寨一個(gè),如果可以霞玄,就改進(jìn)它骤铃!

正文

項(xiàng)目地址

demo和library源碼地址:https://github.com/zhangyuChen1991/PtrSwipeMenuRecyclerView

效果

和常見的側(cè)滑以及下拉刷新效果一樣,見下圖:
側(cè)滑菜單效果:

![側(cè)滑效果]
側(cè)滑菜單

下拉刷新效果:


下拉刷新

上拉加載效果:


加載更多

效果圖就是這樣坷剧,基本使用在上面源碼地址中都有惰爬,步驟非常簡便。下面主要想說的惫企,是它實(shí)現(xiàn)的基本原理撕瞧。

1.側(cè)滑原理

側(cè)滑的主要實(shí)現(xiàn),靠的是一個(gè)自定義的布局容器狞尔。項(xiàng)目中類名為:SwipeMenuLayout丛版,繼承FrameLayout.

public class SwipeMenuLayout extends FrameLayout 

它有兩個(gè)成員:

private View contentView;
private LinearLayout menuView;

一目了然,一個(gè)是內(nèi)容偏序,一個(gè)是菜單页畦。內(nèi)容自然就是RecyclerView條目中的布局內(nèi)容,菜單則是自定義的菜單布局研儒。它作為一個(gè)容器豫缨,包含了這兩個(gè)子布局。
  重點(diǎn)是殉摔,怎么讓兩個(gè)子布局歸位到自己的初始位置呢州胳?內(nèi)容布局鋪滿整個(gè)寬度,菜單布局放在屏幕外邊逸月。簡單看看下面的代碼:

//init()方法中執(zhí)行下面三句
LayoutParams contentParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
contentView.setLayoutParams(contentParams);
menuView.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));

 //重寫onLayout()方法
 @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        Log.d(TAG, "contentView.getWidth() = " + contentView.getWidth() + "contentView.getHeight() = " + contentView.getHeight());
        super.onLayout(changed, left, top, right, bottom);

        int contentViewWidth = contentView.getWidth();
        int contentViewHeight = contentView.getHeight();
        int menuViewWidth = menuView.getWidth();
        if (contentView != null && contentViewWidth != 0 && contentViewHeight != 0) {
            contentView.layout(0, 0, contentView.getWidth(), contentView.getHeight());

            if (menuView != null) {
                menuView.layout(contentViewWidth, 0, contentViewWidth + menuViewWidth, contentViewHeight);
            }
        }
    }

首先對設(shè)置進(jìn)來的內(nèi)容布局和菜單布局設(shè)定寬高栓撞,內(nèi)容布局寬度鋪滿整個(gè)屏幕。菜單布局的寬度為包裹內(nèi)容碗硬。然后瓤湘,決定子控件的位置就是在onLayout()方法中進(jìn)行的,所以重寫onLayout()方法恩尾,根據(jù)內(nèi)容布局和菜單布局的寬和高來執(zhí)行它們的layout()方法弛说。可以看到翰意,內(nèi)容布局的寬是鋪滿整個(gè)屏幕的木人,菜單布局的寬度范圍是contentViewWidth到contentViewWidth+menuViewWidth信柿,也就是從內(nèi)容布局的寬度終點(diǎn)位置到這個(gè)位置加上自己寬度的位置,就剛好在屏幕外面了醒第。
  初始化位置搞定之后渔嚷,就要開始處理它的滑動事件了,要把菜單側(cè)滑出來稠曼,重寫onTouchEvent()方法形病。這里需要自己來實(shí)現(xiàn)smoothScroll()等功能,細(xì)節(jié)上要處理控制具體可滑動方向霞幅,菜單自動打開漠吻、自動關(guān)閉等問題,具體實(shí)現(xiàn)請參考代碼司恳。整個(gè)SwipeMenuLayout也就兩三百行代碼途乃,并不復(fù)雜。
  處理完側(cè)滑菜單的具體實(shí)現(xiàn)之后抵赢,就要考慮把它放到RecyclerView里面去欺劳,作為默認(rèn)的ItemView。當(dāng)使用者設(shè)置他自己的ItemView時(shí)铅鲤,將其作為SwipeMenuLayout的內(nèi)容布局划提,然后加上構(gòu)造的菜單布局(使用者自定義的),返回SwipeMenuLayout作為新的ItemView邢享,這樣鹏往,每一個(gè)Item就都具備側(cè)滑菜單的效果了。
  要做以上的事情骇塘,不可避免的伊履,需要重寫Adapter,這里承擔(dān)這個(gè)角色的是SwipeMenuAdapter款违,繼承RecyclerView.Adapter唐瀑。

public abstract class SwipeMenuAdapter<V extends PtrSwipeMenuRecyclerView.ViewHolder> extends RecyclerView.Adapter 

細(xì)心的同學(xué)會發(fā)現(xiàn)這里ViewHolder和默認(rèn)的不一樣,確實(shí)插爹,ViewHolder也重寫了哄辣,主要是為了設(shè)置菜單的點(diǎn)擊事件監(jiān)聽,這里先不討論它赠尾。

 @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        menuView = createMenuView(parent, viewType);
        contentView = createContentView(parent, viewType);
        SwipeMenuLayout swipeMenuLayout = new SwipeMenuLayout(parent.getContext(), contentView, menuView);
        return onCreateThisViewHolder(swipeMenuLayout, viewType);
    }

    /**
     * 創(chuàng)建item內(nèi)容的view布局
     *
     * @param parent
     * @param viewType
     * @return
     */
    protected abstract View createContentView(ViewGroup parent, int viewType);

    /**
     * 創(chuàng)建菜單view的布局
     *
     * @return
     */
    protected abstract LinearLayout createMenuView(ViewGroup parent, int viewType);
    
    /**
     * 創(chuàng)建ViewHolder
     *
     * @param contentView 已經(jīng)在createContentView()中創(chuàng)建好力穗,然后經(jīng)過再次包裹了側(cè)滑菜單布局的itemview
     * @param viewType
     * @return
     */
    public abstract RecyclerView.ViewHolder onCreateThisViewHolder(ViewGroup contentView, int viewType);

這里有三個(gè)抽象方法,createMenuView()創(chuàng)建一個(gè)菜單布局气嫁,由使用者自己實(shí)現(xiàn)当窗,createContentView()創(chuàng)建一個(gè)內(nèi)容布局,同樣由使用者來實(shí)現(xiàn)寸宵。onCreateThisViewHolder則是替代了原來的onCreateViewHolder()方法崖面,用來返回一個(gè)ViewHolder元咙,但是在這里返回的ViewHolder,其實(shí)已經(jīng)是item被包裹了SwipeMenuLayout的item了嘶朱,實(shí)現(xiàn)了側(cè)滑菜單的功能蛾坯。
  到這里光酣,側(cè)滑菜單的主干實(shí)現(xiàn)原理就大致說完了疏遏。下面看下拉刷新和自動加載。

2.下拉刷新及自動加載原理

下拉刷新效果總體的流程就是:控制touch事件救军,根據(jù)手指滑動動態(tài)的改變頭部HeaderView的高度和其內(nèi)部View的狀態(tài)财异,達(dá)到好像控件被拉下來觸發(fā)刷新的效果。(當(dāng)然也有根據(jù)手指滑動往下滾動View的實(shí)現(xiàn)方法不是這里用的不去多講)
  以前ListView做下拉刷新的時(shí)候唱遭,在頂部會增加一個(gè)Header作為下拉刷新頭戳寸,而ListView也已經(jīng)封裝了setHeader()方法,十分方便拷泽。但是RecyclerView沒有疫鹊,所以實(shí)現(xiàn)下拉刷新的第一個(gè)任務(wù)就是給RecyclerView增加一個(gè)HeaderView作為下拉刷新頭。
  增加HeaderView司致,其實(shí)就是在RecyclerView的第0個(gè)位置放上自己特定的一個(gè)View拆吆,用來實(shí)現(xiàn)下拉刷新的效果。首先我們封裝一個(gè)HeaderView脂矫,相當(dāng)于一個(gè)自定義布局枣耀,方便下拉刷新效果變化的管理(代碼略,請參考源碼)庭再。
  要增加HeaderView捞奕,又得去重寫Adapter了,好在上面做側(cè)滑菜單的時(shí)候已經(jīng)重寫了拄轻,所以把SwipeMenuAdapter拿出來颅围,繼續(xù)添加代碼。主要是onCreateViewHolder()方法恨搓,然后牽涉到getItemCount()和getItemViewType()等方法院促。由于自動加載更多所添加的FooterView與HeaderView是同樣的原理,所以就一并說吧奶卓。先看代碼:

public class HeaderFooterViewHolder extends RecyclerView.ViewHolder {
        public HeaderFooterViewHolder(View itemView) {
            super(itemView);
        }
    }

    @Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if (viewType == HeaderType) {
            headerViewHolder = new HeaderFooterViewHolder(new HeaderView(parent.getContext()));
            return headerViewHolder;
        }
        if (viewType == FooterType) {
            footerViewHolder = new HeaderFooterViewHolder(new FooterView(parent.getContext()));
            if(!footerViewEnable) { //不允許上拉加載更多一疯,隱藏FooterView
                FooterView footerView = (FooterView) footerViewHolder.itemView;
                footerView.setNowState(FooterView.STATE.HIND);
            }
            return footerViewHolder;
        }
        ...
        ... 
    }
    
@Override
    public int getItemCount() {
        //添加Header和Footer的數(shù)目
        return getThisItemCount() + 2;
    }

    /**
     * 此方法執(zhí)行RecyclerView.Adapter中g(shù)etItemCount()的邏輯
     *
     * @return
     */
    public abstract int getThisItemCount();

    /**
     * 重寫此方法時(shí)請注意保留父類方法的邏輯,否則導(dǎo)致header計(jì)數(shù)混亂夺姑,下拉刷新出錯
     * 使用position時(shí)注意減1(減去header的位置)
     *
     * @param position
     * @return
     */
    @Override
    public int getItemViewType(int position) {
        if (position == 0)
            return HeaderType;
        if (position == getThisItemCount() + 1)
            return FooterType;
        return super.getItemViewType(position - 1);//減1去掉herder的位置
    }

首先是getItemCount()方法墩邀,加上HeaderView和FooterView的位置,也就是在原有的數(shù)目上加2盏浙,原有的數(shù)目由getThisItemCount()獲取眉睹,由使用者自己實(shí)現(xiàn)荔茬。然后在特定的位置返回特定的類型,position為0時(shí)竹海,返回HeaderType慕蔚,position在最后時(shí),返回FooterType斋配。然后孔飒,在onCreateViewHolder()中根據(jù)viewType返回特定的ViewHolder類型。這樣艰争,就把HeaderView和FooterView都增加進(jìn)去了坏瞄。
  接下來的步驟就是控制touch事件動態(tài)設(shè)置HeaderView的高度及控件來實(shí)現(xiàn)下拉刷新的效果了。當(dāng)RecyclerView滑動到頂部時(shí)甩卓,繼續(xù)往下拉觸發(fā)下拉刷新鸠匀。當(dāng)滑到底部時(shí),自動觸發(fā)加載更多逾柿。然后設(shè)置好相關(guān)的接口回調(diào)缀棍,就基本完成。這里面許多細(xì)節(jié)机错,一篇文章很難講完了爬范,基本可以另開新篇。涉及很多基本知識和細(xì)節(jié)邏輯毡熏。大家真的愿意了解的話坦敌。源碼鏈接在下方,可以作為參考痢法。

結(jié)尾

項(xiàng)目托管在github上狱窘,再貼一次地址:https://github.com/zhangyuChen1991/PtrSwipeMenuRecyclerView
  有興趣的童鞋可以前去下載,如發(fā)現(xiàn)問題财搁,請斧正蘸炸!非常感謝!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末尖奔,一起剝皮案震驚了整個(gè)濱河市搭儒,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌提茁,老刑警劉巖淹禾,帶你破解...
    沈念sama閱讀 222,865評論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異茴扁,居然都是意外死亡铃岔,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,296評論 3 399
  • 文/潘曉璐 我一進(jìn)店門峭火,熙熙樓的掌柜王于貴愁眉苦臉地迎上來毁习,“玉大人智嚷,你說我怎么就攤上這事》那遥” “怎么了盏道?”我有些...
    開封第一講書人閱讀 169,631評論 0 364
  • 文/不壞的土叔 我叫張陵,是天一觀的道長载碌。 經(jīng)常有香客問我猜嘱,道長,這世上最難降的妖魔是什么恐仑? 我笑而不...
    開封第一講書人閱讀 60,199評論 1 300
  • 正文 為了忘掉前任泉坐,我火速辦了婚禮,結(jié)果婚禮上裳仆,老公的妹妹穿的比我還像新娘。我一直安慰自己孤钦,他們只是感情好歧斟,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,196評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著偏形,像睡著了一般静袖。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上俊扭,一...
    開封第一講書人閱讀 52,793評論 1 314
  • 那天队橙,我揣著相機(jī)與錄音,去河邊找鬼萨惑。 笑死捐康,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的庸蔼。 我是一名探鬼主播解总,決...
    沈念sama閱讀 41,221評論 3 423
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼姐仅!你這毒婦竟也來了花枫?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,174評論 0 277
  • 序言:老撾萬榮一對情侶失蹤掏膏,失蹤者是張志新(化名)和其女友劉穎劳翰,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體馒疹,經(jīng)...
    沈念sama閱讀 46,699評論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡佳簸,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,770評論 3 343
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了行冰。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片溺蕉。...
    茶點(diǎn)故事閱讀 40,918評論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡伶丐,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出疯特,到底是詐尸還是另有隱情哗魂,我是刑警寧澤,帶...
    沈念sama閱讀 36,573評論 5 351
  • 正文 年R本政府宣布漓雅,位于F島的核電站录别,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏邻吞。R本人自食惡果不足惜组题,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,255評論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望抱冷。 院中可真熱鬧崔列,春花似錦、人聲如沸旺遮。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,749評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽耿眉。三九已至边翼,卻和暖如春鹿驼,著一層夾襖步出監(jiān)牢的瞬間枝秤,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,862評論 1 274
  • 我被黑心中介騙來泰國打工捕传, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留筐骇,地道東北人债鸡。 一個(gè)月前我還...
    沈念sama閱讀 49,364評論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像拥褂,于是被迫代替她去往敵國和親娘锁。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,926評論 2 361

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

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,336評論 25 707
  • 內(nèi)容抽屜菜單ListViewWebViewSwitchButton按鈕點(diǎn)贊按鈕進(jìn)度條TabLayout圖標(biāo)下拉刷新...
    皇小弟閱讀 46,793評論 22 665
  • 世界上沒有該結(jié)婚的年齡饺鹃,只有該結(jié)婚的感情莫秆。從遇見你的那一刻,我相信了愛情的存在悔详,無關(guān)相貌镊屎、無關(guān)背景、無關(guān)任何人茄螃,就...
    茗莜閱讀 494評論 2 1
  • 入選時(shí)間:2017年7月3日 入選級別:季級 入選理由:江蘇俐爸缝驳,三年的MBA學(xué)習(xí)經(jīng)歷,使他喜歡用學(xué)習(xí)、拼搏緩解生...
    周助人閱讀 248評論 0 0
  • 雷達(dá)圖用狱,城里人管她叫戴布拉圖 某殺的個(gè)人信息那里有個(gè)运怖。一看就是穩(wěn)如狗 某度的個(gè)人圖譜一看就是不熱愛生活,不關(guān)心政治...
    ac嚕嚕嚕閱讀 1,749評論 0 22