RecyclerView系列之加载更多

一、背景
很久很久以前简单封装过一次RecyclerView,后来一直开发TV端,列表的需求花样不是很多,主要是解决TV端各种兼容问题,也没怎么琢磨这个美丽的控件了,现在打算重新整理一下,其实网上已经有很多优秀的开源项目了,涵盖功能多,但是就因为功能太多,用起来反而有一些不方便的地方,例如用在TV上或者别的什么地方,有的地方得根据需求和兼容问题作出修改,这样改起来就麻烦了,看的头皮发麻呀,而且很多功能用不到,用第三方库时,你们是不是也是多一个功能都不想要,所以还是自己搞一下,可以方便的扩展和修改,最终效果:
list

grid
先说下之前的,RecyclerView下拉刷新很简单,直接通过其外层包裹的SwipeRefreshLayout提供的接口即可实现,然后我就自然而然的像自定义其它控件一样把这两个控件给自定义成了一个组合控件,使用时如下:

    <com.wasu.tvfc.widget.ARecycleView
        android:id="@+id/ac_recycler"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        app:EmptyView="@layout/empty_view"
        app:layout_moreProgress="@layout/view_more_progress" />
mAdapter = new WechatAdapter(mContext, mList);
recyclerview.setLayoutManager(new LinearLayoutManager(mContext,LinearLayoutM
recyclerview.setAdapter(mAdapter);
recyclerview.setOnLoadMoreListener(new ARecycleView.OnLoadMoreListener() {
    @Override
    public void loadMore() {
        mPresenter.getMoreWechatData();
    }
});
recyclerview.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener()
    @Override
    public void onRefresh() {
        mPresenter.getWechatData();
    }
});

之前感觉这样用起来很方便了,但是随着时代的进步,祖国的发展,人民生活水平不断提高,房价也越来越贵了,想生活的简单点的我,越来越觉得之前那种写法用起来不是很舒服,因此对自己提出了几点疑问:
Q:为何要把 SwipeRefreshLayout 和 RecyclerView 封装到一起?
A:当时需求不是很多,在布局的写法也基本一样,所以这种一堆控件不如写成一个组合控件用的方便;
Q:这其实是一种惯性思维,为何要把数据为空的展示逻辑也封装到这个组合控件里了?
A:之前用 ListView 时也经常有列表为空的展示需求,现在自定义 RecyclerVIew 就干脆把这个功能也一起封装到这个组合控件里,让其功能尽量完善;
Q:这样一来其它不包含列表的页面数据为空时如果也要显示一个特殊页,就得再写一套空展示逻辑,这就和 RecyclerView 里的重复了,这不成了形而上学了,现在我想要一个纯净的写法,还要能实现上拉加载更多和下拉刷新,就像这样:

<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".activity.ListMoreActivity">
    <android.support.v4.widget.SwipeRefreshLayout
        android:id="@+id/swiperefreshlayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <android.support.v7.widget.RecyclerView
            android:id="@+id/recyclerview"
            android:layout_width="match_parent"
            android:layout_height="match_parent">

        </android.support.v7.widget.RecyclerView>
    </android.support.v4.widget.SwipeRefreshLayout>
</android.support.constraint.ConstraintLayout>

总结:
1.别的控件为了使用方便可以自定义成一个组合控件,但是 SwipeRefreshLayout 和 RecyclerView 定义到一起感觉有点不爽;
2.empty 页面直接定义到 RecyclerView 里其实不是一种完美的做法,会有很多局限,例如其它没有用到 RecyclerView 的页面如果也要在无数据时展示 empty 页面,这时又得重新搞一套逻辑,所以这种特殊页面应该单独搞一个模块,本文暂不涉及;
3.要在布局里不改变原生写法的情况下实现加载更多和下拉刷新逻辑。

