RecyclerView下拉刷新钱雷、上拉加載及添加頭布局骂铁、腳布局實(shí)現(xiàn)

image.png

前言


  • 隨著RecyclerView的越來越流行,我看著項(xiàng)目里L(fēng)istView罩抗、GridView陷入沉思拉庵,是時(shí)候開始改變了!(認(rèn)真臉)我決定將項(xiàng)目中的這些控件都改用RecyclerView套蒂。然而钞支,像下拉刷新等功能是必不可少的,雖然有很多現(xiàn)成的可以用操刀,但是烁挟,我毅然決定自己動(dòng)手。

思路


  • 下定決心了骨坑,那么接下來就是考慮該怎么實(shí)現(xiàn)了撼嗓。
  • 由于RecyclerView并沒有像ListView一樣為我們提供方便的addHeaderView()、addFooterView()方法來添加頭布局和腳布局(這也是我們要實(shí)現(xiàn)的)欢唾,所以就不能像ListView一樣通過添加頭布局且警、腳布局實(shí)現(xiàn)下拉刷新、上拉加載更多功能了礁遣。
  • 而在RecyclerView里斑芜,展示多少條數(shù)據(jù),有多少條目祟霍,這些杏头,都是由適配器控制的盈包,所以,要想實(shí)現(xiàn)以上的功能大州,就要從適配器入手了续语。

