UI之RecyclerView加載更多

LeaZEZW 8年前發布 | 11K 次閱讀 Android開發 移動開發 RecyclerView

效果圖

anglerNRD90Tzhuleiyue09212016183407.gif

RecyclerView實現加載更多可分為兩個步驟

  1. RecyclerView滑動到底部的監聽

  2. 給RecyclerView添加footer,展示加載狀態

一、給RecyclerView添加ScrollListener監聽滑動到底部

1. 繼承RecyclerView添加滑動監聽

public class LoadMoreRecyclerView extends RecyclerView {

public LoadMoreRecyclerView(Context context) {
    this(context, null);
}

public LoadMoreRecyclerView(Context context, AttributeSet attrs) {
    this(context, attrs, 0);
}

public LoadMoreRecyclerView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    init();
}

private void init() {
    addOnScrollListener(new OnScrollListener() {
        @Override
        public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
            super.onScrollStateChanged(recyclerView, newState);
        }

        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            super.onScrolled(recyclerView, dx, dy);
        }
    });
}

}</code></pre>

2. 判斷滑動到底部

2.1 判斷滑動方向

給LoadMoreRecyclerView添加屬性

/**

  • 是否是向下滑動 */ private boolean isScrollDown;</code></pre>

    在OnScrollListener中的onScrolled方法中判斷RecyclerView的滑動方向

    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
     super.onScrolled(recyclerView, dx, dy);
     isScrollDown = dy > 0;
    }

    RecyclerView在滑動完成的時候會調用onScrolled方法,其中dx和dy分別表示水平滑動和垂直滑動的距離

    如果dy>0表示向下滑動

    2.2 判斷滑動到底部

    在OnScrollListener中的onScrollStateChanged方法中判斷RecyclerView是否滑動到底部

    @Override
    public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
     super.onScrollStateChanged(recyclerView, newState);
     if (newState == RecyclerView.SCROLL_STATE_IDLE) {// RecyclerView已經停止滑動
    
     int lastVisibleItem;
     // 獲取RecyclerView的LayoutManager
     LayoutManager layoutManager = recyclerView.getLayoutManager();
     // 獲取到最后一個可見的item
     if (layoutManager instanceof LinearLayoutManager) {// 如果是LinearLayoutManager
         lastVisibleItem = ((LinearLayoutManager) layoutManager).findLastVisibleItemPosition();
     } else if (layoutManager instanceof StaggeredGridLayoutManager) {// 如果是StaggeredGridLayoutManager
         int[] into = new int[((StaggeredGridLayoutManager) layoutManager).getSpanCount()];
         ((StaggeredGridLayoutManager) layoutManager).findLastVisibleItemPositions(into);
         lastVisibleItem = findMax(into);
     } else {// 否則拋出異常
         throw new RuntimeException("Unsupported LayoutManager used");
     }
     // 獲取item的總數
     int totalItemCount = layoutManager.getItemCount();
         /*
             并且最后一個可見的item為最后一個item
             并且是向下滑動
          */
     if (lastVisibleItem >= totalItemCount - 1 && isScrollDown) {
         // 此處調用加載更多回調接口的回調方法
     }
    
    } }

/**

  • 獲取數組中的最大值 *
  • @param lastPositions 需要找到最大值的數組
  • @return 數組中的最大值 */ private int findMax(int[] lastPositions) { int max = lastPositions[0]; for (int value : lastPositions) {
     if (value > max) {
         max = value;
     }
    
    } return max; }</code></pre>

    RecyclerView的滑動狀態改變時會調用onScrollStateChanged方法,其中newState表示RecyclerView的滑動狀態

    • SCROLL_STATE_IDLE 表示RecyclerView沒有在滑動
    • SCROLL_STATE_DRAGGING 表示RecyclerView正在被拖著滑動
    • SCROLL_STATE_SETTLING 表示RecyclerView正在滑動但是沒有外部控制

    3. 添加加載更多的回調接口

    3.1 創建加載更多回調接口

    /**
  • 加載更多的回調接口 */ public interface OnLoadMore { void onLoad(); }</code></pre>

    3.2 給RecyclerView添加設置回調的方法

    /**
  • 加載更多的回調接口 */ private OnLoadMore mOnLoadMore;