二.方案
既然不能在封装 RecyclerView 和 SwipeRefreshLayout 上做文章了,那么就只好去找 Adapter 看看了,思索了许久终于想到了两个方案;(什么?你说我是看了github 上的开源项目才想到的?就你知道的多)
2.1方案一:Adapter 和布局都以原生的方式来写,然后把原生的 Adapter 装饰一把,增加上拉加载更多的逻辑,加载判断等逻辑放到一个单独工具类里;
思路如下:
1.布局及 adapter 都以原生的方式来写;
2.装饰 adapter 的功能,通过 itemType 区分使用哪种 ViewHolder,从而区分展示内容布局,加载更多布局什么的;
3.再用一个单独的工具类来处理加载更多的监听判断逻辑;
Activity的xml:

<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".activity.ListMoreActivity">
    <android.support.v4.widget.SwipeRefreshLayout
        android:id="@+id/swiperefreshlayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <android.support.v7.widget.RecyclerView
            android:id="@+id/recyclerview"
            android:layout_width="match_parent"
            android:layout_height="match_parent">

        </android.support.v7.widget.RecyclerView>
    </android.support.v4.widget.SwipeRefreshLayout>
</android.support.constraint.ConstraintLayout>

adapter:

public class ListMoreAdapter extends RecyclerView.Adapter<ListMoreAdapter.MyViewHolder> {
    private List<String> mList;

    public ListMoreAdapter(List<String> list) {
        this.mList = list;
    }

    @NonNull
    @Override
    public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        return new MyViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_list, parent, false));
    }

    @Override
    public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
        holder.tv.setText(mList.get(position));
    }

    @Override
    public int getItemCount() {
        return mList.size();
    }

    class MyViewHolder extends RecyclerView.ViewHolder {

        private TextView tv;

        private MyViewHolder(View itemView) {
            super(itemView);
            tv = itemView.findViewById(R.id.textview);
        }
    }
}

Activity里使用:

public class ListMoreActivity extends BaseActivity {


    private static Handler mHandler = new Handler();
    private ALoadMoreAdapterWrapper aLoadMoreAdapterWrapper;

    @Override
    protected void init() {
        initData();
    }

    private void initData() {
        ListMoreAdapter listMoreAdapter = new ListMoreAdapter(mDatas);
        aLoadMoreAdapterWrapper = new ALoadMoreAdapterWrapper(listMoreAdapter);//装饰Adapter
        recyclerView.setAdapter(aLoadMoreAdapterWrapper);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        recyclerView.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL));
        ARecyclerViewHelper aRecyclerViewHelper = new ARecyclerViewHelper(recyclerView, aLoadMoreAdapterWrapper);//工具类

        aRecyclerViewHelper.setOnLoadMoreListener(new ARecyclerViewHelper.OnLoadMoreListener() {
            @Override
            public void loadMore() {
                
            }
        });

        swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
            @Override
            public void onRefresh() {
                
            }
        });
    }

}

去掉 Activity 里的 14 和 18 行,就完全是没有任何杂质的原生写法了,包括布局 xml 和 Adapter,是不是很舒服 ,还有一个优点就是在 TV 或者别的什么一体机设备上万一出现问题,可以快速判断出是 RecyclerView 本身的兼容问题还是自定义 RecyclerView 里哪一段逻辑导致的问题,根据不同情况进行不同处理;
就通过这两行就可以增加上拉加载更多的功能,然后来看看这两行干了什么:
ALoadMoreAdapterWrapper.java

public class ALoadMoreAdapterWrapper extends RecyclerView.Adapter {
    private static final int ITEM_TYPE_LOADMOTE = 100001;

    private boolean enableLoadMore = true;
    private LoadMoreHolder mLoadMoreHolder;
    private RecyclerView.Adapter mAdapter;

