PullToRefreshRecyclerView是实现了下拉刷新与上滑加载更多功能的recyclerView,并且可自定义加载的view。这两个功能也是如今数据展现的必备功能了,传统的多以ListView实现,如果你想更加了解recyclerview特性或者你的项目想完全替换ListView使用recyclerview,那这个控件源码很值得学习。(ps:我这个伸手党通过阅读项目源码对recyclerview的使用理解更深了,推荐大家都去看看)
public interface PrvInterface{
void setOnRefreshComplete();//设置刷新完成
void setOnLoadMoreComplete();//onFinishLoading,加载更多完成
void setPagingableListener(PullToRefreshRecyclerView.PagingableListener pagingableListener);//设置上滑更多监听回调
void setEmptyView(View emptyView);//设置EmptyView
void setAdapter(RecyclerView.Adapter adapter);
void addHeaderView(View view);//添加header
void removeHeader();//移除header
void setFooter(View view);
void setLoadMoreFooter(BaseLoadMoreView loadMoreFooter);
void addOnScrollListener(PullToRefreshRecyclerView.OnScrollListener onScrollLinstener);
RecyclerView.LayoutManager getLayoutManager();
void onFinishLoading(boolean hasMoreItems, boolean needSetSelection);
void setSwipeEnable(boolean enable);//设置是否可以下拉
boolean isSwipeEnable();//返回当前组件是否可以下拉
RecyclerView getRecyclerView();
void setLayoutManager(RecyclerView.LayoutManager layoutManager);
void setLoadMoreCount(int count);//如果不达到count数量不让加载更多
void release();
}
我们都知道ListView有个setEmptyView的方法,很方便使用。可惜的是recyclerview并没有,需要自己去实现,该控件实现方式是通过重写RecyclerView.AdapterDataObserver的onChanged方法实现的,通过类名一看也大概了解是监听adapter数据变化的
代码实现如下:
private class AdapterObserver extends RecyclerView.AdapterDataObserver{
@Override
public void onChanged() {
super.onChanged();
//adapter has change
if(mRecyclerView == null){
//here must be wrong ,recyclerView is null????
return;
}
RecyclerView.Adapter> adapter = mRecyclerView.getAdapter();
if(adapter != null && mEmptyView != null) {
//如果adapter数据为空
if(adapter.getItemCount() == 0) {
//设置的是否刷新的标志位
if(mIsSwipeEnable) {
PullToRefreshRecyclerView.this.setEnabled(false);
}
//将我们设置的EmptyViwe进行显示
mEmptyView.setVisibility(View.VISIBLE);
mRecyclerView.setVisibility(View.GONE);
}
else {
if(mIsSwipeEnable) {
PullToRefreshRecyclerView.this.setEnabled(true);
}
mEmptyView.setVisibility(View.GONE);
mRecyclerView.setVisibility(View.VISIBLE);
}
}
}
}
使用也很简单,registerAdapterDataObserver设置我们自定义dataObserver的就ok了
public void setAdapter(RecyclerView.Adapter adapter) {
mRecyclerView.setAdapter(adapter);
if(mAdapterObserver == null){
mAdapterObserver = new AdapterObserver();
}
if(adapter != null){
adapter.registerAdapterDataObserver(mAdapterObserver);
mAdapterObserver.onChanged();
}
}
通过代码实现发现逻辑是很简单的,但是我们知道了recyclerview原来还有这样的方法(原谅我的无知…),这样以后我们还有什么需求都可以考虑使用这种方式实现了
一样的这也是ListViwe标准功能,我们想像使用ListView来使用recyclerview的化,我们也来自己实现一个吧,控件的实现是使用了addItemDecoration来绘制header的区域,recyclerview提供这个方法可以很自由的绘制任何view,该控件实现很巧妙,大概的思路是使用addItemDecoration来绘制与headerview高度一致的view为recyclerview“预留”出一个headerview的位置,然后再在根布局中add这个header,这样就使得recyclerview与headerview一起滑动,就像真的给recyclerview addHeaderView一样。
public class BaseHeader extends RecyclerView.ItemDecoration {
protected int mHeaderHeight;
public void setHeight(int height){
mHeaderHeight = height;
}
public int getHeight(){
return mHeaderHeight;
}
}
public class Header extends BaseHeader{
@Override
public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) {
if(itemPosition == 0) {
outRect.set(0, mHeaderHeight, 0, 0);
}
}
}
@Override
public void addHeaderView(View view) {
if(mHeader != null){
mRootRelativeLayout.removeView(mHeader);
}
mHeader = view;
if(mHeader == null){
return;
}
mHeader.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
mHeader.getViewTreeObserver().removeOnGlobalLayoutListener(this);
} else {
mHeader.getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
if (getRecyclerView() == null || mHeader == null) {
return;
}
if (mRootHeader == null) {
mRootHeader = new Header();
}
mRootHeader.setHeight(mHeader.getMeasuredHeight());
getRecyclerView().removeItemDecoration(mRootHeader);
//add height like header view
getRecyclerView().addItemDecoration(mRootHeader);
getRecyclerView().getAdapter().notifyDataSetChanged();
}
});
//实际add header view
mRootRelativeLayout.addView(mHeader);
}
与header view 一样,footerview也是RecyclerView.ItemDecoration,所以可以绘制任何你想得到的view来填充该区域,控件也提供了一个默认的view可以参考参考,如何实现footerview的显示与隐藏呢,控件是通过复写onDrawOver方法来实现,具体的是recyclerview滑动到最底部footer itemDecoration绘制完成时会回调该方法,我们通过判断当前数据源最后一个位置是不是当前可见最后一个位置,是的话绘制footerview,因为滑动到最底部我们会去刷新数据,数据源数据最后一个位置不是最后一个可见位置时自动就不显示了。
public class BaseLoadMoreView extends RecyclerView.ItemDecoration {
protected RecyclerView mRecyclerView;
protected String mLoadMoreString;
protected static final int MSG_INVILIDATE = 1;
protected long mUpdateTime = 150;
protected PullToRefreshRecyclerViewUtil mPtrrvUtil;
protected int mLoadMorePadding = 100;
public BaseLoadMoreView(Context context, RecyclerView recyclerView){
mRecyclerView = recyclerView;
mPtrrvUtil = new PullToRefreshRecyclerViewUtil();
}
public void setLoadmoreString(String str) {
mLoadMoreString = str;
}
public String getLoadmoreString(){
return mLoadMoreString;
}
public int getLoadMorePadding(){
return mLoadMorePadding;
}
public void setLoadMorePadding(int padding){
mLoadMorePadding = padding;
}
protected Handler mInvalidateHanlder = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (mRecyclerView == null || mRecyclerView.getAdapter() == null) {
return;
}
int lastItemPosition = mRecyclerView.getAdapter().getItemCount() - 1;
if (mPtrrvUtil.findLastVisibleItemPosition(mRecyclerView.getLayoutManager()) == lastItemPosition) {
//when the item is visiable do this method
// View view = mRecyclerView.getLayoutManager().findViewByPosition(lastItemPosition);
// mInvilidateRect.set(0, 0, view.getRight() - view.getLeft(), view.getBottom() - view.getTop());
mRecyclerView.invalidate();
}
}
};
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDrawOver(c, parent, state);
mInvalidateHanlder.removeMessages(MSG_INVILIDATE);
onDrawLoadMore(c, parent);
mInvalidateHanlder.sendEmptyMessageDelayed(MSG_INVILIDATE, mUpdateTime);
}
/**
* @param outRect
* @param itemPosition
* @param parent
*/
@Override
public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) {
if(itemPosition == parent.getAdapter().getItemCount() - 1) {
outRect.set(0, 0, 0, getLoadMorePadding());
}
}
protected void onDrawLoadMore(Canvas c, RecyclerView parent){
}
public void release(){
mRecyclerView = null;
}
}
/**
* 默认load more view
*/
public class DefaultLoadMoreView extends BaseLoadMoreView {
private Paint paint;
private RectF oval;
private int mCircleSize = 25;
private int mProgress = 30;//圆圈比例
private int mCircleOffset = 70;
public DefaultLoadMoreView(Context context, RecyclerView recyclerView) {
super(context, recyclerView);
paint = new Paint();
oval = new RectF();
setLoadmoreString(context.getString(R.string.loading));
// mLoadMoreString = context.getString(R.string.loading);
}
@Override
public void onDrawLoadMore(Canvas c, RecyclerView parent) {
mProgress = mProgress + 5;
if(mProgress == 100){
mProgress = 0;
}
final int left = parent.getPaddingLeft() ;
final int right = parent.getMeasuredWidth() - parent.getPaddingRight() ;
final int childSize = parent.getChildCount() ;
final View child = parent.getChildAt( childSize - 1 ) ;
RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) child.getLayoutParams();
final int top = child.getBottom() + layoutParams.bottomMargin ;
final int bottom = top + getLoadMorePadding()/2 ;
paint.setAntiAlias(true);// 抗锯齿
paint.setFlags(Paint.ANTI_ALIAS_FLAG);// 增强消除锯齿
paint.setColor(Color.GRAY);// 画笔为灰色
paint.setStrokeWidth(10);// 画笔宽度
paint.setStyle(Paint.Style.STROKE);// 中空
c.drawCircle((right - left) / 2 - mCircleOffset, bottom, mCircleSize, paint);//在中心为((right - left)/2,bottom)的地方画个半径为mCircleSize的圆,
paint.setColor(Color.GREEN);// 设置画笔为绿色
oval.set((right - left) / 2 - mCircleOffset - mCircleSize, bottom - mCircleSize, (right - left) / 2 - mCircleOffset + mCircleSize, bottom + mCircleSize);// 在Circle小于圈圈大小的地方画圆,这样也就保证了半径为mCircleSize
c.drawArc(oval, -90, ((float) mProgress / 100) * 360, false, paint);// 圆弧,第二个参数为:起始角度,第三个为跨的角度,第四个为true的时候是实心,false的时候为空心
paint.reset();// 将画笔重置
paint.setStrokeWidth(3);// 再次设置画笔的宽度
paint.setTextSize(40);// 设置文字的大小
paint.setColor(Color.BLACK);// 设置画笔颜色
c.drawText(getLoadmoreString(), (right - left) / 2, bottom + 10, paint);
}
}
该控件下拉刷新是基于android官方的swipeRefreshLayout实现的,这也是目前比较流行的方法。该控件是直接继承swipeRefreshLayout,所以下拉刷新部分与直接使用swipeRefreshLayout基本一致。
//使用如下两个接口可设置下拉刷新相关内容
void setOnRefreshComplete();
void setSwipeEnable(boolean enable);//设置是否可以下拉
上滑加载更多实现是基于监听RecyclerView.OnScrollListener的onScrolled实现的,首先我们可以通过recyclerview的LayoutManager得到当前可见的第一个与最后一个view的位置position,因为LayoutManager的实现类不同这里有个工具类
public class PullToRefreshRecyclerViewUtil {
//获取最后一个可见item的位置
public int findLastVisibleItemPosition(RecyclerView.LayoutManager layoutManager){
if(layoutManager != null) {
if (layoutManager instanceof LinearLayoutManager) {
return ((LinearLayoutManager) layoutManager).findLastVisibleItemPosition();
}
if (layoutManager instanceof GridLayoutManager) {
return ((GridLayoutManager) layoutManager).findLastVisibleItemPosition();
}
}
return RecyclerView.NO_POSITION;
}
//当第一个item完全可见时posion
public int findFirstCompletelyVisibleItemPosition(RecyclerView.LayoutManager layoutManager){
if(layoutManager != null) {
if (layoutManager instanceof LinearLayoutManager) {
return ((LinearLayoutManager) layoutManager).findFirstCompletelyVisibleItemPosition();
}
if (layoutManager instanceof GridLayoutManager) {
return ((GridLayoutManager) layoutManager).findFirstCompletelyVisibleItemPosition();
}
}
return RecyclerView.NO_POSITION;
}
//当前第一个item的posion
public int findFirstVisibleItemPosition(RecyclerView.LayoutManager layoutManager){
if(layoutManager != null) {
if (layoutManager instanceof LinearLayoutManager) {
return ((LinearLayoutManager) layoutManager).findFirstVisibleItemPosition();
}
if (layoutManager instanceof GridLayoutManager) {
return ((GridLayoutManager) layoutManager).findFirstVisibleItemPosition();
}
}
return RecyclerView.NO_POSITION;
}
}
然后重写onScrollListener
private class InterOnScrollListener extends RecyclerView.OnScrollListener{
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
//do super before callback
if(mOnScrollLinstener != null){
mOnScrollLinstener.onScrollStateChanged(recyclerView,newState);
}
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
//do super before callback
if(getLayoutManager() == null){
//here layoutManager is null
return;
}
mCurScroll = dy + mCurScroll;
if(mHeader != null) {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB) {
mHeader.setTranslationY(-mCurScroll);
}else {
ViewHelper.setTranslationY(mHeader, -mCurScroll);
}
}
int firstVisibleItem, visibleItemCount, totalItemCount, lastVisibleItem;
visibleItemCount = getLayoutManager().getChildCount();
totalItemCount = getLayoutManager().getItemCount();
firstVisibleItem = findFirstVisibleItemPosition();
//sometimes ,the last item is too big so as that the screen cannot show the item fully
lastVisibleItem = findLastVisibleItemPosition();
// lastVisibleItem = mLinearLayoutManager.findLastCompletelyVisibleItemPosition();
if(mIsSwipeEnable) {
if (findFirstCompletelyVisibleItemPosition() != 0) {
//here has a bug, if the item is too big , use findFirstCompletelyVisibleItemPosition will cannot swipe
PullToRefreshRecyclerView.this.setEnabled(false);
} else {
PullToRefreshRecyclerView.this.setEnabled(true);
}
}
if(totalItemCount < mLoadMoreCount){
setHasMoreItems(false);
isLoading = false;
}else if (!isLoading && hasMoreItems && ((lastVisibleItem + 1) == totalItemCount)) {
if (mPagingableListener != null) {
isLoading = true;
mPagingableListener.onLoadMoreItems();
}
}
if(mOnScrollLinstener != null){
mOnScrollLinstener.onScrolled(recyclerView, dx, dy);
mOnScrollLinstener.onScroll(recyclerView, firstVisibleItem, visibleItemCount, totalItemCount);
}
}
}