/**

  • 是否加載更多 */ private boolean mIsLoadMore;

/**

  • 設置加載更多的回調接口
  • @param onLoadMore 加載更多的回調接口 */ public void setOnLoadMore(OnLoadMore onLoadMore) { // 是否加載更多置為true this.mIsLoadMore = true; this.mOnLoadMore = onLoadMore; }</code></pre>

    在判斷滑動到底部的地方調用回調接口的回調方法

    二、給RecyclerView添加footer展示加載狀態

    1. 繼承Adapter添加footer

    private static class LoadMoreAdapter extends Adapter {
     /**

    • 添加footer的類型 */ private static final int TYPE_FOOTER = -1; /**
    • footer的狀態 */ protected int mLoadMoreStatus = STATUS_PREPARE; /**
    • footer的點擊事件 */ protected View.OnClickListener mListener; /**
    • 正常item的adapter */ private Adapter mAdapter; /**
    • 是否加載更多 */ private boolean mIsLoadMore; /**
    • GridLayoutManager */ private GridLayoutManager mGridLayoutManager;

      public LoadMoreAdapter(Adapter adapter, boolean isLoadMore) { this.mAdapter = adapter; this.mIsLoadMore = isLoadMore; }

      @Override public void onAttachedToRecyclerView(RecyclerView recyclerView) { super.onAttachedToRecyclerView(recyclerView); if (recyclerView.getLayoutManager() instanceof GridLayoutManager) {

       this.mGridLayoutManager = (GridLayoutManager) recyclerView.getLayoutManager();
      

      } }

      @Override public void onViewAttachedToWindow(ViewHolder holder) { super.onViewAttachedToWindow(holder); if (mIsLoadMore) {// 如果加載更多

       if (mGridLayoutManager != null) {
           mGridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
               @Override
               public int getSpanSize(int position) {
                   // 當position為最后一項時返回spanCount
                   return position == getItemCount() - 1 ? mGridLayoutManager.getSpanCount() : 1;
               }
           });
       }
       ViewGroup.LayoutParams params = holder.itemView.getLayoutParams();
       if (params instanceof StaggeredGridLayoutManager.LayoutParams) {
           if (holder.getLayoutPosition() == getItemCount() - 1) { // 當position為最后一項時這是FullSpan為true
               ((StaggeredGridLayoutManager.LayoutParams) params).setFullSpan(true);
           }
       }
      

      } }

      /**

    • 如果是footer類型,創建FooterView
    • 否則創建正常的ItemView */ @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (mIsLoadMore && viewType == TYPE_FOOTER) {

       return onCreateFooterViewHolder(parent);
      

      } else {

       return mAdapter.onCreateViewHolder(parent, viewType);
      

      } }

      /**

    • 如果加載更多且是footer類型,則展示footer
    • 否則展示正常的item */ @Override public void onBindViewHolder(ViewHolder holder, int position) { if (mIsLoadMore && getItemViewType(position) == TYPE_FOOTER) {

       bindFooterItem(holder);
      

      } else {

       mAdapter.onBindViewHolder(holder, position);
      

      } }

      /**

    • 如果加載更多
    • 如果正常的item為0 則不顯示footer,返回0
    • 如果正常的item不為0 則返回mAdapter.getItemCount() + 1
    • 如果不加載更多
    • 返回mAdapter.getItemCount() */ @Override public int getItemCount() { return mIsLoadMore ? mAdapter.getItemCount() == 0 ? 0 : mAdapter.getItemCount() + 1 : mAdapter.getItemCount(); }

      /**

    • 如果加載更多且position為最有一個,則返回類型為footer
    • 否則返回mAdapter.getItemViewType(position) */ @Override public int getItemViewType(int position) { if (mIsLoadMore && position == getItemCount() - 1) {

       return TYPE_FOOTER;
      

      } else {

       return mAdapter.getItemViewType(position);
      

      } }

      /**

    • 設置footer的狀態,并通知更改 */ void setLoadMoreStatus(int status) { this.mLoadMoreStatus = status; notifyItemChanged(getItemCount() - 1); }

      /**

    • 設置footer的點擊重試事件
    • @param listener */ public void setRetryListener(View.OnClickListener listener) { this.mListener = listener; }

      public int getLoadMoreStatus() { return this.mLoadMoreStatus; }

      /**

    • 創建FooterView */ public ViewHolder onCreateFooterViewHolder(ViewGroup parent) { return new FooterViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.footer_view_sample, parent, false)); }

      /**

    • 設置是否加載更多 */ public void setIsLoadMore(boolean isLoadMore) { this.mIsLoadMore = isLoadMore; }

      /**

    • 展示FooterView
    • @param holder */ protected void bindFooterItem(ViewHolder holder) { FooterViewHolder footerViewHolder = (FooterViewHolder) holder; switch (mLoadMoreStatus) {
       case STATUS_LOADING:
           holder.itemView.setVisibility(View.VISIBLE);
           footerViewHolder.pb.setVisibility(View.VISIBLE);
           footerViewHolder.tv.setText("正在加載更多...");
           break;
       case STATUS_EMPTY:
           holder.itemView.setVisibility(View.VISIBLE);
           footerViewHolder.pb.setVisibility(View.GONE);
           footerViewHolder.tv.setText("沒有更多了");
           holder.itemView.setOnClickListener(null);
           break;
       case STATUS_ERROR:
           holder.itemView.setVisibility(View.VISIBLE);
           footerViewHolder.pb.setVisibility(View.GONE);
           footerViewHolder.tv.setText("加載出錯,點擊重試");
           holder.itemView.setOnClickListener(mListener);
           break;
       case STATUS_PREPARE:
           holder.itemView.setVisibility(View.INVISIBLE);
           break;
       case STATUS_DISMISS:
           holder.itemView.setVisibility(GONE);
      
      } } }