    public ALoadMoreAdapterWrapper(RecyclerView.Adapter adapter) {
        this.mAdapter = adapter;
    }

    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        if (viewType == ITEM_TYPE_LOADMOTE) {
            if (null == mLoadMoreHolder) {
                mLoadMoreHolder = new LoadMoreHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_load_more, parent, false));
                setLoadMoreVisible(true);
            }
            return mLoadMoreHolder;
        }
        return mAdapter.onCreateViewHolder(parent, viewType);
    }

    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
        if (getItemViewType(position) != ITEM_TYPE_LOADMOTE) {
            mAdapter.onBindViewHolder(holder, position);
        }
    }

    @Override
    public int getItemCount() {
        if (!enableLoadMore) {
            return mAdapter.getItemCount();
        }
        return mAdapter.getItemCount() + 1;
    }

    @Override
    public int getItemViewType(int position) {
        if (!enableLoadMore) {
            return mAdapter.getItemViewType(position);
        }
        if (position == getItemCount() - 1) {
            return ITEM_TYPE_LOADMOTE;
        }
        return mAdapter.getItemViewType(position);
    }

    public void setLoadMoreEnable(boolean enable) {
        enableLoadMore = enable;
        setLoadMoreVisible(enable);
        notifyItemChanged(getLoadMoreViewPosition());
    }

    private int getLoadMoreViewPosition() {
        return getItemCount();
    }

    public boolean isEnableLoadMore() {
        return enableLoadMore;
    }

    private void setLoadMoreVisible(boolean flag) {
        if (null == mLoadMoreHolder) {
            return;
        }
        mLoadMoreHolder.setLoadMoreViewVisible(flag);
    }

    public void setLoadMoreState(boolean isLoadMore, boolean isFinish) {
        if (isFinish) {
            mLoadMoreHolder.setPbVisible(true);
            mLoadMoreHolder.setTv("-- 到底啦 --");
            mLoadMoreHolder.setBGColor();
            return;
        }
        if (isLoadMore) {
            mLoadMoreHolder.setTv("正在加载...");
        } else {
            mLoadMoreHolder.setTv("上拉加载更多...");
        }
    }

    class LoadMoreHolder extends RecyclerView.ViewHolder {

        private TextView tv;
        private View pb;

        LoadMoreHolder(View itemView) {
            super(itemView);
            tv = itemView.findViewById(R.id.ilm_tv);
            pb = itemView.findViewById(R.id.ilm_pb);
            itemView.setVisibility(View.GONE);
        }

        private void setTv(CharSequence txt) {
            tv.setText(txt);
        }

        private void setPbVisible(boolean isFinish) {
            pb.setVisibility(isFinish ? View.GONE : View.VISIBLE);
        }

        private void setLoadMoreViewVisible(boolean flag) {
            itemView.setVisibility(flag ? View.VISIBLE : View.GONE);
        }

        private void setBGColor() {
            itemView.setBackgroundColor(Color.parseColor("#FFF1EFF0"));
        }
    }
}

主要逻辑处理在于这几个方法:
getItemCount(),如果没开启加载更多功能就返回实际 count,否则返回实际的 count 加 1,这个1就是留着放加载更多布局的;
getItemViewType(),如果没开启加载更多功能就返回实际 adapter 里返回 type,否则最后一个 item 返回 TYPE_LOADMORE;
onCreateViewHolder(),如果 itemType 是 TYPE_LOADMORE,就创建 LoadMoreViewHolder,否则创建实际 adapter的ViewHolder;
onBindViewHolder(),如果 itemType 不是 TYPE_LOADMORE,就执行实际 adapter的onBindViewHolder。

ARecyclerViewHelper.java

public class ARecyclerViewHelper extends RecyclerView.OnScrollListener {
    private RecyclerView mRecyclerView;
    private RecyclerView.LayoutManager mLayoutManager;
    private ALoadMoreAdapterWrapper mAdapterWrapper;
    private boolean isLoading;
    private OnLoadMoreListener onLoadMoreListener;
    private boolean isFinish;
    private int lastItemCount;

    public void setOnLoadMoreListener(OnLoadMoreListener onLoadMoreListener) {
        this.onLoadMoreListener = onLoadMoreListener;
        mAdapterWrapper.setLoadMoreEnable(true);
    }

