RecyclerView的上拉加载,下拉刷新

项目中最常使用的listview,其重要程度不言而喻。RecyclerView是对ListView的升级版,据说性能要比ListView高,而且功能要更强大。比如一个RecyclerView可以直接实现瀑布流,而且有着默认的动画效果。

上拉加载,下拉刷新是RecyclerView的拓展的非常实用的功能,在git上也有很多例子,但大都有非常繁琐的逻辑,自带好多不需要的功能。比如我们仅仅需要刷新功能就要导入一个jar包,直接的增加了安装包的体积。现在把RecyclerView的刷新功能抽离出来,做一个纯净的刷新列表,而且代码执行效率要高这么一点点。虽然说纯净,但是和市面上的刷新列表基本类似。

首先直接定义一个XRecyclerView继承RecyclerView,重写他的三个构造方法。

public XRecylcerView(Context context) {
        this(context, null);
    }

    public XRecylcerView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public XRecylcerView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init(context);
    }
init(Context mContext)方法用来初始化底部加载的view

所以我们先自定义一个底部布局LoadingMoreFooter继承Linearlayout,里面是一个居中显示的ProgressBar和一个TextView,添加一个方法setState(int state),来判定当前刷新的状态

public void setState(int state) {
        switch (state) {
            // 刷新中
            case STATE_LAODING:
                progressCon.setVisibility(View.VISIBLE);
                mText.setText("正在刷新");
                this.setVisibility(View.VISIBLE);
                break;
            // 刷新完成
            case STATE_COMPLETE:
                mText.setText("刷新完成");
                this.setVisibility(View.GONE);
                break;
            // 没有更多数据
            case STATE_NOMORE:
                mText.setText("没有更多数据啦");
                progressCon.setVisibility(View.GONE);
                this.setVisibility(View.VISIBLE);
                break;
        }

    }
回到XRecyclerView,实现init
private void init(Context context) {
        mContext = context;
        // loadingMoreEnabled为下拉的开关
        if (loadingMoreEnabled) {
            LoadingMoreFooter footerView = new LoadingMoreFooter(mContext);
            addFootView(footerView);
            mFootViews.get(0).setVisibility(GONE);
        }
    }

RecyclerView的上拉加载,原理很简单,无非就是当滑动到底部的时候,如果有数据,并且允许加载,就请求数据添加到adapter,而RecyclerView需要做的就是当加载的时候,在底部显示正在加载来提醒用户。判断是否滑动到底部,并且进行加载
/**
     * 监听滑动,来定位当前滑动到哪个地方
     *
     * @param state
     */
    @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) {

                View footView = mFootViews.get(0);
                isLoadingData = true;
                if (footView instanceof LoadingMoreFooter) {
                    ((LoadingMoreFooter) footView).setState(LoadingMoreFooter.STATE_LAODING);
                } else {
                    footView.setVisibility(View.VISIBLE);
                }
                mLoadingListener.onLoadMore(); // 一个回调接口,用来加载数据
            }
        }
    }

写到这个地方,基本的上拉加载的逻辑就搞定了,然后就是处理细节。我们需要把底部布局 LoadingMoreFooter加载到RecyclerView,这时候重写setAdapter(Adapter adapter)方法来添加一个adapter。我们这个需要自定义一个adapter来把底部布局加进去。自定义的Adapter如下
private class WrapAdapter extends RecyclerView.Adapter<ViewHolder> {

        private RecyclerView.Adapter adapter;

        private ArrayList<View> mFootViews;

        private int headerPosition = 0;

        public WrapAdapter(ArrayList<View> footViews, RecyclerView.Adapter adapter) {
            this.adapter = adapter;
            this.mFootViews = footViews;
        }