實(shí)現(xiàn)


  • 思路有了,那么接下來就是如何實(shí)現(xiàn)了厦画。

  • 首先疮茄,自然是新建RLRecyclerView繼承RecyclerView,實(shí)現(xiàn)構(gòu)造方法根暑。

  • 接著力试,重寫setAdapter()方法,將傳遞進(jìn)來的適配器對象保存下來排嫌,實(shí)際上設(shè)置的是封裝好的實(shí)現(xiàn)以上功能的適配器畸裳。

      @Override
      public void setAdapter(Adapter adapter) {
          // 保存設(shè)置的適配器
          mAdapter = adapter;
          // 設(shè)置封裝的適配器
          innerAdapter = new InsideAdapter();
    
          super.setAdapter(innerAdapter);
    
          // 注冊觀察者
          mAdapter.registerAdapterDataObserver(mObserver);
      }
    
  • 至于下面的注冊觀察者,我們晚點(diǎn)再說淳地,接下來就是這個(gè) InsideAdapter怖糊,直接以內(nèi)部類形式定義在RLRecyclerView中,繼承RecyclerView.Adapter

       /**
       * 添加了頭布局颇象、腳布局伍伤、下拉刷新、上拉加載更多功能的適配器類
       */
      class InsideAdapter extends Adapter {
    
          /** 布局類型-刷新布局 */
          private static final int VIEW_TYPE_REFRESH = 0;
          /** 布局類型-頭布局 */
          private static final int VIEW_TYPE_HEADER = 1;
          /** 布局類型-普通布局 */
          private static final int VIEW_TYPE_NORMAL = 2;
          /** 布局類型-腳布局 */
          private static final int VIEW_TYPE_FOOTER = 3;
          /** 布局類型-加載更多布局 */
          private static final int VIEW_TYPE_LOADMORE = 4;
    
          @Override
          public int getItemViewType(int position) {
    
              // 重寫方法遣钳,根據(jù)下標(biāo)判斷布局類型
    
              if (isRefresh(position)) {
                  return VIEW_TYPE_REFRESH;
              } else if (isHeader(position)) {
                  return VIEW_TYPE_HEADER;
              } else if (isFooter(position)) {
                  return VIEW_TYPE_FOOTER;
              } else if (isLoadMore(position)) {
                  return VIEW_TYPE_LOADMORE;
              } else {
                  return VIEW_TYPE_NORMAL;
              }
          }
    
          @Override
          public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    
              ViewHolder holder;
    
              // 根據(jù)布局類型扰魂,返回不同的ViewHolder對象,SimpleViewHolder不做任何操作
              switch (viewType) {
                  case VIEW_TYPE_REFRESH:
                      holder = new SimpleViewHolder(mRefresh);
                      break;
                  case VIEW_TYPE_HEADER:
                      holder = new SimpleViewHolder(mHeaders.get(headerPosition++));
                      break;
                  case VIEW_TYPE_NORMAL: // 普通布局類型返回設(shè)置的Adpter的ViewHolder對象
                      holder = mAdapter.onCreateViewHolder(parent, viewType);
                      break;
                  case VIEW_TYPE_FOOTER:
                      holder = new SimpleViewHolder(mFooters.get(footerPosition++));
                      break;
                  case VIEW_TYPE_LOADMORE:
                      holder = new SimpleViewHolder(mLoadMore);
                      break;
                  default:
                      holder = new SimpleViewHolder(null);
                      break;
              }
    
              return holder;
          }
    
          @Override
          public void onBindViewHolder(ViewHolder holder, int position) {
    
              // 刷新布局蕴茴、加載更多劝评、頭布局、腳布局不做處理
              if (isRefresh(position) || isLoadMore(position)
                      || isHeader(position) || isFooter(position)) {
                  return;
              }
    
              mAdapter.onBindViewHolder(holder, realPosition(position));
          }
    
          @Override
          public int getItemCount() {
    
              // 根據(jù)功能開啟情況以及頭布局腳布局返回實(shí)際的條目數(shù)
              if (REFRESH_MODE_BOTH.equals(mode)) {
                  return mAdapter.getItemCount() + mHeaders.size() + mFooters.size() + 2;
              } else if (REFRESH_MODE_REFRESH.equals(mode)
                      || REFRESH_MODE_LOADMORE.equals(mode)) {
                  return mAdapter.getItemCount() + mHeaders.size() + mFooters.size() + 1;
              } else {
                  return mAdapter.getItemCount() + mHeaders.size() + mFooters.size();
              }
          }
    
          class SimpleViewHolder extends RecyclerView.ViewHolder {
              SimpleViewHolder(View itemView) {
                  super(itemView);
              }
          }
      }
    
  • 先不說下拉刷新倦淀、上拉加載控件需要隱藏蒋畜,實(shí)際運(yùn)行起來,你會(huì)發(fā)現(xiàn)撞叽,如果使用 LinnerLayoutManager 是沒有問題的百侧,但是如果使用的是 GridLayoutManager 或者是 StaggeredGridLayoutManager 你就會(huì)發(fā)現(xiàn)并沒有達(dá)到想象中的效果,這是因?yàn)槲覀兊拇a中實(shí)際上只是在設(shè)置適配器的時(shí)候能扒,添加了幾條數(shù)據(jù)佣渴,并沒有改變他的展示效果。而 RecyclerView 把布局展示的工作都交給了 LayoutManager初斑,所以這個(gè)時(shí)候辛润,為了能夠?qū)崿F(xiàn)頭布局等能在 GridLayoutManager 和 StaggeredGridLayoutManager 下寬度也能MATCH_PARENT,我們就需要在 InsideAdapter 中重寫 onAttachedToRecyclerView 和 onViewAttachedToWindow 兩個(gè)方法:

      @Override
      public void onAttachedToRecyclerView(RecyclerView recyclerView) {
    
          RecyclerView.LayoutManager manager = recyclerView.getLayoutManager();
          if (manager instanceof GridLayoutManager) { // 如果是Grid布局
              final GridLayoutManager gridManager = ((GridLayoutManager) manager);
              gridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
                  @Override
                  public int getSpanSize(int position) { // 這個(gè)方法是返回當(dāng)前對象所在行有幾列
                      return (isRefresh(position) || isLoadMore(position)
                              || isHeader(position) || isFooter(position))
                              ? gridManager.getSpanCount() : 1; // 如果是刷新、加載更多或頭布局砂竖、腳布局獨(dú)占一行真椿,否則按照設(shè)置展示
                  }
              });
          }
      }
    
      @Override
      public void onViewAttachedToWindow(ViewHolder holder) {
    
          ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
          if (lp != null
                  && lp instanceof StaggeredGridLayoutManager.LayoutParams
                  && (isRefresh(holder.getLayoutPosition()) || isLoadMore(holder.getLayoutPosition())
                  || isHeader(holder.getLayoutPosition()) || isFooter(holder.getLayoutPosition()))) {
              StaggeredGridLayoutManager.LayoutParams p = (StaggeredGridLayoutManager.LayoutParams) lp;
              // 如果是刷新、加載更多或頭布局乎澄、腳布局獨(dú)占一行突硝,否則按照設(shè)置展示
              p.setFullSpan(true); // 設(shè)置獨(dú)占一行
          }
      }
    
  • 這樣,即使布局為 GridLayoutManager 或者 StaggeredGridLayoutManager 刷新置济、加載更多解恰、頭布局、腳布局都是單獨(dú)的一行了浙于。

  • 接下來要實(shí)現(xiàn)的就是下拉刷新护盈、上拉加載更多的功能了,這個(gè)實(shí)現(xiàn)思路其實(shí)和ListView下拉刷新是一致的羞酗,同樣通過設(shè)置刷新布局和加載更多布局的margin值來實(shí)現(xiàn)腐宋,其核心就是設(shè)置觸摸事件監(jiān)聽,然后在 onTouch 方法中判斷不同的情況檀轨,做不同的處理胸竞。由于這部分內(nèi)容比較復(fù)雜,筆者就不在這里細(xì)說了参萄,有興趣的朋友可以在文章最后找到Github地址查看源碼卫枝,源碼里都有詳細(xì)注釋。

  • 在前文有說明一段代碼后面解釋拧揽,那就是 mAdapter.registerAdapterDataObserver(mObserver)

  • 這是給使用者設(shè)置適配器的時(shí)候同時(shí)給這個(gè)適配器注冊了一個(gè)觀察者:

    private final RecyclerView.AdapterDataObserver mObserver = new RecyclerView.AdapterDataObserver() {
      @Override
      public void onChanged() {
          innerAdapter.notifyDataSetChanged();
      }
    
      @Override
      public void onItemRangeInserted(int positionStart, int itemCount) {
          innerAdapter.notifyItemRangeInserted(positionStart, itemCount);
      }
    
      @Override
      public void onItemRangeChanged(int positionStart, int itemCount) {
          innerAdapter.notifyItemRangeChanged(positionStart, itemCount);
      }
    
      @Override
      public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
          innerAdapter.notifyItemRangeChanged(positionStart, itemCount, payload);
      }
    
      @Override
      public void onItemRangeRemoved(int positionStart, int itemCount) {
          innerAdapter.notifyItemRangeRemoved(positionStart, itemCount);
      }
    
      @Override
      public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
          innerAdapter.notifyItemMoved(fromPosition, toPosition);
      }
    };
    
  • 這個(gè)觀察者所做的就是在使用者調(diào)用適配器的notifyDataSetChanged方法時(shí)剃盾,同步調(diào)用InnerAdapter的方法腺占,因?yàn)橥ㄟ^setAdapter方法設(shè)置的適配器實(shí)際上是我們封裝的InnerAdapter淤袜,所以,當(dāng)數(shù)據(jù)變更時(shí)衰伯,需要調(diào)用InnerAdapter的方法才能同步更新界面铡羡。

