SwipeRefreshLayout和RecyclerView实现下拉刷新和加载更多(当总数据不够一页,也进行了处理)

ε祈祈猫儿з 提交于 2019-12-02 13:57:59

前言:利用SwipeRefreshLayout和RecyclerView来实现下拉刷新和加载更多,有很多的例子,但普遍都存在一个问题,当总的数据不够一屏时,FooterView也显示了,如果直接隐藏FooterView,加载更多时FooterView又不显示了,捣鼓了一阵子后,总算完美的解决了,所以记录一下,同时为了方便使用,进行了一些简单的封装。

直接进入主题,关于SwipeRefreshLayout和RecyclerView的一些基本介绍,就不在这里累述,不了解的童鞋,上网查一下吧!

一、封装自己的RefreshLayout控件

为什么要封装?为了使用方便;更为了Activity简单(MVP模式);因为Adapter的数据源类型不确定,所以采用泛型来实现。

java类和xml布局如下图



仔细的童鞋,已经发现了java类继承的是LinearLayout(xml根节点),为什么不用直接继承SwipeRefreshLayout(xml根节点)呢?其实一开始也是直接已SwipeRefreshLayout为根节点的,直接以SwipeRefreshLayout为根节点,运行后发现下拉刷新的颜色无法修改(swipeRefreshLayout.setColorSchemeColors(int color)无效),而且SwipeRefreshLayout.setRefreshing(false)方法也无效,导致无法释放下拉刷新,界面卡住。

要实现下拉刷新和加载更多,需要SwipeRefreshLayout监听OnRefreshListener和RecyclerView监听OnScrollListener,下拉刷新比较简单,如图


难点在于RecyclerView监听OnScrollListener,重写onScrollStateChanged和onScrolled方法,方法具体实现如下图



代码简单,逻辑也不复杂,图一主要实现“加载更多”,根据判断RecyclerView是否滑倒底部了;图二主要实现“不足一屏数据”的显示逻辑,数据少于一屏时,修改状态值为“初始化”,当状态为“到底”时,不进行操作,一共设计了4个状态(1-初始状态,全隐藏;2-正在加载,3-加载完成,4-加载到底,指没有更多数据了),后文具体交代;

因为把Adapter也封装到了RefreshLayout中,所以需要对外提供刷新和加载更多的方法,如图


到此处RefreshLayout.java就差不结束了,自定义控件相对来说还是比较简单;

二、封装Adapter(实现FooterView)

Adapter部分,其实主要就是给RecyclerView添加一个FooterView,首先定义一个泛型的BaseAbsAdapter抽象类(比较简单,也是为了项目方便使用),代码如下

public abstract class BaseAbsAdapter<T> extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    protected List<T> mList;
    protected Context mContext;

    public BaseAbsAdapter(Context context) {
        mContext = context;
        mList = new ArrayList<>();
    }

    /**
     * 下拉刷新
     * @param list 列表
     */
    public abstract void onRefresh(List<T> list);

    /**
     * 加载更多
     * @param list 列表
     */
    public abstract void onLoadMore(List<T> list);

    /**
     * 创建View
     * @param parent View
     * @param layout XML布局
     * @return View
     */
    protected View inflate(ViewGroup parent, int layout) {
        return LayoutInflater.from(mContext).inflate(layout, parent, false);
    }
}

然后封装实现FooterView的WrapperAdapter类(这里何网络上的大部分实现功能都差不多,就不多累述,这里出现了上面说的4中状态,目前实现比较简单,就是对FooterView的操作,直接上代码)

class WrapperAdapter<T> extends BaseAbsAdapter<T> {
    private BaseAbsAdapter<T>  mAdapter;// 适配器
    private int                mState = KeyUtils.LOAD_INIT;// 状态

    WrapperAdapter(Context context, BaseAbsAdapter<T> adapter) {
        super(context);
        mAdapter = adapter;
    }

    /**
     * 设置状态
     * @param state 状态
     */
    void onState(int state) {
        if (state == mState)
            return;
        mState = state;
        notifyDataSetChanged();
    }

    @Override
    public void onRefresh(List<T> list) {
        mAdapter.onRefresh(list);
        notifyDataSetChanged();
    }

    @Override
    public void onLoadMore(List<T> list) {
        mAdapter.onLoadMore(list);
        notifyDataSetChanged();
    }

    /**
     * 获取当前状态
     * @return 状态(初始、正在、完成、到底)
     */
    int state() {
        return mState;
    }

