自定义上拉加载更多-增强型RecyclerView的实现

简书同步更新:传送门

简述

该增强型RecyclerView,增加了以下特性:

  • 上拉滑动到底部,加载更多
  • 支持添加Header头视图
  • 支持加载数据为空时,显示特定视图
  • 支持拖拽,侧滑删除

下拉刷新实现通过给RecyclerView包一层SwipRefreshLayout来实现。

本文重点分享上拉加载更多的实现,同时实现添加头部视图,侧滑,拖拽功能实现,该实现存在以下注意点:

  1. 如何判断RecyclerView滑动到了底部
  2. 通常RecyclerView显示的item布局相同,怎么做到上拉加载更多时出现一个底栏视图
  3. 滑动到底栏出现加载更多动画,这个动画什么时候结束?动画生命周期是?
  4. ReclcerViewyou多种布局,如果是网格布局(有多列),怎么做到让上拉加载更多的动画视图和头部视图占用一整行?
  5. 自定义了RecyclerView,如何做到像使用标准RecyclerView那样使用?
  6. 如何实现item的拖拽和侧滑删除?

实现的注意点解析

如何判断RecyclerView滑动到了底部

关于布局的逻辑设置,就找LayoutManager。的确,通过查阅官方API手册,有findLastVisibleItemPosition()/findLastCompletelyVisibleItePosition(),在滑动监听里,使用这两个方法就能实现判断

怎么做到上拉加载更多时出现一个底栏视图

这里涉及到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必知必会(五星推荐)

Github:RecyclerView优秀文集

Github:BeautifulRefreshLayout

Github:BaseRecyclerViewAdapterHelper

Github:XRecyclerView

猜你喜欢

转载自blog.csdn.net/sugaryaruan/article/details/54695114