    public ARecyclerViewHelper(RecyclerView recyclerView, ALoadMoreAdapterWrapper adapterWrapper) {
        this.mRecyclerView = recyclerView;
        this.mAdapterWrapper = adapterWrapper;
        mLayoutManager = mRecyclerView.getLayoutManager();
        checkFullScreen(mRecyclerView);
        init();
    }

    private void init() {
        mRecyclerView.addOnScrollListener(this);
    }

    @Override
    public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
        super.onScrollStateChanged(recyclerView, newState);
        if (!mAdapterWrapper.isEnableLoadMore()) {
            return;
        }
        if (newState == RecyclerView.SCROLL_STATE_IDLE) {
            if (isLoading) {
                return;
            }
            if (mLayoutManager instanceof GridLayoutManager) {
                final GridLayoutManager gridLayoutManager = (GridLayoutManager) mLayoutManager;
                gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
                    @Override
                    public int getSpanSize(int position) {
                        if (position == mLayoutManager.getItemCount() - 1) {
                            return gridLayoutManager.getSpanCount();
                        } else {
                            return 1;
                        }
                    }
                });
            }
            int lastCompletelyVisibleItemPosition = 0;
            if (mLayoutManager instanceof LinearLayoutManager) {
                lastCompletelyVisibleItemPosition = ((LinearLayoutManager) mLayoutManager).findLastCompletelyVisibleItemPosition();
                lastItemCount = mLayoutManager.getItemCount();
                if (!isFinish && lastCompletelyVisibleItemPosition == lastItemCount - 2) {//回弹效果
                    int firstCompletelyVisibleItemPosition = ((LinearLayoutManager) mLayoutManager).findFirstCompletelyVisibleItemPosition();
                    View viewByPosition = mLayoutManager.findViewByPosition(lastCompletelyVisibleItemPosition);
                    if (null == viewByPosition) {
                        return;
                    }
                    int i = recyclerView.getBottom() - recyclerView.getPaddingBottom() - viewByPosition.getBottom();
                    if (i > 0 && firstCompletelyVisibleItemPosition != 0) {
                        recyclerView.smoothScrollBy(0, -i);
                    }
                } else if (!isFinish && lastCompletelyVisibleItemPosition == lastItemCount - 1) {
                    isLoading = true;
                    mAdapterWrapper.setLoadMoreState(true, false);
                    if (null != onLoadMoreListener) {
                        onLoadMoreListener.loadMore();
                    }
                }
            }
        }
    }

    /**
     * 加载完了调用此方法来关闭加载更多View或设置没有更多数据的View
     * @param isFinish
     */
    public void setLoadMoreComplete(boolean isFinish) {
        isLoading = false;
        this.isFinish = isFinish;
        if (mLayoutManager.getItemCount() == lastItemCount) {//处理加载更多时没数据增加的情况,隐藏加载更多条目
            int lastCompletelyVisibleItemPosition = ((LinearLayoutManager) mLayoutManager).findLastCompletelyVisibleItemPosition();
            if (lastCompletelyVisibleItemPosition < lastItemCount - 2) {
                return;
            }
            int firstCompletelyVisibleItemPosition = ((LinearLayoutManager) mLayoutManager).findFirstCompletelyVisibleItemPosition();
            View viewByPosition = mLayoutManager.findViewByPosition(lastItemCount - 2);
            int i = mRecyclerView.getBottom() - mRecyclerView.getPaddingBottom() - viewByPosition.getBottom();
            if (i > 0 && firstCompletelyVisibleItemPosition != 0) {
                mRecyclerView.smoothScrollBy(0, -i);
            }
        }
        mAdapterWrapper.setLoadMoreState(false, isFinish);
    }

    /**
     * 检测数据是否满屏
     * @param recyclerView
     */
    private void checkFullScreen(RecyclerView recyclerView) {
        mAdapterWrapper.setLoadMoreEnable(false);
        if (recyclerView == null) return;
        final RecyclerView.LayoutManager manager = recyclerView.getLayoutManager();
        if (manager == null) return;
        recyclerView.postDelayed(new Runnable() {
            @Override
            public void run() {
                if (manager instanceof LinearLayoutManager) {
                    LinearLayoutManager linearLayoutManager = (LinearLayoutManager) manager;
                    mAdapterWrapper.setLoadMoreEnable(isFullScreen(linearLayoutManager));
                } else if (manager instanceof StaggeredGridLayoutManager) {
                    StaggeredGridLayoutManager staggeredGridLayoutManager = (StaggeredGridLayoutManager) manager;
                    int[] positions = new int[staggeredGridLayoutManager.getSpanCount()];
                    staggeredGridLayoutManager.findLastCompletelyVisibleItemPositions(positions);
                    int pos = getTheBiggestNumber(positions) + 1;
                    mAdapterWrapper.setLoadMoreEnable(pos != mAdapterWrapper.getItemCount());
                }
            }
        }, 50);
    }

    private boolean isFullScreen(LinearLayoutManager layoutManager) {
        return (layoutManager.findLastCompletelyVisibleItemPosition() + 1) != mAdapterWrapper.getItemCount() ||
                layoutManager.findFirstCompletelyVisibleItemPosition() != 0;
    }

    private int getTheBiggestNumber(int[] numbers) {
        int tmp = -1;
        if (numbers == null || numbers.length == 0) {
            return tmp;
        }
        for (int num : numbers) {
            if (num > tmp) {
                tmp = num;
            }
        }
        return tmp;
    }

    public interface OnLoadMoreListener {
        void loadMore();
    }
}