        @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 (isFooter(position))
                                ? gridManager.getSpanCount() : 1;
                    }
                });
            }
        }

        @Override
        public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) {
            super.onViewAttachedToWindow(holder);
            ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
            if(lp != null
                    && lp instanceof StaggeredGridLayoutManager.LayoutParams
                    &&  (isFooter( holder.getLayoutPosition())) {
                StaggeredGridLayoutManager.LayoutParams p = (StaggeredGridLayoutManager.LayoutParams) lp;
                p.setFullSpan(true);
            }
        }

        public boolean isFooter(int position) {
            return position < getItemCount() && position >= getItemCount() - mFootViews.size();
        }

        public int getFootersCount() {
            return mFootViews.size();
        }

        @Override
        public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            if (viewType == TYPE_FOOTER) {
                return new SimpleViewHolder(mFootViews.get(0));
            }
            return adapter.onCreateViewHolder(parent, viewType);
        }

        @Override
        public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
            if (isHeader(position)) {
                return;
            }
            int adjPosition = position;
            int adapterCount;
            if (adapter != null) {
                adapterCount = adapter.getItemCount();
                if (adjPosition < adapterCount) {
                    adapter.onBindViewHolder(holder, adjPosition);
                    return;
                }
            }
        }

        @Override
        public int getItemCount() {
            if (adapter != null) {
                return getFootersCount() + adapter.getItemCount();
            } else {
                return getFootersCount();
            }
        }

        @Override
        public int getItemViewType(int position) {
           
            if(isFooter(position)){
                return TYPE_FOOTER;
            }
            int adjPosition = position;
            int adapterCount;
            if (adapter != null) {
                adapterCount = adapter.getItemCount();
                if (adjPosition < adapterCount) {
                    return adapter.getItemViewType(adjPosition);
                }
            }
            return TYPE_NORMAL;
        }

        @Override
        public long getItemId(int position) {
            if (adapter != null) {
                int adjPosition = position - getHeadersCount();
                int adapterCount = adapter.getItemCount();
                if (adjPosition < adapterCount) {
                    return adapter.getItemId(adjPosition);
                }
            }
            return -1;
        }

        private class SimpleViewHolder extends RecyclerView.ViewHolder {
            public SimpleViewHolder(View itemView) {
                super(itemView);
            }
        }
    }
就是一个继承自RecyclerView.Adapter的adapter,主要用于根据类型加载不同的布局,普通的itemView和“正在加载”的底部提示。

回到setAdapter(Adapter adapter)方法

/**
     * 重写Adapter,通过状态判断是否显示“正在加载”
     *
     * @param adapter
     */
    @Override
    public void setAdapter(Adapter adapter) {
        this.mAdapter = adapter;
        this.mWrapAdapter = new WrapAdapter(mFootViews, mAdapter);// 定义WrapAdapter
        super.setAdapter(mWrapAdapter);// 通过父类方法将自定义的Adapter重新设置进去
        mAdapter.registerAdapterDataObserver(mDataObserver);//请看下面分析
    }
查看super.setAdapter()方法,会找到adapter.registerAdapterDataObserver(mObserver)方法,当adapter里面的数据发生改变时会即时监听并且更新。那为什么还要把mAdapter再设置一遍呢?其实当我们调用通过我们自定义的RecyclerView来调用setAdapter方法时,只有当WrapAdapter数据改变的时候,才会有更新,而当我们仅仅只更新mAdapter里面的数据的时候,如果不监听,我们看到的itemView并没有改变。

定义一个mDataObserver

private final RecyclerView.AdapterDataObserver mDataObserver = new RecyclerView.AdapterDataObserver() {
        @Override
        public void onChanged() {
            mWrapAdapter.notifyDataSetChanged();
        }

        @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);
        }
    };

都是直接调用父类的方法就可以。

这样一个RecyclerView的上拉加载逻辑就全部搞定了,这是极其简单的封装方法,所以逻辑没有多么的复杂。

好的,来回顾一下逻辑:重写RecyclerView进行滑动监听,当滑动到底部的时候通过重写setAdapter来将底部视图加载出来,最后对mAdapter进行数据更改的监听。

上拉刷新的逻辑更简单,因为有谷歌的SwipeRefreshLayout,所以实现起来就简单很多,首先来看布局文件

<android.support.v4.widget.SwipeRefreshLayout
        android:id="@+id/swipe"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <com.baiyyyhjl.pullrecyclerview.recyclerview.XRecylcerView
            android:id="@+id/recyclerview"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />

    </android.support.v4.widget.SwipeRefreshLayout>

直接用SwipeRefreshLayout将我们刚才自定义的RecyclerView包裹起来,然后swipeRefreshLayout.setOnRefreshListener(this)进行监听,实现onRefresh()接口来实现加载的逻辑。

在刚进入页面就进行刷新

swipeRefreshLayout.post(new Runnable() {
            @Override
            public void run() {
                swipeRefreshLayout.setRefreshing(true);
                // TODO 获取数据
            }
        });

就这样,一个简单实用的RecyclerView上拉加载,下拉刷新就实现了。没有多余的布局文件,极其简便。当我们项目中有多个RecyclerView并且要求上拉加载,下拉刷新的时候,我们可以定义一个抽象类,只通过修改itemView的布局就能实现。

下一篇将对自定义的XRecyclerView进行拓展,可以随意添加头布局。






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值