static class FooterViewHolder extends RecyclerView.ViewHolder { ProgressBar pb; TextView tv;

public FooterViewHolder(View itemView) {
    super(itemView);
    pb = (ProgressBar) itemView.findViewById(R.id.pb_footer_view);
    tv = (TextView) itemView.findViewById(R.id.tv_footer_view);
}

}</code></pre>

footer_view_sample.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="

<ProgressBar
    android:id="@+id/pb_footer_view"
    style="@android:style/Widget.ProgressBar.Small"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />

<TextView
    android:id="@+id/tv_footer_view"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginLeft="8dp"
    android:gravity="center"
    android:text="正在加載更多..." />

</LinearLayout></code></pre>

2. 設置在GridLayoutManager和StaggeredGridLayoutManager下footer撐滿一行

2.1 GridLayoutManager

重寫Adapter的onAttachedToRecyclerView,獲取GridLayoutManager

/**

  • GridLayoutManager */ private GridLayoutManager mGridLayoutManager;

@Override public void onAttachedToRecyclerView(RecyclerView recyclerView) { super.onAttachedToRecyclerView(recyclerView); if (recyclerView.getLayoutManager() instanceof GridLayoutManager) { this.mGridLayoutManager = (GridLayoutManager) recyclerView.getLayoutManager(); } }</code></pre>

重寫onViewAttachedToWindow,給GridLayoutManager設置SpanSizeLookup

