RecyclerView下拉刷新、上拉加載及添加頭布局、腳布局實現

image.png

前言


  • 隨著RecyclerView的越來越流行,我看著項目里ListView、GridView陷入沉思,是時候開始改變了!(認真臉)我決定將項目中的這些控件都改用RecyclerView。然而,像下拉刷新等功能是必不可少的,雖然有很多現成的可以用,但是,我毅然決定自己動手。

思路


  • 下定決心了,那么接下來就是考慮該怎么實現了。
  • 由于RecyclerView并沒有像ListView一樣為我們提供方便的addHeaderView()、addFooterView()方法來添加頭布局和腳布局(這也是我們要實現的),所以就不能像ListView一樣通過添加頭布局、腳布局實現下拉刷新、上拉加載更多功能了。
  • 而在RecyclerView里,展示多少條數據,有多少條目,這些,都是由適配器控制的,所以,要想實現以上的功能,就要從適配器入手了。

實現


  • 思路有了,那么接下來就是如何實現了。

  • 首先,自然是新建RLRecyclerView繼承RecyclerView,實現構造方法。

  • 接著,重寫setAdapter()方法,將傳遞進來的適配器對象保存下來,實際上設置的是封裝好的實現以上功能的適配器。

      @Override
      public void setAdapter(Adapter adapter) {
          // 保存設置的適配器
          mAdapter = adapter;
          // 設置封裝的適配器
          innerAdapter = new InsideAdapter();
    
          super.setAdapter(innerAdapter);
    
          // 注冊觀察者
          mAdapter.registerAdapterDataObserver(mObserver);
      }
    
  • 至于下面的注冊觀察者,我們晚點再說,接下來就是這個 InsideAdapter,直接以內部類形式定義在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) {
    
              // 重寫方法,根據下標判斷布局類型
    
              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;
    
              // 根據布局類型,返回不同的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: // 普通布局類型返回設置的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() {
    
              // 根據功能開啟情況以及頭布局腳布局返回實際的條目數
              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);
              }
          }
      }
    
  • 先不說下拉刷新、上拉加載控件需要隱藏,實際運行起來,你會發現,如果使用 LinnerLayoutManager 是沒有問題的,但是如果使用的是 GridLayoutManager 或者是 StaggeredGridLayoutManager 你就會發現并沒有達到想象中的效果,這是因為我們的代碼中實際上只是在設置適配器的時候,添加了幾條數據,并沒有改變他的展示效果。而 RecyclerView 把布局展示的工作都交給了 LayoutManager,所以這個時候,為了能夠實現頭布局等能在 GridLayoutManager 和 StaggeredGridLayoutManager 下寬度也能MATCH_PARENT,我們就需要在 InsideAdapter 中重寫 onAttachedToRecyclerView 和 onViewAttachedToWindow 兩個方法:

      @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) { // 這個方法是返回當前對象所在行有幾列
                      return (isRefresh(position) || isLoadMore(position)
                              || isHeader(position) || isFooter(position))
                              ? gridManager.getSpanCount() : 1; // 如果是刷新、加載更多或頭布局、腳布局獨占一行,否則按照設置展示
                  }
              });
          }
      }
    
      @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;
              // 如果是刷新、加載更多或頭布局、腳布局獨占一行,否則按照設置展示
              p.setFullSpan(true); // 設置獨占一行
          }
      }
    
  • 這樣,即使布局為 GridLayoutManager 或者 StaggeredGridLayoutManager 刷新、加載更多、頭布局、腳布局都是單獨的一行了。

  • 接下來要實現的就是下拉刷新、上拉加載更多的功能了,這個實現思路其實和ListView下拉刷新是一致的,同樣通過設置刷新布局和加載更多布局的margin值來實現,其核心就是設置觸摸事件監聽,然后在 onTouch 方法中判斷不同的情況,做不同的處理。由于這部分內容比較復雜,筆者就不在這里細說了,有興趣的朋友可以在文章最后找到Github地址查看源碼,源碼里都有詳細注釋。

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

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

    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);
      }
    };
    
  • 這個觀察者所做的就是在使用者調用適配器的notifyDataSetChanged方法時,同步調用InnerAdapter的方法,因為通過setAdapter方法設置的適配器實際上是我們封裝的InnerAdapter,所以,當數據變更時,需要調用InnerAdapter的方法才能同步更新界面。

總結

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

推薦閱讀更多精彩內容