    @Override
    public int getItemViewType(int position) {
        return position + 1 == getItemCount() ? KeyUtils.WRAPPER_FOOT : KeyUtils.WRAPPER_ITEM;
    }

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        if (KeyUtils.WRAPPER_ITEM == viewType)// FooterHolder
            return mAdapter.onCreateViewHolder(parent, viewType);
        return new FooterHolder(inflate(parent, R.layout.view_um_footer));
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        if (KeyUtils.WRAPPER_FOOT != getItemViewType(position)) {// Footer
            mAdapter.onBindViewHolder(holder, position);
            return;
        }
        ((WrapperAdapter.FooterHolder) holder).showView(mState);
    }

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

    /**
     * FooterHolder
     */
    private class FooterHolder extends ViewHolder {
        private LinearLayout[] mLayouts;

        private FooterHolder(View view) {
            super(view);
            mLayouts = new LinearLayout[] {
                    view.findViewById(R.id.ll_wrapper_load),
                    view.findViewById(R.id.ll_wrapper_end)
            };
        }

        /**
         * 显示视图
         * @param state 状态
         */
        private void showView(int state) {
            switch (state) {
                case KeyUtils.LOAD_INIT:// 初始状态
                    mLayouts[0].setVisibility(View.GONE);
                    mLayouts[1].setVisibility(View.GONE);
                    break;
                case KeyUtils.LOAD_ING:// 正在加载
                    mLayouts[0].setVisibility(View.VISIBLE);
                    mLayouts[1].setVisibility(View.GONE);
                    break;
                case KeyUtils.LOAD_OVER:// 加载完成
                    mLayouts[0].setVisibility(View.VISIBLE);
                    mLayouts[1].setVisibility(View.GONE);
                    break;
                case KeyUtils.LOAD_END:// 加载到底
                    mLayouts[0].setVisibility(View.GONE);
                    mLayouts[1].setVisibility(View.VISIBLE);
                    break;
            }
        }
    }

    @Override
    public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
        super.onAttachedToRecyclerView(recyclerView);
        final LayoutManager lm = recyclerView.getLayoutManager();
        if (lm == null || !(lm instanceof GridLayoutManager))
            return;
        final GridLayoutManager glm = (GridLayoutManager) lm;
        glm.setSpanSizeLookup(new SpanSizeLookup() {
            @Override
            public int getSpanSize(int position) {
                // GridView时,Footer占据的是整个横排,其他Item只占据1个单元格
                return getItemViewType(position) == KeyUtils.WRAPPER_FOOT ? glm.getSpanCount() : 1;
            }
        });
    }
}

<?xml version="1.0" encoding="utf-8"?><!-- 加载更多的FooterView布局 -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <LinearLayout
        android:id="@+id/ll_wrapper_load"
        android:layout_width="match_parent"
        android:layout_height="@dimen/l_60"
        android:gravity="center"
        android:orientation="horizontal">

        <ProgressBar
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:indeterminateDrawable="@drawable/view_wrapper_progress" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:padding="@dimen/l_15"
            android:text="@string/sm_wrapper_load"
            android:textColor="@color/color_d_7"
            android:textSize="@dimen/s_16" />
    </LinearLayout>

    <LinearLayout
        android:id="@+id/ll_wrapper_end"
        android:layout_width="match_parent"
        android:layout_height="@dimen/l_60"
        android:layout_marginLeft="@dimen/l_15"
        android:layout_marginRight="@dimen/l_15"
        android:gravity="center"
        android:orientation="horizontal">

        <View
            android:layout_width="0dp"
            android:layout_height="1dp"
            android:layout_weight="1"
            android:alpha="0.5"
            android:background="@color/color_d_7"
            android:gravity="start" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:padding="@dimen/l_15"
            android:text="@string/sm_wrapper_end"
            android:textColor="@color/color_d_7"
            android:textSize="@dimen/s_16" />

        <View
            android:layout_width="0dp"
            android:layout_height="1dp"
            android:layout_weight="1"
            android:alpha="0.5"
            android:background="@color/color_d_7"
            android:gravity="end" />
    </LinearLayout>
</LinearLayout>

三、RefreshLayout和WrapperAdapter都封装好了,接下来就是使用了

1、看一下Activity的布局文件,几行代码,简单至极

<?xml version="1.0" encoding="utf-8"?>
<qc.lot.lib.refresh.RefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/rl_um_refresh"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

2、看一下Activity的使用(添加回调、设置颜色、填充适配器)


回调需要重写的方法(上图前2个方法),这里有一个InitiateImpl类,同时Activity也实现了InitiateIF接口(上图中后2个方法),这里是用MVP模式搭建的框架,InitiateImpl类相当于Presenter层,InitiateIF接口是连接Presenter和View层的纽带;

InitiateImpl类实现数据逻辑,这里模拟了一下耗时操作:


3、填充适配器的地方,有一个InitateAdapter类,该类继承了BaseAbsAdapter,主要负责数据在RecyclerView中的填充,代码如下:

class InitiateAdapter extends BaseAbsAdapter<String> {

    InitiateAdapter(Context context) {
        super(context);
    }

    @Override
    public void onRefresh(List<String> list) {
        mList.clear();
        mList.addAll(list == null ? mList : list);
    }

    @Override
    public void onLoadMore(List<String> list) {
        mList.addAll(list == null ? new ArrayList<String>() : list);
    }

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

    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        return new InitiateViewHolder(inflate(parent, R.layout.item_um_initiate));
    }

    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
        ((InitiateViewHolder) holder).show(mList.get(position));
    }

    private class InitiateViewHolder extends RecyclerView.ViewHolder {
        private TextView mTvName;

        private InitiateViewHolder(View view) {
            super(view);
            mTvName = view.findViewById(R.id.tv_initiate_name);
        }

        private void show(String value) {
            mTvName.setText(value);
        }
    }
}

四、总结

下拉刷新和加载更多都实现了,回顾一下MVP模式、数据不足一页时,FooterView的处理逻辑,眸然回首,是不是发现很简单。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!