總結(jié)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市慌烧,隨后出現(xiàn)的幾起案子逐抑,更是在濱河造成了極大的恐慌,老刑警劉巖屹蚊,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件厕氨,死亡現(xiàn)場離奇詭異,居然都是意外死亡淑翼,警方通過查閱死者的電腦和手機(jī)腐巢,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進(jìn)店門荞估,熙熙樓的掌柜王于貴愁眉苦臉地迎上來葵陵,“玉大人,你說我怎么就攤上這事永品≡饩” “怎么了胃惜?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長哪雕。 經(jīng)常有香客問我船殉,道長,這世上最難降的妖魔是什么斯嚎? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任利虫,我火速辦了婚禮,結(jié)果婚禮上堡僻,老公的妹妹穿的比我還像新娘糠惫。我一直安慰自己,他們只是感情好钉疫,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布硼讽。 她就那樣靜靜地躺著,像睡著了一般牲阁。 火紅的嫁衣襯著肌膚如雪固阁。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天城菊,我揣著相機(jī)與錄音备燃,去河邊找鬼。 笑死凌唬,一個(gè)胖子當(dāng)著我的面吹牛并齐,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼冀膝,長吁一口氣:“原來是場噩夢啊……” “哼唁奢!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起窝剖,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤麻掸,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后赐纱,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體脊奋,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年疙描,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了诚隙。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,137評論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡起胰,死狀恐怖久又,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情效五,我是刑警寧澤地消,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站畏妖,受9級(jí)特大地震影響脉执,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜戒劫,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一半夷、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧迅细,春花似錦巫橄、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽列荔。三九已至敬尺,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間贴浙,已是汗流浹背砂吞。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留崎溃,地道東北人蜻直。 一個(gè)月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親概而。 傳聞我的和親對象是個(gè)殘疾皇子呼巷,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評論 2 345

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