上一篇 讲解了为RecylerView添加HeaderView, FooterView, EmptyView, 以及完成了对GloriousRecyclerView的初步封装
本篇我们将继续尝试为RecyclerView添加下拉刷新和上拉加载更多
一、为RecyclerView添加下拉刷新
对于已经了解SwipeRefreshLayout的同学请直接跳至 "二、为RecyclerView添加上拉加载更多"
添加下拉刷新其实很简单,android.support.v4.widget.SwipeRefreshLayout
已经很好用了,使用起来也比较方便
但是有个缺点,就是滑动到下面的时候,有时不能再滑上去,而是触发了下拉刷新
不过没关系,我们可以自己继承SwipeRefreshLayout,然后重写canChildScrollUp()
方法即可
public class ScrollChildSwipeRefreshLayout extends SwipeRefreshLayout {
private View mScrollUpChild;
public ScrollChildSwipeRefreshLayout(Context context) {
super(context);
}
public ScrollChildSwipeRefreshLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean canChildScrollUp() {
if (mScrollUpChild != null) {
return ViewCompat.canScrollVertically(mScrollUpChild, -1);
}
return super.canChildScrollUp();
}
public void setScrollUpChild(View view) {
mScrollUpChild = view;
}
}
集成
我们还是用GloriousRecyclerView为例
1.xml中加入
<com.xpc.gloriousrecyclerviewdemo.ScrollChildSwipeRefreshLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/refresh_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.xpc.gloriousrecyclerview.GloriousRecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</com.xpc.gloriousrecyclerviewdemo.ScrollChildSwipeRefreshLayout>
2.Actiivty中加入
protected void onCreate(Bundle savedInstanceState) {
//...
mRecyclerView = (GloriousRecyclerView) findViewById(R.id.recycler_view);
mSwipeRefreshLayout = (ScrollChildSwipeRefreshLayout) findViewById(R.id.refresh_layout);
mSwipeRefreshLayout.setScrollUpChild(mRecyclerView);
mSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
//这里做刷新的操作
//然后调用下面这句话告诉SwipeRefreshLayout已经加载完毕
mSwipeRefreshLayout.setRefreshing(false);
}
});
//...
}
效果展示
二、为RecyclerView添加上拉加载更多
Step 1. 添加 "加载更多" 的布局
由上一篇为RecyclerView添加HeaderView, FooterView和EmptyView我们已经知道,要为RecyclerView添加额外的childView需要在Adapter中定义指定的viewType, 所以我们这里添加下拉加载更多的原理是一样的,如果没看过的话请查看前面一篇文章,这里我就不在赘述了
Step 2. 判断是否滑到了当前数据的最后一条
这里,我们只是在RecylerView中加了一个childView, 但是我们要得效果并没有达到,我们还需要滑动到最后一条数据的时候,触发加载更多的操作
查看RecyclerView的源码发现有个滑动的事件监听
/**
* Add a listener that will be notified of any changes in scroll state or position.
*/
public void addOnScrollListener(OnScrollListener listener) {
//...
}
/**
* An OnScrollListener can be added to a RecyclerView to receive messages when a scrolling event
* has occurred on that RecyclerView.
*/
public abstract static class OnScrollListener {
public void onScrollStateChanged(RecyclerView recyclerView, int newState){}
public void onScrolled(RecyclerView recyclerView, int dx, int dy){}
}
所以我们可以继承OnScrollListener
然后调用RecyclerView
的addOnScrollListener(OnScrollListener listener)
方法
public class GloriousRecyclerView extends RecyclerView {
//...
private void init(){
this.addOnScrollListener(mOnScrollListener);
}
private OnScrollListener mOnScrollListener = 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);
//在这里判段是否滑动到了最后一条数据
/**
* LinearLayoutManager、GridLayoutManager、StaggeredGridLayoutManager都提供了一个叫做
* public int findLastVisibleItemPositions()的方法
* 这个方法会返回当前可见的最后一条Item的 position
* 当 position == 我们的真实数据的长度-1时即表示滑到了最后
* 这时我们就可以触发上拉加载更多了
*/
}
};
private int findLastVisibleItemPosition() {
int position;
if (getLayoutManager() instanceof LinearLayoutManager) {
position = ((LinearLayoutManager) getLayoutManager()).findLastVisibleItemPosition();
} else if (getLayoutManager() instanceof GridLayoutManager) {
position = ((GridLayoutManager) getLayoutManager()).findLastVisibleItemPosition();
} else if (getLayoutManager() instanceof StaggeredGridLayoutManager) {
StaggeredGridLayoutManager layoutManager = (StaggeredGridLayoutManager) getLayoutManager();
int[] lastPositions = layoutManager.findLastVisibleItemPositions(new int[layoutManager.getSpanCount()]);
position = findMaxPosition(lastPositions);
} else {
position = getLayoutManager().getItemCount() - 1;
}
return position;
}
//...
}
效果展示
三、完整源码
我已将完整代码及Demo上传至GitHub,查看完整源码请移步至
https://github.com/titanchen2000/GloriousRecyclerView
四、GloriousRecyclerView主要功能点
- addHeaderView //添加HeaderView
- addFooterView //添加FooterView
- setEmptyView //设置EmptyView
- setLoadMoreListener //下拉加载更多
- removeHeaderView //移除HeaderView
- removeFooterView //移除FooterView
- "Cumstomzie LoadMoreView" //客制化加载更多, 这里通过layout xml配置
五、快速集成GloriousRecyclerView
我已经将GloriousRecyclerView发布至jcenter仓库
Step 1. 添加依赖
Gradle
dependencies {
compile 'com.xpc:gloriousrecyclerview:0.2.5@aar'
compile 'com.android.support:recyclerview-v7:24.2.0'
}
Maven
<dependency>
<groupId>com.xpc</groupId>
<artifactId>gloriousrecyclerview</artifactId>
<version>0.2.5</version>
<type>pom</type>
</dependency>
Step 2. 布局中加入
<com.xpc.gloriousrecyclerview.GloriousRecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
或者像下面这样客制化加载更多
<com.xpc.gloriousrecyclerview.GloriousRecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:hideNoMoreData="true"
app:loadAllCompleteText="Load Complete"
app:loadMoreBackground="#ccc"
app:loadMoreFailedText="@string/loading_more_failed"
app:loadMoreIndeterminateDrawable="@drawable/loading_icon_drawable"
app:loadMoreTextColor="#0080ff"
app:loadMoreTextSize="14sp"
app:loadingMoreText="Loading More, Pls wait"/>
hideNoMoreData
: Hide the LoadMoreView When no more data, default istrue
loadMoreIndeterminateDrawable
: The ProgressBar Indeterminate Drawable of LoadMoreView
六、总结
本篇主要讲解了如何为RecyclerView添加添加下拉刷新和上拉加载更多,顺便告知了开源到GitHub的地址和jcenter的引用
世上无难事,只怕有心人,不要停留在只是想想的地步,要勇敢地迈出第一步,你会发现另一片天空
七、下篇预告
ExpandableRecylerView ,可分组显示和折叠的RecylerView
附录:GloriousRecyclerView.java完整代码
/**
* A full function RecyclerView integration of Header, Footer,EmptyView and Up Swipe To Load More
*
* @author cxp
* @version 0.2.5
* @see <a href="https://github.com/titanchen2000/GloriousRecyclerView">Source code in GitHub</a>
*/
public class GloriousRecyclerView extends RecyclerView {
private View mHeaderView;
private View mFooterView;
private View mEmptyView;
private View mLoadMoreView;
private TextView mTvLoadMore;
private ProgressBar mPbLoadMore;
private boolean mIsLoadMoreEnabled;
private boolean mIsLoadingMore;
private GloriousAdapter mGloriousAdapter;
private AutoLoadMoreListener mLoadMoreListener;
/**
* Hide the mLoadMoreView When no more data
*/
private boolean mIsHideNoMoreData;
private float mLoadMoreTextSize;
private int mLoadMoreTextColor;
private int mLoadMoreBackgroundColor;
private String mLoadingMoreText;
private String mLoadMoreFailedText;
private String mLoadAllCompleteText;
private int mLastPbLoadMoreVisibility;
private String mLastPbTvLoadMoreText;
/**
* The ProgressBar IndeterminateDrawable of mLoadMoreView
*/
private Drawable mLoadMorePbIndeterminateDrawable;
private OnScrollListener mOnScrollListener = 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);
if (mIsLoadMoreEnabled && !mIsLoadingMore && dy > 0) {
if (findLastVisibleItemPosition() == mGloriousAdapter.getItemCount() - 1) {
mIsLoadingMore = true;
mLoadMoreListener.onLoadMore();
}
}
}
};
public GloriousRecyclerView(Context context) {
this(context, null);
}
public GloriousRecyclerView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public GloriousRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
/*
* Add the customize of mLoadMoreView from layout
*/
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.GloriousRecyclerView);
mIsHideNoMoreData = a.getBoolean(R.styleable.GloriousRecyclerView_hideNoMoreData, false);
mLoadMoreTextColor = a.getColor(R.styleable.GloriousRecyclerView_loadMoreTextColor, 0xff888888);
mLoadMoreTextSize = a.getDimensionPixelSize(R.styleable.GloriousRecyclerView_loadMoreTextSize, getResources()
.getDimensionPixelSize(R.dimen.load_more_text_size));
mLoadMoreBackgroundColor = a.getColor(R.styleable.GloriousRecyclerView_loadMoreBackground, 0xffffffff);
int indeterminateDrawableResId = a.getResourceId(R.styleable
.GloriousRecyclerView_loadMoreIndeterminateDrawable, 0);
if (indeterminateDrawableResId != 0) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
mLoadMorePbIndeterminateDrawable = getResources().getDrawable(indeterminateDrawableResId, context
.getTheme());
} else {
//noinspection deprecation
mLoadMorePbIndeterminateDrawable = getResources().getDrawable(indeterminateDrawableResId);
}
}
mLoadingMoreText = a.getString(R.styleable.GloriousRecyclerView_loadingMoreText);
mLoadMoreFailedText = a.getString(R.styleable.GloriousRecyclerView_loadMoreFailedText);
mLoadAllCompleteText = a.getString(R.styleable.GloriousRecyclerView_loadAllCompleteText);
if (TextUtils.isEmpty(mLoadingMoreText)) mLoadingMoreText = getResources().getString(R.string.glorious_recyclerview_loading_more);
if (TextUtils.isEmpty(mLoadMoreFailedText)) mLoadMoreFailedText = getResources().getString(R.string.glorious_recyclerview_load_more_failed);
if (TextUtils.isEmpty(mLoadAllCompleteText)) mLoadAllCompleteText = getResources().getString(R.string.glorious_recyclerview_no_more_data);
a.recycle();
}
public interface AutoLoadMoreListener {
void onLoadMore();
}
/**
* Add the {@link GloriousRecyclerView} headerView
*
* @param view headerView
*/
public void addHeaderView(View view) {
if (view != null && mHeaderView == null) {
mHeaderView = view;
mGloriousAdapter.notifyItemInserted(0);
}
}
/**
* Add the {@link GloriousRecyclerView} footerView
*
* @param view footerView
*/
public void addFooterView(View view) {
if (view != null && mFooterView == null) {
mFooterView = view;
mGloriousAdapter.notifyItemInserted(mGloriousAdapter.getItemCount() - 1);
}
}
/**
* Remove the {@link GloriousRecyclerView} headerView
*/
public void removeHeaderView() {
if (mHeaderView != null) {
mHeaderView = null;
mGloriousAdapter.notifyItemRemoved(0);
}
}
/**
* Remove the {@link GloriousRecyclerView} footerView
*/
public void removeFooterView() {
if (mFooterView != null) {
mFooterView = null;
mGloriousAdapter.notifyItemRemoved(mGloriousAdapter.getItemCount() - 1);
}
}
/**
* Add the {@link GloriousRecyclerView} emptyView
*
* @param view emptyView
*/
public void setEmptyView(View view) {
mEmptyView = view;
mGloriousAdapter.notifyDataSetChanged();
}
/**
* Called this also means that loadMore enabled
*
* @param loadMoreListener loadMoreListener
*/
public void setLoadMoreListener(final AutoLoadMoreListener loadMoreListener) {
if (null != loadMoreListener) {
mLoadMoreListener = loadMoreListener;
mIsLoadMoreEnabled = true;
this.addOnScrollListener(mOnScrollListener);
}
}
/**
* @param adapter the adapter as normal RecyclerView you used {@link RecyclerView#setAdapter(Adapter)}.
* @see GloriousAdapter
*/
@Override
public void setAdapter(Adapter adapter) {
if (adapter != null) {
mGloriousAdapter = new GloriousAdapter(adapter);
}
super.setAdapter(mGloriousAdapter);
}
/**
* Called when load more data failed
*
* @see #notifyLoadMoreFinish(boolean, boolean)
*/
public void notifyLoadMoreFailed() {
notifyLoadMoreFinish(false, true);
}
/**
* Called when load more data successful
*
* @see #notifyLoadMoreFinish(boolean, boolean)
*/
public void notifyLoadMoreSuccessful(boolean hasMore) {
notifyLoadMoreFinish(true, hasMore);
}
/**
* Notify mLoadMoreView do some UI change
*
* @param success Is load more data successful
* @param hasMore Whether has more data to be loaded
* @see #setLoadMoreListener(AutoLoadMoreListener)
*/
private void notifyLoadMoreFinish(boolean success, boolean hasMore) {
this.clearOnScrollListeners();
mIsLoadingMore = false;
if (success) {
mGloriousAdapter.notifyDataSetChanged();
if (hasMore) {
mPbLoadMore.setVisibility(VISIBLE);
mTvLoadMore.setText(mLoadingMoreText);
this.addOnScrollListener(mOnScrollListener);
} else {
if (mIsHideNoMoreData) {
//the mLoadMoreView will GONE when no more data to be loaded
mIsLoadMoreEnabled = false;
} else {
//the mLoadMoreView will display "No More Data" when no more data to be loaded
//and the progressBar will GONE
mLoadMoreView.setOnClickListener(null);
mPbLoadMore.setVisibility(GONE);
mTvLoadMore.setText(mLoadAllCompleteText);
}
}
} else {
//the mLoadMoreView will display "Loading Failed, Click To Reload" when load more data failed
//and the progressBar will GONE
mTvLoadMore.setText(mLoadMoreFailedText);
mPbLoadMore.setVisibility(GONE);
}
mLastPbLoadMoreVisibility = mPbLoadMore.getVisibility();
mLastPbTvLoadMoreText = mTvLoadMore.getText().toString();
}
/**
* Find the last visible position depends on LayoutManger
*
* @return the last visible position
* @see #setLoadMoreListener(AutoLoadMoreListener)
*/
private int findLastVisibleItemPosition() {
int position;
if (getLayoutManager() instanceof LinearLayoutManager) {
position = ((LinearLayoutManager) getLayoutManager()).findLastVisibleItemPosition();
} else if (getLayoutManager() instanceof GridLayoutManager) {
position = ((GridLayoutManager) getLayoutManager()).findLastVisibleItemPosition();
} else if (getLayoutManager() instanceof StaggeredGridLayoutManager) {
StaggeredGridLayoutManager layoutManager = (StaggeredGridLayoutManager) getLayoutManager();
int[] lastPositions = layoutManager.findLastVisibleItemPositions(new int[layoutManager.getSpanCount()]);
position = findMaxPosition(lastPositions);
} else {
position = getLayoutManager().getItemCount() - 1;
}
return position;
}
/**
* Find StaggeredGridLayoutManager the last visible position
*
* @see #findLastVisibleItemPosition()
*/
private int findMaxPosition(int[] positions) {
int maxPosition = 0;
for (int position : positions) {
maxPosition = Math.max(maxPosition, position);
}
return maxPosition;
}
/**
* The decoration Adapter
*/
private class GloriousAdapter extends RecyclerView.Adapter<ViewHolder> {
private Adapter mOriginalAdapter;
private int ITEM_TYPE_NORMAL = 0;
private int ITEM_TYPE_HEADER = 1;
private int ITEM_TYPE_FOOTER = 2;
private int ITEM_TYPE_EMPTY = 3;
private int ITEM_TYPE_LOAD_MORE = 4;
public GloriousAdapter(Adapter originalAdapter) {
mOriginalAdapter = originalAdapter;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == ITEM_TYPE_HEADER) {
return new GloriousViewHolder(mHeaderView);
} else if (viewType == ITEM_TYPE_EMPTY) {
return new GloriousViewHolder(mEmptyView);
} else if (viewType == ITEM_TYPE_FOOTER) {
return new GloriousViewHolder(mFooterView);
} else if (viewType == ITEM_TYPE_LOAD_MORE) {
mLoadMoreView = LayoutInflater.from(getContext()).inflate(R.layout
.glorious_recyclerview_layout_load_more, parent, false);
/*
* Customize the mLoadMoreView
*/
mLoadMoreView.setBackgroundColor(mLoadMoreBackgroundColor);
mTvLoadMore = (TextView) mLoadMoreView.findViewById(R.id.tv_loading_more);
mPbLoadMore = (ProgressBar) mLoadMoreView.findViewById(R.id.pb_loading_more);
if (null != mLoadMorePbIndeterminateDrawable) {
mPbLoadMore.setIndeterminateDrawable(mLoadMorePbIndeterminateDrawable);
}
mTvLoadMore.getPaint().setTextSize(mLoadMoreTextSize);
mTvLoadMore.setTextColor(mLoadMoreTextColor);
mTvLoadMore.setText(mLoadingMoreText);
//Add reload strategy if load more failed
mLoadMoreView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (!mIsLoadingMore) {
mIsLoadingMore = true;
mPbLoadMore.setVisibility(VISIBLE);
mTvLoadMore.setText(mLoadingMoreText);
mTvLoadMore.setVisibility(VISIBLE);
mLoadMoreListener.onLoadMore();
}
}
});
//when remove footerView will trigger onCreateViewHolder()
if (!TextUtils.isEmpty(mLastPbTvLoadMoreText)) {
mTvLoadMore.setText(mLastPbTvLoadMoreText);
mPbLoadMore.setVisibility(mLastPbLoadMoreVisibility);
}
return new GloriousViewHolder(mLoadMoreView);
} else {
return mOriginalAdapter.onCreateViewHolder(parent, viewType);
}
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
int type = getItemViewType(position);
if (type == ITEM_TYPE_HEADER
|| type == ITEM_TYPE_FOOTER
|| type == ITEM_TYPE_EMPTY
|| type == ITEM_TYPE_LOAD_MORE) {
return;
}
int realPosition = getRealItemPosition(position);
mOriginalAdapter.onBindViewHolder(holder, realPosition);
}
@Override
public int getItemCount() {
//Get the real data counts
int itemCount = mOriginalAdapter.getItemCount();
/*
* Add the extra views counts
*/
if (null != mHeaderView) itemCount++;
if (null != mFooterView) itemCount++;
if (null != mEmptyView && itemCount == 0) {
//if the real data is empty, do not show loadMore any way
itemCount++;
return itemCount;
}
if (mIsLoadMoreEnabled) itemCount++;
return itemCount;
}
@Override
public int getItemViewType(int position) {
/*
* The sequence is very important, do not change or you will get wrong type
*/
if (null != mHeaderView && position == 0) return ITEM_TYPE_HEADER;
if (null != mFooterView && position == getItemCount() - 1) return ITEM_TYPE_FOOTER;
//if the real data is empty, do not show loadMore any way
if (null != mEmptyView && mOriginalAdapter.getItemCount() == 0) {
return ITEM_TYPE_EMPTY;
} else if (mIsLoadMoreEnabled && position == getLoadMorePosition()) {
return ITEM_TYPE_LOAD_MORE;
}
return ITEM_TYPE_NORMAL;
}
private int getRealItemPosition(int position) {
if (null != mHeaderView) {
return position - 1;
}
return position;
}
/**
* Get the loadMore position,
* <p>
* if mFooterView is null , loadMore position will display at the end,
* or it will display at the last but one
*
* @return the loadMore position
*/
private int getLoadMorePosition() {
if (null == mFooterView) {
return getItemCount() - 1;
} else {
return getItemCount() - 2;
}
}
/**
* GloriousViewHolder here is only let the extra views we add own a ViewHolder
*/
class GloriousViewHolder extends ViewHolder {
GloriousViewHolder(View itemView) {
super(itemView);
}
}
}
}