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>