美文网首页定义控件安卓实用知识学习区
Android RecycleView轻松实现下拉刷新、加载更多

Android RecycleView轻松实现下拉刷新、加载更多

作者: lovejjfg | 来源:发表于2016-08-07 22:01 被阅读5185次
    ![PullRefresh.gif](http:https://img.haomeiwen.com/i2244299/ea8bb3b32f34f5aa.gif?imageMogr2/auto-orient/strip)

    那如同这个题目,这里面涉及的东西其实还是比较多的,RecycleView SwipeRefreshLayout,下拉刷新(这个就是SwipeRefreshLayout的),加载更多。

    SwipeRefreshLayout

    这个是Google自己封装的一个下拉刷新的控件,里面使用了5.0开始的嵌套滑动机制,有兴趣的朋友可以去看看源码!使用起来其实就涉及到以下方法:

    setOnRefreshListener() 下拉刷新的相关回调。

    setRefresh() 通知是否开始刷新或者刷新完成。(坑1)

    setColorSchemeColors() loading的时候progressbar的颜色,支持多个。

    SwipeRefreshLayout的坑

    进入页面调用setRefresh(true),根本不显示刷新的小圆圈?!
    简单的说,这个就是在onCreate()方法执行的时候,view还没有绘制出来,这个时候你设置刷新不刷新其实都一样的,解决方法,post一下!

    mRecyclerView.post(new Runnable() {
            @Override
            public void run() {
                mRefreshLayout.setRefreshing(refresh);
            }
        });
    

    RecycleView

    RecycleView其实出现都有一定的年头了,前几天公司来面试的居然说他还没有用过。。这个也是醉醉的!

    RecycleViewListView的强力升级!加入了holder便于管理和复用相同的类型。

    就我目前掌握的情况,RecycleView对于ListView有了以下的不同:

    1、加入了LayoutManager用用管理各种类型的布局,而且通过不同的布局可以实现横向、竖向、瀑布式的等各种复杂的布局。

    2、加入Holder来管理相关布局和复用,对于每一种Type的View你都要创建一个对应的Holder来管理它!

    3、取消了header和bottom布局。

    4、没有现成的itemClick回调。

    5、引入了丰富的动画效果。(坑4)

    6、添加了丰富的数据刷新的方法,可以局部刷新了!(坑3)

    7、可自定义相关分割线。

    8、支持swipe删除和drag排序。(ItemTouchHelper 帮助类)

    9、默认是不显示scrollBar的(坑2)

    10、可以设置不同类型holder占据不同的空间(ItemColumnSpan GridLayoutManager)

    上面这些不是所有的都讲,其实本文主要涉及的就是相关adapter,里面对应不同的holder,及相关的封装。然后说说踩了哪些坑。

    基本思路

    • 1、明确什么时候开始加载更多?

    下拉刷新就调用SwipeRefreshLayout相关就好了,那么加载更多呢?这个就要自己去写相关的布局了。然后第一个问题,什么时候加载更多??因为RecycleView有各种布局,所以判断最后一个也是要区分不同的adapter的!

    • 2、加载更多有多少种情况?
      大致有三种,正在加载更多;加载更多错误;没有更多数据了;

    具体实现

    1、监听滑动,满足条件开始加载更多。

            @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            if (null != scrollListener) {
                scrollListener.onScrolled(SwipeRefreshRecycleView.this, dx, dy);
            }
            if (null == manager) {
                throw new RuntimeException("you should call setLayoutManager() first!!");
            }
            if (null == adapter) {
                throw new RuntimeException("you should call setAdapter() first!!");
            }
            if (manager instanceof LinearLayoutManager) {
                int lastCompletelyVisibleItemPosition = ((LinearLayoutManager) manager).findLastCompletelyVisibleItemPosition();
    
                if (adapter.getItemCount() > 1 && lastCompletelyVisibleItemPosition >= adapter.getItemCount() - 1 && adapter.isHasMore()) {
                    adapter.isLoadingMore();
                    if (null != listener) {
                        listener.onLoadMore();
                    }
                }
                int position = ((LinearLayoutManager) manager).findFirstVisibleItemPosition();
                if (lastTitlePos == position) {
                    return;
                }
                lastTitlePos = position;
            }
            if (manager instanceof StaggeredGridLayoutManager) {
                int[] itemPositions = new int[2];
                ((StaggeredGridLayoutManager) manager).findLastVisibleItemPositions(itemPositions);
    
                int lastVisibleItemPosition = (itemPositions[1] != 0) ? ++itemPositions[1] : ++itemPositions[0];
    
                if (lastVisibleItemPosition >= adapter.getItemCount()  && adapter.isHasMore()) {
                    adapter.isLoadingMore();
                    if (null != listener) {
                        listener.onLoadMore();
                    }
                }
    
            }
    
        }
    

    2、定义自己的加载更多的ViewHolder。

    3.定义相关的方法实时更新ViewHolder的三种状态。

    public class NewBottomViewHolder extends RecyclerView.ViewHolder{
        @Bind(R.id.footer_container)
        public LinearLayout contaier;
    
        @Bind(R.id.progressbar)
        ProgressBar pb;
        @Bind(R.id.content)
        TextView content;
        @Nullable
        private final SwipeRefreshRecycleView.OnRefreshLoadMoreListener mListener;
    
        public NewBottomViewHolder(View itemView, SwipeRefreshRecycleView.OnRefreshLoadMoreListener listener) {
    
            super(itemView);
            ButterKnife.bind(this,itemView);
            mListener = listener;
        }
    
        public void bindDateView(int state) {
            switch (state) {
                case AdapterLoader.STATE_LASTED:
                    contaier.setVisibility(View.VISIBLE);
                    contaier.setOnClickListener(null);
                    pb.setVisibility(View.GONE);
                    content.setText("---  没有更多了  ---");
                    break;
                case AdapterLoader.STATE_LOADING:
                    contaier.setVisibility(View.VISIBLE);
                    content.setText("加载更多!!");
                    contaier.setOnClickListener(null);
                    pb.setVisibility(View.VISIBLE);
                    break;
                case AdapterLoader.STATE_ERROR:
                    contaier.setVisibility(View.VISIBLE);
                    pb.setVisibility(View.GONE);
                    content.setText("--- 加载更多失败点击重试 ---");
                    contaier.setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View v) {
                            if (mListener != null) {
                                mListener.onLoadMore();
                            }
                            content.setText("加载更多!!");
                            pb.setVisibility(View.VISIBLE);
                        }
                    });
                    break;
            }
        }
    
    }
    

    4.定义相关扩展方法便于用户自己定义底部布局及相关状态处理。

    这里就必须详细讲讲Adapter里面的相关方法了!

    getItemCount(),在RecycleView知道它一共有多少数量的Item需要展示,返回0之后不会执行剩余的方法!

    onCreateViewHolder(ViewGroup parent, int viewType),某种Type的Holder第一次创建的时候会调用该方法,当然没有复用的时候也会去创建,一旦复用了,改方法不会再执行了!

    onBindViewHolder(RecyclerView.ViewHolder holder, int position),每一次更新对应itemView的时候都会调用该方法,所以在该方法中要实时的刷新数据!(因为存在复用,所以刷新的时候一定要彻底!!!

    以上三个方法是必须实现的,因为在父类adapter里是抽象滴!

    还有一个方法也比较重要:

    getItemViewType(int position),这个方法是返回对应pos的类型的,如果你只有一个类型,不需要重写该方法,默认返回的是0。

    是不是这么说起来比ListView还要爽一点儿?不用去判断什么convertView==null!

    @Override
    public final RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        switch (viewType) {
            case TYPE_BOTTOM:
                if (loadMore != null) {
                    RecyclerView.ViewHolder holder = onBottomViewHolderCreate(loadMore);
                    if (holder == null) {
                        throw new RuntimeException("You must impl onBottomViewHolderCreate() and return your own holder ");
                    }
                    return holder;
                } else {
                    return new BottomViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.recycler_footer, parent, false));
                }
            default:
                return onViewHolderCreate(parent, viewType);
        }
    
    }
    
    @Override
    public final void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        if (getItemViewType(position) == TYPE_BOTTOM) {
            loadState = loadState == STATE_ERROR ? STATE_ERROR : isHasMore() ? STATE_LOADING : STATE_LASTED;
            if (loadMore != null) {
                try {
                    onBottomViewHolderBind(holder, loadState);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            } else {
                try {
                    ((BottomViewHolder) holder).bindDateView(loadState);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        } else {
            onViewHolderBind(holder, position);
        }
    }
    

    这里在RefreshRecycleAdapter<T>中实现了刚刚说的三个方法,并且相关的已经加final修饰了!所以之后你只需要实现如下方法来完成你自己的item填充就好了:

       RecyclerView.ViewHolder onViewHolderCreate(ViewGroup parent, int viewType);
    
    void onViewHolderBind(RecyclerView.ViewHolder holder, int position);
    

    对于加载更多的几种状态的更改,提供如下的相关方法!

    boolean isHasMore();
    
    void isLoadingMore();
    
    void loadMoreError();
    

    对于创建自己制定的加载更多的布局,提供如下方法扩展!

    void setLoadMoreView(View view);
    
    RecyclerView.ViewHolder onBottomViewHolderCreate(View loadMore);
    
    void onBottomViewHolderBind(RecyclerView.ViewHolder holder, int loadState);
    

    还没有说的那就是数据源相关的方法。提供了set和append两种方式!

    void setList(List<T> data);
    
    void appendList(List<T> data);
    
    @Override
    public final void appendList(List<T> data) {
        int positionStart = list.size();
        list.addAll(data);
        int itemCount = list.size() - positionStart;
    
        if (positionStart == 0) {
            notifyDataSetChanged();
        } else {
            notifyItemRangeInserted(positionStart + 1, itemCount);
        }
    }
    

    还是那话,这些方法都是RefreshRecycleAdapter<T>里面写好的,我们写自己的adapter时更本不用去care!只需要去调用setList()或者appendList()就好了!!

    说到这里不得不提提RecycleView刷新数据的相关方法和坑!

    notifyDataSetChanged()的基础上, RecycleView增加了一系列的方法用于增删改。所以不要再任性的一味使用notifyDataSetChanged(),这样也不专业了!

    notifyItemInserted();
    notifyItemRangeInserted();
    
    notifyItemChanged();
    notifyItemRangeChanged();
    
    notifyItemRemoved();
    notifyItemRangeRemoved();
    

    在使用的过程中,发现调用notifyItemChanged()之后不会去执行onBindViewHolder(),(坑3 坑4)这个就导致刷新没有触发了!最后搜到的结果是因为mRecyclerView.setItemAnimator(new DefaultItemAnimator())引起的,解决方案是复写相关方法

    @Override
    public boolean canReuseUpdatedViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, @NonNull List<Object> payloads) {
        return true;
    }
    

    小结

    首先通过addList()或者appendList()的方法设置相关数据源。

    滑动过程中需要加载更多时回调相关方法,并在adapter中通知相关状态刷新。

     adapter.isLoadingMore();
     if (null != listener) {
        listener.onLoadMore();
     }
    

    加载错误的时候调用相关的方法通知状态改变。

    adapter.loadMoreError();
    

    创建BottomHolder的时候判断有没有设置自定义的view,如果有,那么就去走子类的onBottomViewHolderCreate()方法创建自定义的Bottomholder,然后实时更新相关数据!

        @Override
    public final void setLoadMoreView(@NonNull View view) {
        loadMore = view;
    }
    
    if (loadMore != null) {
        RecyclerView.ViewHolder holder = onBottomViewHolderCreate(loadMore);
        if (holder == null) {
            throw new RuntimeException("You must impl onBottomViewHolderCreate() and return your own holder ");
        }
        return holder;
            } 
    

    最后在onBottomViewHolderBind(RecyclerView.ViewHolder holder, int state)的方法中执行bindDateView(state)实时刷新相关的状态。

    public void bindDateView(int state) {
        switch (state) {
            case AdapterLoader.STATE_LASTED:
                contaier.setVisibility(View.VISIBLE);
                contaier.setOnClickListener(null);
                pb.setVisibility(View.GONE);
                content.setText("---  没有更多了  ---");
                break;
            case AdapterLoader.STATE_LOADING:
                contaier.setVisibility(View.VISIBLE);
                content.setText("加载更多!!");
                contaier.setOnClickListener(null);
                pb.setVisibility(View.VISIBLE);
                break;
            case AdapterLoader.STATE_ERROR:
                contaier.setVisibility(View.VISIBLE);
                pb.setVisibility(View.GONE);
                content.setText("--- 加载更多失败点击重试 ---");
                contaier.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        if (mListener != null) {
                            mListener.onLoadMore();
                        }
                        content.setText("加载更多!!");
                        pb.setVisibility(View.VISIBLE);
                    }
                });
                break;
        }
    }
    

    PS 最后还有默认是不显示scrollBar的问题,这个问题,似乎必须在xml里面配置,不能代码直接new RecycleView。然后可以使用相关代码控制ScrollBar是否显示!

    mRecyclerView.setVerticalScrollBarEnabled(true)
    
    <android.support.v7.widget.RecyclerView
        android:id="@+id/recycle_view"
        android:scrollbars="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
    

    另外对于特性8、10这里就不详细介绍了,滑动删除和拖拽排序在TouchHelperCallback中有相关支持!方法如下:

    @Override
    public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
        if (callBack != null) {
            callBack.onItemMove(viewHolder.getAdapterPosition(),
                    target.getAdapterPosition());
        }
        return true;
    }
    
    @Override
    public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
        if (callBack != null) {
            callBack.onItemDismiss(viewHolder.getAdapterPosition());
        }
    }
    

    详细的可以参照相关Demo-FangShiActivity


    gradle快速集成

      compile 'com.lovejjfg.powerrecycle:powerrecycle:1.0.0'
    

    相关下载

    演示Demo下载

    项目中的使用

    ---- Edit By Joe ----

    相关文章

      网友评论

      本文标题:Android RecycleView轻松实现下拉刷新、加载更多

      本文链接:https://www.haomeiwen.com/subject/idahsttx.html