这个类的逻辑也不多,思路如下:
1.检测一下数据是否满屏了,如果没满就禁止加载更多,这个逻辑如果不处理,会出现只有一条数据时也显示加载更多 item 的情况,那就尴尬了;
2.设置 RecyclerView 的滑动监听,在 onScrollStateChanged() 方法里处理是否要加载更多的逻辑;
3.加载更多数据完成后,调用 setLoadMoreComplete() 方法处理 View 状态;
至此就已经实现了加载更多功能了。
list1

有句话不知当讲不当讲,说了半天,其实我不喜欢这种方式,所以加载更多出错、点击重新加载什么的功能我也没在方案一代码里加。(什么?二狗子说要顺着网线来打我?且慢,让我说完方案二再来不迟,我还有更好的方案。)

2.2方案二:把控制上拉加载的逻辑放到一个 BaseAdapter 中,实际的 adapter 直接继承这个 BaseAdapter 就拥有了上拉加载功能;

public abstract class BaseAdapter<T> extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    ...

    public void setOnLoadMoreListener(OnLoadMoreListener onLoadMoreListener) {
        this.onLoadMoreListener = onLoadMoreListener;
        isOpenLoadMore = true;
    }

    public BaseAdapter(List<T> mDatas) {
        this.mDatas = mDatas;
    }

    protected abstract int getLayoutId();

    protected abstract void convert(BaseViewHolder viewHolder, T item);

    @NonNull
    @Override
    public BaseViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        BaseViewHolder baseViewHolder = null;
        switch (viewType) {
            case TYPE_CONTENT_VIEW:
                baseViewHolder = BaseViewHolder.create(getLayoutId(), parent);
                break;
            case TYPE_FOOTER_VIEW:
                if (mFooterLayout == null) {
                    mFooterLayout = new RelativeLayout(parent.getContext());
                }
                baseViewHolder = BaseViewHolder.create(mFooterLayout);
                break;
        }
        return baseViewHolder;
    }

    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
        switch (holder.getItemViewType()) {
            case TYPE_CONTENT_VIEW:
                BaseViewHolder viewHolder = (BaseViewHolder) holder;
                convert(viewHolder, mDatas.get(position));
                break;
        }
    }

    @Override
    public int getItemCount() {
        return mDatas.size() + getFooterViewCount();
    }

    @Override
    public int getItemViewType(int position) {
        if (isFooterView(position)) {
            return TYPE_FOOTER_VIEW;
        }
        return TYPE_CONTENT_VIEW;
    }
    
    protected int getFooterViewCount() {
        return isOpenLoadMore && !mDatas.isEmpty() ? 1 : 0;
    }
    
    ...
}

