UI之RecyclerView加载更多

LeaZEZW 8年前
   <h3><strong>效果图</strong></h3>    <p style="text-align:center"><img src="https://simg.open-open.com/show/22297bfd2ef263504091f91f5cfbc563.gif"></p>    <p style="text-align:center">anglerNRD90Tzhuleiyue09212016183407.gif</p>    <p><strong>RecyclerView实现加载更多可分为两个步骤</strong></p>    <ol>     <li> <p>RecyclerView滑动到底部的监听</p> </li>     <li> <p>给RecyclerView添加footer,展示加载状态</p> </li>    </ol>    <h3><strong>一、给RecyclerView添加ScrollListener监听滑动到底部</strong></h3>    <h3><strong>1. 继承RecyclerView添加滑动监听</strong></h3>    <pre>  <code class="language-java">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>    <h3><strong>2. 判断滑动到底部</strong></h3>    <p><strong>2.1 判断滑动方向</strong></p>    <p>给LoadMoreRecyclerView添加属性</p>    <pre>  <code class="language-java">/**   * 是否是向下滑动   */  private boolean isScrollDown;</code></pre>    <p>在OnScrollListener中的onScrolled方法中判断RecyclerView的滑动方向</p>    <pre>  <code class="language-java">@Override  public void onScrolled(RecyclerView recyclerView, int dx, int dy) {      super.onScrolled(recyclerView, dx, dy);      isScrollDown = dy > 0;  }</code></pre>    <p>RecyclerView在滑动完成的时候会调用onScrolled方法,其中dx和dy分别表示水平滑动和垂直滑动的距离</p>    <p>如果dy>0表示向下滑动</p>    <p><strong>2.2 判断滑动到底部</strong></p>    <p>在OnScrollListener中的onScrollStateChanged方法中判断RecyclerView是否滑动到底部</p>    <pre>  <code class="language-java">@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>    <p>RecyclerView的滑动状态改变时会调用onScrollStateChanged方法,其中newState表示RecyclerView的滑动状态</p>    <ul>     <li>SCROLL_STATE_IDLE 表示RecyclerView没有在滑动</li>     <li>SCROLL_STATE_DRAGGING 表示RecyclerView正在被拖着滑动</li>     <li>SCROLL_STATE_SETTLING 表示RecyclerView正在滑动但是没有外部控制</li>    </ul>    <h3><strong>3. 添加加载更多的回调接口</strong></h3>    <p><strong>3.1 创建加载更多回调接口</strong></p>    <pre>  <code class="language-java">/**   * 加载更多的回调接口   */  public interface OnLoadMore {      void onLoad();  }</code></pre>    <p><strong>3.2 给RecyclerView添加设置回调的方法</strong></p>    <pre>  <code class="language-java">/**   * 加载更多的回调接口   */  private OnLoadMore mOnLoadMore;    /**   * 是否加载更多   */  private boolean mIsLoadMore;    /**   * 设置加载更多的回调接口   * @param onLoadMore 加载更多的回调接口   */  public void setOnLoadMore(OnLoadMore onLoadMore) {      // 是否加载更多置为true      this.mIsLoadMore = true;      this.mOnLoadMore = onLoadMore;  }</code></pre>    <p>在判断滑动到底部的地方调用回调接口的回调方法</p>    <h2><strong>二、给RecyclerView添加footer展示加载状态</strong></h2>    <h3><strong>1. 继承Adapter添加footer</strong></h3>    <pre>  <code class="language-java">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>    <p>footer_view_sample.xml</p>    <pre>  <code class="language-java"><?xml version="1.0" encoding="utf-8"?>  <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"      android:layout_width="match_parent"      android:layout_height="wrap_content"      android:gravity="center_horizontal"      android:orientation="horizontal"      android:padding="16dp">        <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>    <h3><strong>2. 设置在GridLayoutManager和StaggeredGridLayoutManager下footer撑满一行</strong></h3>    <p><strong>2.1 GridLayoutManager</strong></p>    <p>重写Adapter的onAttachedToRecyclerView,获取GridLayoutManager</p>    <pre>  <code class="language-java">/**   * 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>    <p>重写onViewAttachedToWindow,给GridLayoutManager设置SpanSizeLookup</p>    <pre>  <code class="language-java">@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;                  }              });          }      }  }</code></pre>    <p><strong>2.2 StaggeredGridLayoutManager</strong></p>    <p>重写onViewAttachedToWindow</p>    <pre>  <code class="language-java">@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);              }          }      }  }</code></pre>    <h3><strong>3. 在LoadMoreRecyclerView的滑动监听中添加判断并设置footer状态</strong></h3>    <pre>  <code class="language-java">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>    <h3><strong>4. 在LoadMoreRecyclerView中重写setAdapter方法,设置footer状态和点击事件</strong></h3>    <pre>  <code class="language-java">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>    <p>ps: 关于onViewAttachedToWindow和onViewAttachedToWindow的说明</p>    <ul>     <li>onViewAttachedToWindow方法在RecyclerView调用setAdapter方法是被调用</li>     <li> <p>onViewAttachedToWindow方法在RecyclerView展示在界面上是被调用</p> <p>为了保证LoadMoreRecyclerView中setOnLoadMore和setAdapter调用的无序性,不能在onViewAttachedToWindow方法中设置GridLayoutManager的SpanSizeLookup</p> </li>    </ul>    <h2><strong>三、使用注意</strong></h2>    <p>因为重写了RecyclerView的setAdapter方法,把传如的adapter包装之后重新设置,所以在调用notifyDataSetChanged()等方法时,不能直接用自己创建adapter调用,而要使用RecyclerView.getAdapter调用。</p>    <p> </p>    <p> </p>    <p>来自:http://www.jianshu.com/p/de627ba8d902</p>    <p> </p>