簡述
該增強型RecyclerView鸯绿,增加了以下特性:
- 上拉滑動到底部,加載更多
- 支持添加Header頭視圖
- 支持加載數據為空時,顯示特定視圖
- 支持拖拽漠嵌,側滑刪除
下拉刷新實現通過給RecyclerView包一層SwipRefreshLayout來實現敬矩。
本文重點分享上拉加載更多的實現概行,同時實現添加頭部視圖,側滑弧岳,拖拽功能實現占锯,該實現存在以下注意點:
- 如何判斷RecyclerView滑動到了底部
- 通常RecyclerView顯示的item布局相同,怎么做到上拉加載更多時出現一個底欄視圖
- 滑動到底欄出現加載更多動畫缩筛,這個動畫什么時候結束消略?動畫生命周期是?
- ReclcerViewyou多種布局瞎抛,如果是網格布局(有多列)艺演,怎么做到讓上拉加載更多的動畫視圖和頭部視圖占用一整行?
- 自定義了RecyclerView桐臊,如何做到像使用標準RecyclerView那樣使用胎撤?
- 如何實現item的拖拽和側滑刪除?
實現的注意點解析
如何判斷RecyclerView滑動到了底部
關于布局的邏輯設置断凶,就找LayoutManager伤提。的確,通過查閱官方API手冊认烁,有findLastVisibleItemPosition()/findLastCompletelyVisibleItePosition()肿男,在滑動監(jiān)聽里,使用這兩個方法就能實現判斷
怎么做到上拉加載更多時出現一個底欄視圖
這里涉及到RecyclerView如何實現多布局顯示的知識却嗡,通過getItemViewType()舶沛,底部上拉加載更多視圖設定一種ItemViewType值,對應新建一個ViewHolder.
滑動到底欄出現加載更多動畫窗价,這個動畫什么時候結束如庭?動畫生命周期是?
動畫的開始時刻是列表滑倒底部撼港,當滑倒底部時坪它,在客戶類(Fragment/Activity)里接口回調骤竹,開始網絡請求數據,動畫的結束時刻是網絡加載完成往毡,刷新列表時
ReclcerViewyou多種布局瘤载,如果是網格布局(有多列),怎么做到讓上拉加載更多的動畫視圖和頭部視圖占用一整行卖擅?
和布局相關的鸣奔,找LayoutManager,這里要找GridLayoutManager惩阶,它提供了setSpanSizeLookup(GridLayoutManager.SpanSizeLookup)挎狸,通過這個方法,可以根據位置來設置item占用一整行還是正常顯示
自定義了RecyclerView断楷,如何做到像使用標準RecyclerView那樣使用锨匆?
使用裝飾器設計模式,能很好的實現對用戶透明使用效果
如何實現item的拖拽和側滑刪除冬筒?
使用android提供的ItemTouchHelper工具類恐锣,能快速的實現
核心代碼
EnhanceRecyclerView
public class EnhanceRecyclerView extends RecyclerView {
private static final String TAG = "EnhanceRecyclerView";
private OnLoadMoreListener mOnLoadMoreListener;
private InternalAdapter mInternalAdapter;
private View mEmptyView;
private @LayoutRes int mHeaderResId;
private AdapterDataObserver mAdapterDataObserver = new EnhanceAdapterDataObserver();
/**
* 滾動方向
*/
private int mScrollDy = 0;
public EnhanceRecyclerView(Context context) {
super(context);
}
public EnhanceRecyclerView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public EnhanceRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
public void onScrolled(int dx, int dy) {
super.onScrolled(dx, dy);
mScrollDy = dy;
}
@Override
public void onScrollStateChanged(int state) {
super.onScrollStateChanged(state);
switch (state) {
case SCROLL_STATE_IDLE:
LayoutManager layoutManager = getLayoutManager();
int itemCount = getAdapter().getItemCount();
int lastVisibleItemPosition = 0;
if (layoutManager instanceof GridLayoutManager) {
GridLayoutManager gridLayoutManager = (GridLayoutManager) layoutManager;
lastVisibleItemPosition = gridLayoutManager.findLastVisibleItemPosition();
} else if (layoutManager instanceof LinearLayoutManager) {
LinearLayoutManager linearLayoutManager = (LinearLayoutManager) layoutManager;
lastVisibleItemPosition = linearLayoutManager.findLastVisibleItemPosition();
}
if (lastVisibleItemPosition >= itemCount - 1) {
if (getParent() instanceof SwipeRefreshLayout) {
SwipeRefreshLayout swipeRefreshLayout = (SwipeRefreshLayout) getParent();
if (swipeRefreshLayout.isRefreshing()) {
break;
}
}
if (mOnLoadMoreListener != null && mScrollDy > 0) {
mInternalAdapter.setLoadingIndicatorViewVisible(VISIBLE);
mOnLoadMoreListener.onLoadMore();
}
}
break;
}
}
/**
* 重寫此方法,設置GridLayout的上拉加載更多視圖的位置
*
* @param layout
*/
@Override
public void setLayoutManager(LayoutManager layout) {
if (layout instanceof GridLayoutManager) {
final GridLayoutManager externalGridLayoutManager = (GridLayoutManager) layout;
final int spanCount = externalGridLayoutManager.getSpanCount();
int orientation = externalGridLayoutManager.getOrientation();
final GridLayoutManager innerGridLayoutManager = new GridLayoutManager(getContext(), spanCount, orientation, false);
innerGridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
int headerViewCount = mInternalAdapter.getHeaderViewCount();
int footViewCount = mInternalAdapter.getFootViewCount();
if (position < headerViewCount) {
return spanCount;
}
int totalItemCount = innerGridLayoutManager.getItemCount();
if (position >= totalItemCount - footViewCount) {
return spanCount;
}
return externalGridLayoutManager.getSpanSizeLookup().getSpanSize(position - headerViewCount);
}
});
super.setLayoutManager(innerGridLayoutManager);
} else {
super.setLayoutManager(layout);
}
}
public View getEmptyView() {
return mEmptyView;
}
public final void setEmptyView(View emptyView) {
mEmptyView = emptyView;
setupEmptyViewHierarchy(emptyView);
}
protected void setupEmptyViewHierarchy(View emptyView) {
((ViewGroup) getParent().getParent()).addView(emptyView,0);
}
public void addHeaderResId(@LayoutRes int resId) {
mHeaderResId = resId;
if (mInternalAdapter != null) {
mInternalAdapter.setExternalHeaderResId(resId);
}
}
@Override
public void setAdapter(Adapter adapter) {
mInternalAdapter = new InternalAdapter(adapter);
super.setAdapter(mInternalAdapter);
//addHeaderView方法依賴于setAdapter方法
if (mHeaderResId > 0) {
addHeaderResId(mHeaderResId);
}
mInternalAdapter.registerAdapterDataObserver(mAdapterDataObserver);
mAdapterDataObserver.onChanged();
}
public void setOnLoadMoreListener(OnLoadMoreListener onLoadMoreListener) {
mOnLoadMoreListener = onLoadMoreListener;
}
public void loadMoreOnSuccess() {
if (mInternalAdapter != null) {
mInternalAdapter.loadMoreOnSuccess();
}
}
public void loadMoreOnError() {
if (mInternalAdapter != null) {
mInternalAdapter.loadMoreOnError();
}
}
public void loadMoreOnComplete() {
if (mInternalAdapter != null) {
mInternalAdapter.loadMoreOnComplete();
}
}
public final void notifyDataSetChanged() {
mInternalAdapter.notifyDataSetChanged();
}
public final void notifyItemChanged(int position) {
mInternalAdapter.notifyItemChanged(position);
}
public final void notifyItemChanged(int position, Object payload) {
position = position + mInternalAdapter.getHeaderViewCount();
mInternalAdapter.notifyItemChanged(position, payload);
}
public final void notifyItemRangeChanged(int positionStart, int itemCount) {
positionStart = positionStart + mInternalAdapter.getHeaderViewCount();
mInternalAdapter.notifyItemRangeChanged(positionStart, itemCount);
}
public final void notifyItemRangeChanged(int positionStart, int itemCount, Object payload) {
positionStart = positionStart + mInternalAdapter.getHeaderViewCount();
mInternalAdapter.notifyItemRangeChanged(positionStart, itemCount, payload);
}
public final void notifyItemInserted(int position) {
position = position + mInternalAdapter.getHeaderViewCount();
mInternalAdapter.notifyItemInserted(position);
}
public final void notifyItemMoved(int fromPosition, int toPosition) {
fromPosition = fromPosition + mInternalAdapter.getHeaderViewCount();
toPosition = toPosition + mInternalAdapter.getHeaderViewCount();
mInternalAdapter.notifyItemMoved(fromPosition, toPosition);
}
public final void notifyItemRangeInserted(int positionStart, int itemCount) {
positionStart = positionStart + mInternalAdapter.getHeaderViewCount();
mInternalAdapter.notifyItemRangeInserted(positionStart, itemCount);
}
public final void notifyItemRemoved(int position) {
position = position + mInternalAdapter.getHeaderViewCount();
mInternalAdapter.notifyItemRemoved(position);
}
public final void notifyItemRangeRemoved(int positionStart, int itemCount) {
positionStart = positionStart + mInternalAdapter.getHeaderViewCount();
mInternalAdapter.notifyItemRangeRemoved(positionStart, itemCount);
}
public InternalAdapter getInternalAdapter() {
return mInternalAdapter;
}
/**
* 上拉加載更多回調
*/
public interface OnLoadMoreListener {
void onLoadMore();
}
private class EnhanceAdapterDataObserver extends AdapterDataObserver {
@Override
public void onChanged() {
super.onChanged();
if (getEmptyView() != null && getAdapter() != null) {
int itemCount = getAdapter().getItemCount();
if (itemCount == 0) {
getEmptyView().setVisibility(VISIBLE);
setVisibility(GONE);
} else {
getEmptyView().setVisibility(GONE);
setVisibility(VISIBLE);
}
}
}
@Override
public void onItemRangeChanged(int positionStart, int itemCount) {
super.onItemRangeChanged(positionStart, itemCount);
onChanged();
}
@Override
public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
super.onItemRangeChanged(positionStart, itemCount, payload);
onChanged();
}
@Override
public void onItemRangeInserted(int positionStart, int itemCount) {
super.onItemRangeInserted(positionStart, itemCount);
onChanged();
}
@Override
public void onItemRangeRemoved(int positionStart, int itemCount) {
super.onItemRangeRemoved(positionStart, itemCount);
onChanged();
}
@Override
public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
super.onItemRangeMoved(fromPosition, toPosition, itemCount);
onChanged();
}
}
}
InternalAdapter
public class InternalAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final String TAG = "InternalAdapter";
private static final int HEADER_ITEM_TYPE = 170118;
private static final int FOOTER_ITEM_TYPE = 170116;
private RecyclerView.Adapter<RecyclerView.ViewHolder> mExternalAdapter;
private int mBodyItemCount;
private FooterView mFooterView;
private @LayoutRes int mExternalHeaderResId;
public InternalAdapter(RecyclerView.Adapter<RecyclerView.ViewHolder> externalAdapter) {
mExternalAdapter = externalAdapter;
mBodyItemCount = externalAdapter.getItemCount();
}
@Override
public int getItemViewType(int position) {
if(isHeaderView(position)){
return HEADER_ITEM_TYPE;
}
else if (isFootView(position)) {
return FOOTER_ITEM_TYPE;
}
return mExternalAdapter.getItemViewType(position);
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
switch (viewType) {
case HEADER_ITEM_TYPE:
View headerView = LayoutInflater.from(parent.getContext()).inflate(mExternalHeaderResId, parent, false);
return new HeaderView(headerView);
case FOOTER_ITEM_TYPE:
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_footer_indicator, parent, false);
mFooterView = new FooterView(view);
return mFooterView;
default:
return mExternalAdapter.onCreateViewHolder(parent, viewType);
}
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if(isHeaderView(position)){
return;
}
if (isFootView(position)) {
return;
}
if(mExternalHeaderResId > 0){
position = position - getHeaderViewCount();
}
mExternalAdapter.onBindViewHolder(holder, position);
}
@Override
public int getItemCount() {
mBodyItemCount = mExternalAdapter.getItemCount();
if(mBodyItemCount == 0){
return 0;
}
else{
return getHeaderViewCount() + mBodyItemCount + getFootViewCount();
}
}
private boolean isHeaderView(int position){
return mExternalHeaderResId > 0 && position == 0;
}
private boolean isFootView(int position) {
return (position >= mBodyItemCount + getHeaderViewCount());
}
public int getFootViewCount() {
return 1;
}
public int getHeaderViewCount(){
return mExternalHeaderResId > 0 ? 1 : 0;
}
public void setLoadingIndicatorViewVisible(int visible){
if(mFooterView != null){
mFooterView.setLoadingIndicatorViewVisible(visible);
}
}
public void setExternalHeaderResId(int externalHeaderResId) {
mExternalHeaderResId = externalHeaderResId;
}
public void loadMoreOnSuccess(){
setLoadingIndicatorViewVisible(View.GONE);
}
public void loadMoreOnError(){
setLoadingIndicatorViewVisible(View.GONE);
}
public void loadMoreOnComplete(){
setLoadingIndicatorViewVisible(View.GONE);
}
static class HeaderView extends RecyclerView.ViewHolder{
HeaderView(View itemView) {
super(itemView);
}
}
static class FooterView extends RecyclerView.ViewHolder {
@Bind(R.id.item_footer_indicator)
LoadingIndicatorView mLoadingIndicatorView;
FooterView(View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);
mLoadingIndicatorView.setVisibility(View.GONE);
}
void setLoadingIndicatorViewVisible(int visible){
mLoadingIndicatorView.setVisibility(visible);
}
}
}
底部FooterView的布局item_footer_indicator.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="48dp"
android:background="@android:color/transparent"
>
<com.sugary.refreshrecyclerview.enhancerecycler.indicator.LoadingIndicatorView
android:id="@+id/item_footer_indicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
app:indicator_color="@color/indicator_loading_more_orange"
/>
</RelativeLayout>
LoadingIndicatorView
public class LoadingIndicatorView extends View {
//Sizes (with defaults in DP)
public static final int DEFAULT_SIZE = 50;
private Paint mPaint;
private BaseIndicatorController mIndicatorController;
private boolean mHasAnimation;
public LoadingIndicatorView(Context context) {
this(context, null);
}
public LoadingIndicatorView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public LoadingIndicatorView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(attrs);
}
private void init(AttributeSet attrs) {
TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.LoadingIndicatorView);
int indicatorColor = a.getColor(R.styleable.LoadingIndicatorView_indicator_color, Color.GRAY);
a.recycle();
mPaint = new Paint();
mPaint.setColor(indicatorColor);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setAntiAlias(true);
mIndicatorController = new BallPulseIndicator();
mIndicatorController.setTarget(this);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = measureDimension(dp2px(DEFAULT_SIZE), widthMeasureSpec);
int height = measureDimension(dp2px(DEFAULT_SIZE), heightMeasureSpec);
setMeasuredDimension(width, height);
}
private int measureDimension(int defaultSize, int measureSpec) {
int result;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if (specMode == MeasureSpec.EXACTLY) {
result = specSize;
} else if (specMode == MeasureSpec.AT_MOST) {
result = Math.min(defaultSize, specSize);
} else {
result = defaultSize;
}
return result;
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (!mHasAnimation) {
mHasAnimation = true;
mIndicatorController.initAnimation();
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawIndicator(canvas);
}
void drawIndicator(Canvas canvas) {
mIndicatorController.draw(canvas, mPaint);
}
private int dp2px(int dpValue) {
return (int) getContext().getResources().getDisplayMetrics().density * dpValue;
}
@Override
public void setVisibility(int v) {
if (getVisibility() != v) {
super.setVisibility(v);
if (v == GONE || v == INVISIBLE) {
mIndicatorController.setAnimationStatus(BaseIndicatorController.AnimStatus.END);
} else {
mIndicatorController.setAnimationStatus(BaseIndicatorController.AnimStatus.START);
}
}
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
if (mHasAnimation) {
mIndicatorController.setAnimationStatus(BaseIndicatorController.AnimStatus.START);
}
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
mIndicatorController.setAnimationStatus(BaseIndicatorController.AnimStatus.CANCEL);
}
}
小結:
列表數據刷新舞痰,改成了調用EnhanceRecylerView方法土榴,用自己建的Adapter刷新數據無效(這是這個輪子的缺陷,有待改進)响牛。
底部滑動動畫使用了他人的開源動畫
在自制增強型RecyclerView過程中玷禽,也刷了一些資料,推薦閱讀呀打。
參考資料
RecyclerView必知必會(五星推薦)