RecyclerView+SwipeRefreshLayou封装下拉刷新,上拉加载功能

来源:互联网 发布:npoi 导出excel java 编辑:程序博客网 时间:2024/05/16 17:34

随着Android系统的进步,我们不该老有老旧的ListView来封装下拉刷新,上拉加载了,今天记录一下如何用RecyclerView+SwipeRefreshLayou 封装下拉刷新上拉加载。

Android很奇怪。。以前是没有提供瀑布流,刷新控件等。现在是提供了刷新的SwipeRefreshLayou 但又没提供加载更多的功能╮(╯▽╰)╭。总是要劳烦大家自己弄出五花八门的东西来。而且这个SwipeRefreshLayou 和RecyclerView封装下拉刷新还会出现一些问题。比如:

1、手动调用SwipeRefreshLayou 的setRefresh(true)方法,是可以显示刷新动画,但并不会回调onRefresh()方法。

2、假定已经封装好了,写一个方法供外部调用,内部手动调用setRefresh(true)并且手动调用onRefresh()方法,其他情况都好用,但在首次进入页面的时候,会发现刷新动画不显示。

3、我们实现加载更多的思路是:当滚动到最后一个Item的时候,再就可以加载更多了。但是RecyclerView的布局管理器如何获取到这个情况呢?

4、瀑布流不规则,如何在最后一行显示一个充满屏幕宽度的加载更多布局?

先来看一下效果:



用户体验非常的好,这只是简单的演示版本,成品程序更完善。

这里来贴一下主要实现类的代码:

RefreshLoadingRecyclerView.java   封装SwipeRefreshLayout与RecyclerView的类,以及主要逻辑:
/** * Created by Ade-rui on 2016/12/19. */public class RefreshLoadingRecyclerView extends FrameLayout implements SwipeRefreshLayout.OnRefreshListener{    /**     * 空闲状态     */    public static final int STATE_IDLE = 1002;    /**     * 加载更多中     */    public static final int STATE_LOAD_MORE = 1003;    /**     * 刷新中     */    public static final int STATE_REFRESH = 1004;    /**     * 刷新器     */    private SwipeRefreshLayout swipeRefreshLayout;    /**     * recyclerview     */    private RecyclerView recyclerView;    /**     * 刷新与加载的回调监听     */    private OnRefreshLoadingListener listener;    /**     * 布局管理器     */    private ILayoutManager layoutManager;    /**     * 适配器     */    private BaseRecyclerAdapter adapter;    /**     * 当前状态     * 默认为空闲     */    private int mCurrentState = STATE_IDLE;    /**     * 是否开启加载更多功能     */    private boolean isEnableLoadMore;    public RefreshLoadingRecyclerView(Context context) {        super(context);        init();    }    public RefreshLoadingRecyclerView(Context context, AttributeSet attrs) {        super(context, attrs);        init();    }    private void init() {        LayoutInflater.from(getContext()).inflate(R.layout.layout_recycler_view,this,true);        swipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swipe_refresh_layout);        recyclerView = (RecyclerView) findViewById(R.id.recycler_view);        swipeRefreshLayout.setOnRefreshListener(this);        //设置recyclerView默认的LayoutManager -- > 相当于普通listview样式        this.layoutManager = new MyLinearLayoutManager(getContext());        recyclerView.setLayoutManager(layoutManager.getLayoutManager());        recyclerView.addOnScrollListener(onScrollListener);    }    /**     * 滚动监听     */    private RecyclerView.OnScrollListener onScrollListener = new RecyclerView.OnScrollListener() {        @Override        public void onScrollStateChanged(RecyclerView recyclerView, int newState) {            super.onScrollStateChanged(recyclerView, newState);        }        @Override        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {            super.onScrolled(recyclerView, dx, dy);            /**             * mCurrentState == STATE_IDLE  因为如果是加载中的话,继续有下拉的动作,还会回调加载             * ,所以必须是空闲状态下才能回调加载。进入加载回调就立刻将状态修改为加载状态,避免再次回聊。             * isEnableLoadMore : 用来手动设置是否开启加载更多的功能             * loadingSecurity() :判断是否可以加载更多,如何实现由具体的布局管理器而定             * dy>0 : 为了返回有时候数据不满一屏,都没有滚动,便发生了加载的情况             */            if(mCurrentState == STATE_IDLE&&isEnableLoadMore && loadingSecurity() && dy > 0){                loadMore();            }        }    };    /**     * 设置是否开启加载更多的功能     * @param isEnableLoadMore     */    public void isEnableLoadMore(boolean isEnableLoadMore){        this.isEnableLoadMore = isEnableLoadMore;    }    /**     * 加载更多     */    private void loadMore() {        if(listener == null){            return;        }        //修改状态        mCurrentState = STATE_LOAD_MORE;        //禁止swipeRefreshLayout功能        swipeRefreshLayout.setEnabled(false);        //通知adapter显示加载更多的布局        adapter.loadMoreChangeState(true);        listener.onLoading();    }    /**     * 完成加载或者刷新     * 由用户调用     */    public void complete(){        if(mCurrentState == STATE_LOAD_MORE){            //通知adapter隐藏加载更多布局            adapter.loadMoreChangeState(false);            //回复SwipeRefreshLayout功能            swipeRefreshLayout.setEnabled(true);        }else if(mCurrentState == STATE_REFRESH){            //让刷新动画消失            swipeRefreshLayout.setRefreshing(false);        }        mCurrentState = STATE_IDLE;    }    /**     * 是否可以加载     * 根据具体的LayoutManager实现来判断     * @return     */    private boolean loadingSecurity() {        return layoutManager.loadMore();    }    /**     * 设置适配器     * @param adapter     */    public void setAdapter(BaseRecyclerAdapter adapter){        this.adapter = adapter;        recyclerView.setAdapter(adapter);    }    /**     * 设置布局管理器     * @param layoutManager     */    public void setLayoutManager(ILayoutManager layoutManager){        this.layoutManager = layoutManager;        recyclerView.setLayoutManager(layoutManager.getLayoutManager());    }    /**     * 设置刷新加载监听     * @param listener     */    public void setOnRefreshLoadingListener(OnRefreshLoadingListener listener){        this.listener = listener;    }    /**     * 刷新     */    public void setRefresh(){        /**         *  使用post加入消息队列调用---》 可以防止第一次进入页面的时候,         *  虽然执行swipeRefreshLayout.setRefreshing(true),但并不会显示刷新动画的bug         */        post(new Runnable() {            @Override            public void run() {                swipeRefreshLayout.setRefreshing(true);                onRefresh();            }        });    }    /**     * RereshLayout监听到下拉手势的回调,然后我们在这个回调里面修改自身控件的状态     * 并且回调总结的listener给用户     */    @Override    public void onRefresh() {        if(listener!=null){            mCurrentState = STATE_REFRESH;            Log.i("mydata","onRefresh");            listener.onRefresh();        }    }    /**     * 监听回调     */    public interface OnRefreshLoadingListener{        void onRefresh();        void onLoading();    }}


短短的200行代码便可以实现这个功能了。在XML文件声明以及在Activity中的使用:
<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"    android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"    android:paddingRight="@dimen/activity_horizontal_margin"    android:paddingTop="@dimen/activity_vertical_margin"    android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity">    <drawapp.cw.com.recyclerviewdemo.RefreshLoadingRecyclerView        android:id="@+id/pull_view"        android:layout_width="match_parent"        android:layout_height="match_parent"></drawapp.cw.com.recyclerviewdemo.RefreshLoadingRecyclerView></RelativeLayout>

Activity:
 pullView = (RefreshLoadingRecyclerView) findViewById(R.id.pull_view);        adapter = new BaseRecyclerAdapter(new ArrayList<String>());        pullView.setAdapter(adapter);        pullView.setOnRefreshLoadingListener(new RefreshLoadingRecyclerView.OnRefreshLoadingListener() {            @Override            public void onRefresh() {                refresh();            }            @Override            public void onLoading() {                load();            }        });        pullView.isEnableLoadMore(true);        pullView.setRefresh();

使用起来与流行的第三方库等没有任何差别。
现在来统一总结一下都需要解决哪些问题:

1、手动调用SwipeRefreshLayou 的setRefresh(true)方法,是可以显示刷新动画,但并不会回调onRefresh()方法。

解决方法与2是一体的,向外提供一个setRefresh()方法,外部调用的时候,内部同时调用显示动画和onRefresh方法。

2、假定已经封装好了,写一个方法供外部调用,内部手动调用setRefresh(true)并且手动调用onRefresh()方法,其他情况都好用,但在首次进入页面的时候,会发现刷新动画不显示。

在内部使用post加入消息队列中使用,在第一次进入页面就也可以看到刷新动画了

 post(new Runnable() {            @Override            public void run() {                swipeRefreshLayout.setRefreshing(true);                onRefresh();            }        });

3、我们实现加载更多的思路是:当滚动到最后一个Item的时候,再就可以加载更多了。但是RecyclerView的布局管理器如何获取到这个情况呢?

LinearLayoutManager和GridLayoutManager的方法:
        //最后一个可见Item的索引        int lastVisiableItemIndex = findLastVisibleItemPosition();        //总共Item的数量        int totalItemIndex = getItemCount();        //这里取<=1的时候返回true表示可以加载更多,也就是说当滑动到倒数第二个Item出现的时候就代表可以加载了

这两个布局管理器都有获取最后一个课件Item的方法,但是StaggeredGridLayoutManager 没有这个方法,用以前方法获取:
 int[] positions = null;        positions = findLastVisibleItemPositions(positions);        positions[0];

因为瀑布流一行中不等高,所以从概念上来讲,就是得到一组最后出现的Item索引,我们拿到第一个进行比较就行了,不会差很多的。
4、瀑布流不规则,GridLayoutManager一列不占全屏幕,那么如何在最后一行显示一个充满屏幕宽度的加载更多布局?
一、GridLayoutManager的解决方案:

GrildLayoutManager源码对于类的解释大概有如下:

/** * A {@link RecyclerView.LayoutManager} implementations that lays out items in a grid. * <p> * By default, each item occupies 1 span. You can change it by providing a custom * {@link SpanSizeLookup} instance via {@link #setSpanSizeLookup(SpanSizeLookup)}. */
大致意思就是,每一列显示1span单位, 如果你想改变这一列所占的单位span 就调用setSpanSizeLookup方法,那么span是什么?是我们在new GridLayoutManager时传入的第二个参数就是spanCount,几列就传入几。所以重写GrildLayoutManager:

/** * Created by lenovo on 2016/12/20. */public class MyGridLayoutManager extends GridLayoutManager implements ILayoutManager {    public MyGridLayoutManager(BaseRecyclerAdapter adapter,Context context, int spanCount) {        super(context, spanCount);        setSpanSizeLookup(new BaseRecyclerAdapter.FooterSpanSize(adapter,this));    }    public MyGridLayoutManager(Context context, int spanCount, int orientation, boolean reverseLayout) {        super(context, spanCount, orientation, reverseLayout);    }    @Override    public boolean loadMore() {        //第一个可见Item的索引        int lastVisiableItemIndex = findLastVisibleItemPosition();        //总共Item的数量        int totalItemIndex = getItemCount();        //这里取<=1的时候返回true表示可以加载更多,也就是说当滑动到倒数第二个Item出现的时候就代表可以加载了        //你也可以写成<5 或者 == 0 随便,由自己的体验来决定        return totalItemIndex - lastVisiableItemIndex <= 1;    }    @Override    public RecyclerView.LayoutManager getLayoutManager() {        return this;    }    @Override    public SpanSizeLookup getSpanSizeLookup() {        return super.getSpanSizeLookup();    }}

在构造的时候便调用setSpanSizeLookUp设置我们自定义的lookUpSize:

 public  static  class  FooterSpanSize extends GridLayoutManager.SpanSizeLookup {        private  BaseRecyclerAdapter adapter;        private  GridLayoutManager layoutManager;        public FooterSpanSize(BaseRecyclerAdapter adapter,GridLayoutManager layoutManager){            this.layoutManager = layoutManager;            this.adapter = adapter;        }        /**         * 每显示一个Item的时候这个方法就会被回调,并传入item的索引position;         * 为什么要持有Adapater的引用? 因为我们需要让Adapter来判断是否是加载更多的布局         * 为什么要持有自身LayoutManager的引用? 因为我们需要知道总共几列         * @param position         * @return         */        @Override        public int getSpanSize(int position) {            if(adapter.enableLoadMore(position)){                return layoutManager.getSpanCount();            }            return 1;        }    }

那么StaggeredGridLayoutManager是否也有这样的方法? 答案,没有,那么该如何让加载更多的Item显示整行呢?

 public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {        //如果是最后一个Item并且要加载更多,则只显示加载更多的布局,不进行bind数据操作        if(position == getItemCount() - 1 && isLoadMore){            if(holder.itemView.getLayoutParams() instanceof StaggeredGridLayoutManager.LayoutParams){                StaggeredGridLayoutManager.LayoutParams params = (StaggeredGridLayoutManager.LayoutParams) holder.itemView.getLayoutParams();                params.setFullSpan(true);            }            return;        }        //这里其实应该写一个BaseViewHolder 调用其中的onBind抽象方法,让其具体的子类去绑定        //不要用强转这种方式,这里为了做例子方便所以强转        ((ContentViewHolder)holder).tvContent.setText(datas.get(position));    }
我们只有在onBind的时候来动态判断,此viewHolder的布局的LayoutParams是不是StaggeredGridLayoutManager.LayoutParams 如果是的话就取出来,调用去setFullSpan(true),来让此Item显示一整行。有人说为什么不在ViewHolder初始化的时候就调用,我只想说。。那时候你的View还没有添加进入瀑布流的父View,自身的LayoutParams应该还不是为StaggeredGridLayoutManager.LayoutParams的吧。个人臆测,没有实际测验。

为什么要有ILayoutManager接口?

因为每一个layoutManager判断是否可以加载更多的逻辑不一样,所以必须用接口来编程。

文章中填出的代码示例直接拷贝不能立即使用,想使用请下载源码查看修改自己对应需求来使用就行。

后期我会将此Demo整理成可用项目上传至GitHub

---------------------------------------------------------------

所有的难点都已经解决,代码敲得灵活度并不是很大,不适合开源,但对应每一个想为自己项目封装一套的童鞋来说,这个完全没有问题,覆盖了所有的解决的方案。如果有什么问题,请留言。

源码链接-------http://download.csdn.net/detail/sinat_31311947/9717971

0 0
原创粉丝点击