这是BaseAdapter里处理上拉加载的主要方法,也是通过 itemType 来区分加载什么 item,通过是否开启加载更多的 flag 来控制 itemCount,里面多处理了一个 ViewHolder 的逻辑,搞了一个 BaseViewHolder:

public class BaseViewHolder extends RecyclerView.ViewHolder {
    private final SparseArray<View> views;

    public BaseViewHolder(View itemView) {
        super(itemView);
        this.views = new SparseArray<>();
    }

    public static BaseViewHolder create(int layoutId, ViewGroup parent) {
        View itemView = LayoutInflater.from(parent.getContext()).inflate(layoutId, parent, false);
        return new BaseViewHolder(itemView);
    }

    public static BaseViewHolder create(View itemView) {
        return new BaseViewHolder(itemView);
    }

    @SuppressWarnings("unchecked")
    public <T extends View> T getView(@IdRes int viewId) {
        View view = views.get(viewId);
        if (view == null) {
            view = itemView.findViewById(viewId);
            views.put(viewId, view);
        }
        return (T) view;
    }

    public BaseViewHolder setText(@IdRes int viewId, CharSequence value) {
        TextView view = getView(viewId);
        view.setText(value);
        return this;
    }

    public BaseViewHolder setText(@IdRes int viewId, @StringRes int strId) {
        TextView view = getView(viewId);
        view.setText(strId);
        return this;
    }
}

示例代码只加了个 setText() 方法,增加其它方法和此方法基本相同,例如 setImg() 什么的。
实际使用:
ListMore2Adapter.java

public class ListMore2Adapter extends BaseAdapter<String> {
    public ListMore2Adapter(List<String> mDatas) {
        super(mDatas);
    }

    @Override
    protected int getLayoutId() {
        return R.layout.item_list;
    }

    @Override
    protected void convert(BaseViewHolder viewHolder, String item) {
        viewHolder.setText(R.id.textview, item);
    }
}

ListMore2Activity.java

private void initData() {
        listMoreAdapter = new ListMore2Adapter(mDatas);
        listMoreAdapter.setOnLoadMoreListener(new OnLoadMoreListener() {
            @Override
            public void onLoadMore(boolean isReload) {
                loadMore();
            }
        });
        //要在setAdapter之前设置OnLoadMoreListener,因为要通过设置Listener来标记加载更多功能开启,如果不这样做,就得在Adapter的构造函数里把标记传过去
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        recyclerView.setAdapter(listMoreAdapter);
        recyclerView.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL));
        swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
            @Override
            public void onRefresh() {
                
            }
        });
    }

这个方案就是文章最开始展示的效果了,其中有两点要注意的地方,GridView布局时,要自己写个ItemDecoration,需要对加载更多的item处理一下,否则会出现下面的情况
在这里插入图片描述
加载更多的layout右边被加上了空隙。

/**
 * Created by Aislli on 2018/9/10 0010.
 */
public class SpaceDecoration extends RecyclerView.ItemDecoration {
    private int space;

    public SpaceDecoration(int space) {
        this.space = space;
    }

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        super.getItemOffsets(outRect, view, parent, state);

        RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
        int spanCount;
        int orientation;
        int itemCount = layoutManager.getItemCount();
        boolean isLoadMore;
        if (layoutManager instanceof GridLayoutManager) {
            GridLayoutManager gridLayoutManager = (GridLayoutManager) layoutManager;
            spanCount = gridLayoutManager.getSpanCount();
            orientation = gridLayoutManager.getOrientation();

            GridLayoutManager.LayoutParams lp = (GridLayoutManager.LayoutParams) view.getLayoutParams();
            isLoadMore = lp.getSpanSize() == spanCount;
        } else {
            StaggeredGridLayoutManager staggeredGridLayoutManager = (StaggeredGridLayoutManager) layoutManager;
            spanCount = staggeredGridLayoutManager.getSpanCount();
            orientation = staggeredGridLayoutManager.getOrientation();

            StaggeredGridLayoutManager.LayoutParams lp = (StaggeredGridLayoutManager.LayoutParams) view.getLayoutParams();
            isLoadMore = lp.isFullSpan();
        }
        int childAdapterPosition = parent.getChildAdapterPosition(view);

        if (orientation == GridLayoutManager.VERTICAL) {
            outRect.top = childAdapterPosition < spanCount ? 0 : space;// the first row
            outRect.bottom = 0;
            outRect.left = 0;
            if (childAdapterPosition % spanCount < (spanCount - 1)) {// except the last column
                outRect.right = space;
            }
            if (childAdapterPosition == itemCount - 1 && isLoadMore) {// load more view
                outRect.right = 0;
            }
        }
    }
}

第 44 行这里处理一下最后一个 item 的 right 值就 OK 了。
第二个需要注意的是,如果一行有 4 个 item,结果产品要求上拉加载时每页数据少于 4 条,例如要求一页加载 3 条数据,这样会出现一个问题
在这里插入图片描述
在这里插入图片描述
当加载后数据正好充满一行时,这个加载更多的 View 不会触发 onScrollStateChanged() 方法,那么就不会执行弹回判断操作,从而导致加载更多 View 回不去了,不过理论上不会有这样的需求的,一行 4 条数据,加载更多时一页最少也要 4 条数据才有意义,所以正常情况下是不会出现这种情况的。但是如果真的有这种需求也没关系,加个判断处理一下就是

    /**
     * 重置成正常状态
     */
    private void stateDefault() {
        currentState = STATE_DEFAULT;
        mLoadingViewTv.setText("上拉加载更多");
        mLoadingViewPb.setVisibility(View.GONE);
//        mRecyclerView.postDelayed(new Runnable() {
//            @Override
//            public void run() {
//                hiddenFooterView();
//            }
//        }, 200);
    }

    private void hiddenFooterView() {
        RecyclerView.LayoutManager layoutManager = mRecyclerView.getLayoutManager();
        int lastVisibleItemPosition = findLastVisibleItemPosition(layoutManager);
        if (lastVisibleItemPosition >= getItemCount() - 2 && currentState != STATE_END) {
            if (findFirstVisibleItemPosition(layoutManager) == 0) return;
            View viewByPosition = layoutManager.findViewByPosition(getItemCount() - 2);
            if (null == viewByPosition) return;
            int i = mRecyclerView.getBottom() - mRecyclerView.getPaddingBottom() - viewByPosition.getBottom();
            if (i > 0) {
                mRecyclerView.smoothScrollBy(0, -i);
            }
        }
    }

正常加载完都会触发 stateDefault() 方法重置状态,如果有上面所说需求,就把上面的注释放开,在重置完状态后执行 hiddenFooterView() 就 OK 了,没这种需求就不用加这个检测了,一般不会出现这种情况的。
后面的同学突然大吼一声:嘿!!同学,你仄条嗦的有问题吧,“一行 4 条数据,加载更多时一页最少也要 4 条数据才有意义”,什么叫赠藏情况下不会粗现仄种情况,如果最后一页就剩下 2 条数据怎么办,每次都可能粗现呀!你嗦怎么办?
真是的…吼的我心里一凉,还以为大清要亡了,允许加载更多的接口数据中,一般都会有个页数和总数,到了最后一页时就会调用 adapter.loadEnd() 方法,调完之后下面的加载更多View就变成类似“–到底了–”的 endView,完全是正常流程,无任何问题嘛。
我自己在项目中用的是方案二的方式,有兴趣的可看完整代码。
源码

  • 4
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值