public class XRecyclerView extends RecyclerView {
private boolean isLoadingData = false;
private boolean isNoMore = false;
private int mRefreshProgressStyle = ProgressStyle.SysProgress;
private int mLoadingMoreProgressStyle = ProgressStyle.SysProgress;
private ArrayList<View> mHeaderViews = new ArrayList<>();
private WrapAdapter mWrapAdapter;
private float mLastY = -1;
private static final float DRAG_RATE = 3;
private LoadingListener mLoadingListener;
private ArrowRefreshHeader mRefreshHeader;
private boolean pullRefreshEnabled = true;
private boolean loadingMoreEnabled = true;
//下面的ItemViewType是保留值(ReservedItemViewType),如果用户的adapter与它们重复将会强制抛出异常。不过为了简化,我们检测到重复时对用户的提示是ItemViewType必须小于10000
private static final int TYPE_REFRESH_HEADER = 10000;//设置一个很大的数字,尽可能避免和用户的adapter冲突
private static final int TYPE_FOOTER = 10001;
private static final int HEADER_INIT_INDEX = 10002;
private static List<Integer> sHeaderTypes = new ArrayList<>();//每个header必须有不同的type,不然滚动的时候顺序会变化
private int mPageCount = 0;
//adapter没有数据的时候显示,类似于listView的emptyView
private View mEmptyView;
private View mFootView;
private final RecyclerView.AdapterDataObserver mDataObserver = new DataObserver();
private AppBarStateChangeListener.State appbarState = AppBarStateChangeListener.State.EXPANDED;
public XRecyclerView(Context context) {
this(context, null);
}
public XRecyclerView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public XRecyclerView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init() {
if (pullRefreshEnabled) {
mRefreshHeader = new ArrowRefreshHeader(getContext());
mRefreshHeader.setProgressStyle(mRefreshProgressStyle);
}
LoadingMoreFooter footView = new LoadingMoreFooter(getContext());
footView.setProgressStyle(mLoadingMoreProgressStyle);
mFootView = footView;
mFootView.setVisibility(GONE);
}
public void addHeaderView(View view) {
sHeaderTypes.add(HEADER_INIT_INDEX + mHeaderViews.size());
mHeaderViews.add(view);
if (mWrapAdapter != null) {
mWrapAdapter.notifyDataSetChanged();
}
}
//根据header的ViewType判断是哪个header
private View getHeaderViewByType(int itemType) {
if(!isHeaderType(itemType)) {
return null;
}
return mHeaderViews.get(itemType - HEADER_INIT_INDEX);
}
//判断一个type是否为HeaderType
private boolean isHeaderType(int itemViewType) {
return mHeaderViews.size() > 0 && sHeaderTypes.contains(itemViewType);
}
//判断是否是XRecyclerView保留的itemViewType
private boolean isReservedItemViewType(int itemViewType) {
if(itemViewType == TYPE_REFRESH_HEADER || itemViewType == TYPE_FOOTER || sHeaderTypes.contains(itemViewType)) {
return true;
} else {
return false;
}
}
public void setFootView(final View view) {
mFootView = view;
}
public void loadMoreComplete() {
isLoadingData = false;
if (mFootView instanceof LoadingMoreFooter) {
((LoadingMoreFooter) mFootView).setState(LoadingMoreFooter.STATE_COMPLETE);
} else {
mFootView.setVisibility(View.GONE);
}
}
public void setNoMore(boolean noMore){
isLoadingData = false;
isNoMore = noMore;
if (mFootView instanceof LoadingMoreFooter) {
((LoadingMoreFooter) mFootView).setState(isNoMore ? LoadingMoreFooter.STATE_NOMORE:LoadingMoreFooter.STATE_COMPLETE);
} else {
mFootView.setVisibility(View.GONE);
}
}
public void refresh() {
if (pullRefreshEnabled && mLoadingListener != null) {
mRefreshHeader.setState(ArrowRefreshHeader.STATE_REFRESHING);
mLoadingListener.onRefresh();
}
}
public void reset(){
setNoMore(false);
loadMoreComplete();
refreshComplete();
}
public void refreshComplete() {
mRefreshHeader.refreshComplete();
setNoMore(false);
}
public void setRefreshHeader(ArrowRefreshHeader refreshHeader) {
mRefreshHeader = refreshHeader;
}
public void setPullRefreshEnabled(boolean enabled) {
pullRefreshEnabled = enabled;
}
public void setLoadingMoreEnabled(boolean enabled) {
loadingMoreEnabled = enabled;
if (!enabled) {
if (mFootView instanceof LoadingMoreFooter) {
((LoadingMoreFooter)mFootView).setState(LoadingMoreFooter.STATE_COMPLETE);
}
}
}
public void setRefreshProgressStyle(int style) {
mRefreshProgressStyle = style;
if (mRefreshHeader != null) {
mRefreshHeader.setProgressStyle(style);
}
}
public void setLoadingMoreProgressStyle(int style) {
mLoadingMoreProgressStyle = style;
if (mFootView instanceof LoadingMoreFooter) {
((LoadingMoreFooter) mFootView).setProgressStyle(style);
}
}
public void setArrowImageView(int resId) {
if (mRefreshHeader != null) {
mRefreshHeader.setArrowImageView(resId);
}
}
public void setEmptyView(View emptyView) {
this.mEmptyView = emptyView;
mDataObserver.onChanged();
}
public View getEmptyView() {
return mEmptyView;
}
@Override
public void setAdapter(Adapter adapter) {
mWrapAdapter = new WrapAdapter(adapter);
super.setAdapter(mWrapAdapter);
adapter.registerAdapterDataObserver(mDataObserver);
mDataObserver.onChanged();
}
//避免用户自己调用getAdapter() 引起的ClassCastException
@Override
public Adapter getAdapter() {
if(mWrapAdapter != null)
return mWrapAdapter.getOriginalAdapter();
else
return null;
}
@Override
public void setLayoutManager(LayoutManager layout) {
super.setLayoutManager(layout);
if(mWrapAdapter != null){
if (layout instanceof GridLayoutManager) {
final GridLayoutManager gridManager = ((GridLayoutManager) layout);
gridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
return (mWrapAdapter.isHeader(position) || mWrapAdapter.isFooter(position) || mWrapAdapter.isRefreshHeader(position))
? gridManager.getSpanCount() : 1;
}
});
}
}
}
@Override
public void onScrollStateChanged(int state) {
super.onScrollStateChanged(state);
if (state == RecyclerView.SCROLL_STATE_IDLE && mLoadingListener != null && !isLoadingData && loadingMoreEnabled) {
LayoutManager layoutManager = getLayoutManager();
int lastVisibleItemPosition;
if (layoutManager instanceof GridLayoutManager) {
lastVisibleItemPosition = ((GridLayoutManager) layoutManager).findLastVisibleItemPosition();
} else if (layoutManager instanceof StaggeredGridLayoutManager) {
int[] into = new int[((StaggeredGridLayoutManager) layoutManager).getSpanCount()];
((StaggeredGridLayoutManager) layoutManager).findLastVisibleItemPositions(into);
lastVisibleItemPosition = findMax(into);
} else {
lastVisibleItemPosition = ((LinearLayoutManager) layoutManager).findLastVisibleItemPosition();
}
if (layoutManager.getChildCount() > 0
&& lastVisibleItemPosition >= layoutManager.getItemCount() - 1 && layoutManager.getItemCount() > layoutManager.getChildCount() && !isNoMore && mRefreshHeader.getState() < ArrowRefreshHeader.STATE_REFRESHING) {
isLoadingData = true;
if (mFootView instanceof LoadingMoreFooter) {
((LoadingMoreFooter) mFootView).setState(LoadingMoreFooter.STATE_LOADING);
} else {
mFootView.setVisibility(View.VISIBLE);
}
mLoadingListener.onLoadMore();
}
}
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
if (mLastY == -1) {
mLastY = ev.getRawY();
}
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
mLastY = ev.getRawY();
break;
case MotionEvent.ACTION_MOVE:
final float deltaY = ev.getRawY() - mLastY;
mLastY = ev.getRawY();
if (isOnTop() && pullRefreshEnabled && appbarState == AppBarStateChangeListener.State.EXPANDED) {
mRefreshHeader.onMove(deltaY / DRAG_RATE);
if (mRefreshHeader.getVisibleHeight() > 0 && mRefreshHeader.getState() < ArrowRefreshHeader.STATE_REFRESHING) {
return false;
}
}
break;
default:
mLastY = -1; // reset
if (isOnTop() && pullRefreshEnabled && appbarState == AppBarStateChangeListener.State.EXPANDED) {
if (mRefreshHeader.releaseAction()) {
if (mLoadingListener != null) {
mLoadingListener.onRefresh();
}
}
}
break;
}
return super.onTouchEvent(ev);
}
private int findMax(int[] lastPositions) {
int max = lastPositions[0];
for (int value : lastPositions) {
if (value > max) {
max = value;
}
}
return max;
}
private boolean isOnTop() {
if (mRefreshHeader.getParent() != null) {
return true;
} else {
return false;
}
}
private class DataObserver extends RecyclerView.AdapterDataObserver {
@Override
public void onChanged() {
if (mWrapAdapter != null) {
mWrapAdapter.notifyDataSetChanged();
}
if (mWrapAdapter != null && mEmptyView != null) {
int emptyCount = 1 + mWrapAdapter.getHeadersCount();
if (loadingMoreEnabled) {
emptyCount++;
}
if (mWrapAdapter.getItemCount() == emptyCount) {
mEmptyView.setVisibility(View.VISIBLE);
XRecyclerView.this.setVisibility(View.GONE);
} else {
mEmptyView.setVisibility(View.GONE);
XRecyclerView.this.setVisibility(View.VISIBLE);
}
}
}
@Override
public void onItemRangeInserted(int positionStart, int itemCount) {
mWrapAdapter.notifyItemRangeInserted(positionStart, itemCount);
}
@Override
public void onItemRangeChanged(int positionStart, int itemCount) {
mWrapAdapter.notifyItemRangeChanged(positionStart, itemCount);
}
@Override
public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
mWrapAdapter.notifyItemRangeChanged(positionStart, itemCount, payload);
}
@Override
public void onItemRangeRemoved(int positionStart, int itemCount) {
mWrapAdapter.notifyItemRangeRemoved(positionStart, itemCount);
}
@Override
public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
mWrapAdapter.notifyItemMoved(fromPosition, toPosition);
}
};
private class WrapAdapter extends RecyclerView.Adapter<ViewHolder> {
private RecyclerView.Adapter adapter;
public WrapAdapter(RecyclerView.Adapter adapter) {
this.adapter = adapter;
}
public RecyclerView.Adapter getOriginalAdapter(){
return this.adapter;
}
public boolean isHeader(int position) {
return position >= 1 && position < mHeaderViews.size() + 1;
}
public boolean isFooter(int position) {
if(loadingMoreEnabled) {
return position == getItemCount() - 1;
}else {
return false;
}
}
public boolean isRefreshHeader(int position) {
return position == 0;
}
public int getHeadersCount() {
return mHeaderViews.size();
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == TYPE_REFRESH_HEADER) {
return new SimpleViewHolder(mRefreshHeader);
} else if (isHeaderType(viewType)) {
return new SimpleViewHolder(getHeaderViewByType(viewType));
} else if (viewType == TYPE_FOOTER) {
return new SimpleViewHolder(mFootView);
}
return adapter.onCreateViewHolder(parent, viewType);
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (isHeader(position) || isRefreshHeader(position)) {
return;
}
int adjPosition = position - (getHeadersCount() + 1);
int adapterCount;
if (adapter != null) {
adapterCount = adapter.getItemCount();
if (adjPosition < adapterCount) {
adapter.onBindViewHolder(holder, adjPosition);
}
}
}
// some times we need to override this
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position,List<Object> payloads) {
if (isHeader(position) || isRefreshHeader(position)) {
return;
}
int adjPosition = position - (getHeadersCount() + 1);
int adapterCount;
if (adapter != null) {
adapterCount = adapter.getItemCount();
if (adjPosition < adapterCount) {
if(payloads.isEmpty()){
adapter.onBindViewHolder(holder, adjPosition);
}
else{
adapter.onBindViewHolder(holder, adjPosition,payloads);
}
}
}
}
@Override
public int getItemCount() {
if(loadingMoreEnabled) {
if (adapter != null) {
return getHeadersCount() + adapter.getItemCount() + 2;
} else {
return getHeadersCount() + 2;
}
}else {
if (adapter != null) {
return getHeadersCount() + adapter.getItemCount() + 1;
} else {
return getHeadersCount() + 1;
}
}
}
@Override
public int getItemViewType(int position) {
int adjPosition = position - (getHeadersCount() + 1);
if (isRefreshHeader(position)) {
return TYPE_REFRESH_HEADER;
}
if (isHeader(position)) {
position = position - 1;
return sHeaderTypes.get(position);
}
if (isFooter(position)) {
return TYPE_FOOTER;
}
int adapterCount;
if (adapter != null) {
adapterCount = adapter.getItemCount();
if (adjPosition < adapterCount) {
int type = adapter.getItemViewType(adjPosition);
if(isReservedItemViewType(type)) {
throw new IllegalStateException("XRecyclerView require itemViewType in adapter should be less than 10000 " );
}
return type;
}
}
return 0;
}
@Override
public long getItemId(int position) {
if (adapter != null && position >= getHeadersCount() + 1) {
int adjPosition = position - (getHeadersCount() + 1);
if (adjPosition < adapter.getItemCount()) {
return adapter.getItemId(adjPosition);
}
}
return -1;
}
@Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
super.onAttachedToRecyclerView(recyclerView);
RecyclerView.LayoutManager manager = recyclerView.getLayoutManager();
if (manager instanceof GridLayoutManager) {
final GridLayoutManager gridManager = ((GridLayoutManager) manager);
gridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
return (isHeader(position) || isFooter(position) || isRefreshHeader(position))
? gridManager.getSpanCount() : 1;
}
});
}
adapter.onAttachedToRecyclerView(recyclerView);
}
@Override
public void onDetachedFromRecyclerView(RecyclerView recyclerView) {
adapter.onDetachedFromRecyclerView(recyclerView);
}
@Override
public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) {
super.onViewAttachedToWindow(holder);
ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
if (lp != null
&& lp instanceof StaggeredGridLayoutManager.LayoutParams
&& (isHeader(holder.getLayoutPosition()) ||isRefreshHeader(holder.getLayoutPosition()) || isFooter(holder.getLayoutPosition()))) {
StaggeredGridLayoutManager.LayoutParams p = (StaggeredGridLayoutManager.LayoutParams) lp;
p.setFullSpan(true);
}
adapter.onViewAttachedToWindow(holder);
}
@Override
public void onViewDetachedFromWindow(RecyclerView.ViewHolder holder) {
adapter.onViewDetachedFromWindow(holder);
}
@Override
public void onViewRecycled(RecyclerView.ViewHolder holder) {
adapter.onViewRecycled(holder);
}
@Override
public boolean onFailedToRecycleView(RecyclerView.ViewHolder holder) {
return adapter.onFailedToRecycleView(holder);
}
@Override
public void unregisterAdapterDataObserver(AdapterDataObserver observer) {
adapter.unregisterAdapterDataObserver(observer);
}
@Override
public void registerAdapterDataObserver(AdapterDataObserver observer) {
adapter.registerAdapterDataObserver(observer);
}
private class SimpleViewHolder extends RecyclerView.ViewHolder {
public SimpleViewHolder(View itemView) {
super(itemView);
}
}
}
public void setLoadingListener(LoadingListener listener) {
mLoadingListener = listener;
}
public interface LoadingListener {
void onRefresh();
void onLoadMore();
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
//解决和CollapsingToolbarLayout冲突的问题
AppBarLayout appBarLayout = null;
ViewParent p = getParent();
while (p != null) {
if (p instanceof CoordinatorLayout) {
break;
}
p = p.getParent();
}
if(p instanceof CoordinatorLayout) {
CoordinatorLayout coordinatorLayout = (CoordinatorLayout)p;
final int childCount = coordinatorLayout.getChildCount();
for (int i = childCount - 1; i >= 0; i--) {
final View child = coordinatorLayout.getChildAt(i);
if(child instanceof AppBarLayout) {
appBarLayout = (AppBarLayout)child;
break;
}
}
if(appBarLayout != null) {
appBarLayout.addOnOffsetChangedListener(new AppBarStateChangeListener() {
@Override
public void onStateChanged(AppBarLayout appBarLayout, State state) {
appbarState = state;
}
});
}
}
}
public class DividerItemDecoration extends RecyclerView.ItemDecoration {
private Drawable mDivider;
private int mOrientation;
/**
* Sole constructor. Takes in a {@link Drawable} to be used as the interior
* divider.
*
* @param divider A divider {@code Drawable} to be drawn on the RecyclerView
*/
public DividerItemDecoration(Drawable divider) {
mDivider = divider;
}
/**
* Draws horizontal or vertical dividers onto the parent RecyclerView.
*
* @param canvas The {@link Canvas} onto which dividers will be drawn
* @param parent The RecyclerView onto which dividers are being added
* @param state The current RecyclerView.State of the RecyclerView
*/
@Override
public void onDraw(Canvas canvas, RecyclerView parent, RecyclerView.State state) {
if (mOrientation == LinearLayoutManager.HORIZONTAL) {
drawHorizontalDividers(canvas, parent);
} else if (mOrientation == LinearLayoutManager.VERTICAL) {
drawVerticalDividers(canvas, parent);
}
}
/**
* Determines the size and location of offsets between items in the parent
* RecyclerView.
*
* @param outRect The {@link Rect} of offsets to be added around the child
* view
* @param view The child view to be decorated with an offset
* @param parent The RecyclerView onto which dividers are being added
* @param state The current RecyclerView.State of the RecyclerView
*/
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
if (parent.getChildAdapterPosition(view) <= mWrapAdapter.getHeadersCount() + 1) {
return;
}
mOrientation = ((LinearLayoutManager) parent.getLayoutManager()).getOrientation();
if (mOrientation == LinearLayoutManager.HORIZONTAL) {
outRect.left = mDivider.getIntrinsicWidth();
} else if (mOrientation == LinearLayoutManager.VERTICAL) {
outRect.top = mDivider.getIntrinsicHeight();
}
}
/**
* Adds dividers to a RecyclerView with a LinearLayoutManager or its
* subclass oriented horizontally.
*
* @param canvas The {@link Canvas} onto which horizontal dividers will be
* drawn
* @param parent The RecyclerView onto which horizontal dividers are being
* added
*/
private void drawHorizontalDividers(Canvas canvas, RecyclerView parent) {
int parentTop = parent.getPaddingTop();
int parentBottom = parent.getHeight() - parent.getPaddingBottom();
int childCount = parent.getChildCount();
for (int i = 0; i < childCount - 1; i++) {
View child = parent.getChildAt(i);
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
int parentLeft = child.getRight() + params.rightMargin;
int parentRight = parentLeft + mDivider.getIntrinsicWidth();
mDivider.setBounds(parentLeft, parentTop, parentRight, parentBottom);
mDivider.draw(canvas);
}
}
/**
* Adds dividers to a RecyclerView with a LinearLayoutManager or its
* subclass oriented vertically.
*
* @param canvas The {@link Canvas} onto which vertical dividers will be
* drawn
* @param parent The RecyclerView onto which vertical dividers are being
* added
*/
private void drawVerticalDividers(Canvas canvas, RecyclerView parent) {
int parentLeft = parent.getPaddingLeft();
int parentRight = parent.getWidth() - parent.getPaddingRight();
int childCount = parent.getChildCount();
for (int i = 0; i < childCount - 1; i++) {
View child = parent.getChildAt(i);
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
int parentTop = child.getBottom() + params.bottomMargin;
int parentBottom = parentTop + mDivider.getIntrinsicHeight();
mDivider.setBounds(parentLeft, parentTop, parentRight, parentBottom);
mDivider.draw(canvas);
}
}
}
}
布局
<com.jcodecraeer.xrecyclerview.XRecyclerView android:id="@+id/xrv_list" android:layout_width="fill_parent" android:layout_height="fill_parent" android:scrollbarSize="0.5dp" android:scrollbars="vertical" />