@Override
public void onViewAttachedToWindow(ViewHolder holder) {
    super.onViewAttachedToWindow(holder);
    if (mIsLoadMore) {// 如果加載更多
        if (mGridLayoutManager != null) {
            mGridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
                @Override
                public int getSpanSize(int position) {
                    // 當position為最后一項時返回spanCount
                    return position == getItemCount() - 1 ? mGridLayoutManager.getSpanCount() : 1;
                }
            });
        }
    }
}

2.2 StaggeredGridLayoutManager

重寫onViewAttachedToWindow

@Override
public void onViewAttachedToWindow(ViewHolder holder) {
    super.onViewAttachedToWindow(holder);
    if (mIsLoadMore) {// 如果加載更多
        ViewGroup.LayoutParams params = holder.itemView.getLayoutParams();
        if (params instanceof StaggeredGridLayoutManager.LayoutParams) {
            if (holder.getLayoutPosition() == getItemCount() - 1) { // 當position為最后一項時這是FullSpan為true
                ((StaggeredGridLayoutManager.LayoutParams) params).setFullSpan(true);
            }
        }
    }
}

3. 在LoadMoreRecyclerView的滑動監聽中添加判斷并設置footer狀態

private void init() {
    addOnScrollListener(new OnScrollListener() {
        @Override
        public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
            super.onScrollStateChanged(recyclerView, newState);
            if (mOnLoadMore != null) {// 如果加載更多的回調接口不為空
                if (newState == RecyclerView.SCROLL_STATE_IDLE) {// RecyclerView已經停止滑動
                    ...
                    /
                        如果RecyclerView的footer的狀態為準備中
                        并且最后一個可見的item為最后一個item
                        并且是向下滑動
                     /
                    if (mLoadMoreAdapter.getLoadMoreStatus() == STATUS_PREPARE
                            && lastVisibleItem >= totalItemCount - 1 && isScrollDown) {
                        // 設置RecyclerView的footer的狀態為加載中
                        mLoadMoreAdapter.setLoadMoreStatus(STATUS_LOADING);
                        // 觸發加載更多的回調方法
                        mOnLoadMore.onLoad();
                    }
                }
            }
        }

    ...
});

}</code></pre>

4. 在LoadMoreRecyclerView中重寫setAdapter方法,設置footer狀態和點擊事件

private LoadMoreAdapter mLoadMoreAdapter;

public void setAdapter(Adapter adapter) { this.mLoadMoreAdapter = new LoadMoreAdapter(adapter, mIsLoadMore); this.mLoadMoreAdapter.setRetryListener(retryListener); super.setAdapter(mLoadMoreAdapter); }

/**

  • 設置footer的狀態 */ public void setLoadMoreStatus(int status) { if (mLoadMoreAdapter != null) {
     mLoadMoreAdapter.setLoadMoreStatus(status);
    
    } }

/**

  • footer的重試點擊事件 */ View.OnClickListener retryListener = new View.OnClickListener() { @Override public void onClick(View v) {
     mLoadMoreAdapter.setLoadMoreStatus(STATUS_LOADING);
     mOnLoadMore.onLoad();
    
    } };</code></pre>

    ps: 關于onViewAttachedToWindow和onViewAttachedToWindow的說明

    • onViewAttachedToWindow方法在RecyclerView調用setAdapter方法是被調用
    • onViewAttachedToWindow方法在RecyclerView展示在界面上是被調用

      為了保證LoadMoreRecyclerView中setOnLoadMore和setAdapter調用的無序性,不能在onViewAttachedToWindow方法中設置GridLayoutManager的SpanSizeLookup

    三、使用注意

    因為重寫了RecyclerView的setAdapter方法,把傳如的adapter包裝之后重新設置,所以在調用notifyDataSetChanged()等方法時,不能直接用自己創建adapter調用,而要使用RecyclerView.getAdapter調用。

     

     

    來自:http://www.jianshu.com/p/de627ba8d902

     

 本文由用戶 LeaZEZW 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!