美文网首页嵌牛IT观察安卓学习Android
安卓RecyclerView那些事-(二)实现一个下拉刷新、划到

安卓RecyclerView那些事-(二)实现一个下拉刷新、划到

作者: 小怪兽大作战 | 来源:发表于2018-12-19 16:34 被阅读1次

    这次,我们在RecyclerView的基础之上来实现一个下拉刷新,滑到底部加载更多的RecyclerView。如果你还不熟悉RecyclerView的基本使用方法,请看我的上一篇博客安卓RecyclerView那些事 - (一)了解RecycleView

    需求

    我们要实现的功能:(1)下拉刷新RecyclerView的数据,更新数据,(2)划到底部加载更多数据,更新数据(3)加载更多数据时显示“正在加载”的footerView

    需求分析

    1.下拉刷新数据

    这个功能比较容易实现,通常的做法在RecyclerView外部嵌套一个SwipeRefreshLayout,然后给SwipeRefreshLayout添加刷新监听函数,在刷新监听函数中我们重新获取数据,并更新RecyclerView的数据。还不清楚SwipeRefreshLayout怎么用的同学可以看这个博客android之官方下拉刷新组件SwipeRefreshLayout;

    2.划到底部加载更多数据

    要实现这个功能我们首先要判断什么样的情况是划到了RecyclerView的底部。

    通过LinearLayoutManager判断

    通过LinearLayoutManager获得一下几个参数:item的总个数,当前可见的最后一个item在所有item中的位置,当前可见的最后一个item的bottom,RecyclerView的bottom。
    我们为RecyclerView添加滑动监听函数RecyclerView.addOnScrollListener(mOnScrollListener),在滑动监听函数中我们判断当前可见的最后一个item是不是RecyclerView中所有item的最后一个,并且当前可见的最后一个item的bottom是不是等于RecyclerView的bottom。
    代码如下:

        public MyRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
            super(context, attrs, defStyle);
            addOnScrollListener(mOnScrollListener);//添加底部加载接口
        }
        
        /**
         * 滑动监听
         * 滑动到最后一个item的底部时加载更多信息
         */
        private OnScrollListener mOnScrollListener = new OnScrollListener() {
            @Override
            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                super.onScrollStateChanged(recyclerView, newState);
            }
    
            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                LayoutManager layoutManager = recyclerView.getLayoutManager();
                if (layoutManager instanceof LinearLayoutManager) {
                    LinearLayoutManager linearLayout = (LinearLayoutManager) layoutManager;
                    int mLastChildPosition = linearLayout.findLastVisibleItemPosition();//当前页面最后一个可见的item的位置
                    int itemTotalCount = linearLayout.getItemCount();//获取总的item的数量
                    View lastChildView = linearLayout.getChildAt(linearLayout.getChildCount() - 1);//最后一个子view
                    Log.e(TAG,"mLastChildPosition:"+mLastChildPosition+",itemTotalCount:"+itemTotalCount);
                    int lastChildBottom = lastChildView.getBottom();//最后一个子view的bottom
                    int recyclerBottom = getBottom();
                    if (mLastChildPosition == itemTotalCount - 1 && lastChildBottom == recyclerBottom) {//当前页面的最后一个item是item全部的最后一个并且当前页面的最后一个item的底部是recycleView的底部的时候,获取新数据
                        if (listener != null) {
                            //业务代码
                            listener.loadMore();
                        }
                    }
                }
            }
        };
    
    通过RecyclerView的canScrollVertically()函数判断

    canScrollVertically()是在View中定义的,用来判断是否能进行滑动。我们看一下这个方法怎么用


    boolean canScrollVertically(int)

    这个方法需要传入一个int型变量,当这个变量为正时函数返回否能向上滑动,为负时返回能否向下滑动。

    同样,我们要利用canScrollVertically实现判断是否滑倒底部,需要在RecyclerView的滑动监听函数中判断canScrollVertically(1)的值。代码如下

        public MyRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
            super(context, attrs, defStyle);
            addOnScrollListener(mOnScrollListener);//添加底部加载接口
        }
    
        /**
         * 滑动监听
         * 滑动到最后一个item的底部时加载更多信息
         */
        private OnScrollListener mOnScrollListener = new OnScrollListener() {
            @Override
            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                super.onScrollStateChanged(recyclerView, newState);
            }
    
            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                if(!canScrollVertically(1)){
                    if (listener != null) {
                            listener.loadMore();
                    }
                }
            }
        };
    

    3.RecyclerView自定义footerView

    我们知道Adapter通过先调用onCreateViewHolder(ViewGroup parent, int viewType)创建视图(当创建足够的ViewHolder后便不再创建,我的上一篇博客 安卓RecyclerView那些事 - (一)了解RecycleView),然后调用onBindViewHolder(MyViewHolder holder, int position)为视图绑定数据。
    大家有没有注意onCreateViewHolder(ViewGroup parent, int viewType)的参数中有一个viewType,那这个参数是哪里来的呢?其实RecyclerView还有一个方法getItemViewType(int position)
    其实,Adapter创建每一个item时,先调用getItemViewType(int position)获得item的类型,然后调用onCreateViewHolder(ViewGroup parent, int viewType)创建视图,最后调用onBindViewHolder(MyViewHolder holder, int position)为视图绑定数据。顺序如下图所示。

    image.png

    我们可以在getItemViewType(int position)中根据position返回不同的ViewType,在onCreateViewHolder(ViewGroup parent, int viewType)中根据viewType创建不同的ViewHolder,最后在onBindViewHolder(MyViewHolder holder, int position)中绑定数据。这样,就可以在RecyclerView中显示不同类型的item,甚至可以在RecyclerView中嵌套RecyclerView等各种View。

    代码实现

    1.自定义一个ViewHolder

    public class MyViewHolder extends RecyclerView.ViewHolder{
        private SparseArray<View> mHolderView;//缓存子控件View
        private View ParentView;    //最外层view
    
        public MyViewHolder(View itemView) {
            super(itemView);
            this.ParentView=itemView;
    
            if(mHolderView==null){
                mHolderView=new SparseArray<>();
            }
        }
    
        /**
         * 根据layoutID创建ViewHolder
         * @param parent
         * @param layoutId
         * @return
         */
        public static MyViewHolder createViewHolder(ViewGroup parent,int layoutId){
            View view= LayoutInflater.from(parent.getContext()).inflate(layoutId,parent,false);
            return new MyViewHolder(view);
        }
    
        /**
         * 根据View创建viewHolder
         * @param view
         * @return
         */
        public static MyViewHolder createViewHolder(View view){
            return new MyViewHolder(view);
        }
    
        /**
         * 获取View
         * @param id
         * @param <T>
         * @return
         */
        public <T extends View> T getView(int id){
            View view=mHolderView.get(id);
            if(view==null){
                view=ParentView.findViewById(id);
                mHolderView.put(id,view);
            }
            return (T) view;
        }
    }
    

    2.定义一个Model,用于模拟数据加载

    public class Model {
        public static List<Integer> getData(){
            List<Integer> list=new ArrayList<>();
            for(int i=0;i<20;i++){
                list.add(i);
            }
            return list;
        }
    }
    

    3.自定义Adapter

    重写Adapter中的getItemViewType,onCreateViewHolder,onBindViewHolder,getItemCount。

    public class MyAdapter extends RecyclerView.Adapter<MyViewHolder> {
        private List<Integer> list;//数据list
        private static final String TAG="MyAdapter";
        public MyAdapter(List<Integer> list) {
            this.list = list;
        }
    
        /**
         * 返回view类型
         *
         * @param position
         * @return
         */
        @Override
        public int getItemViewType(int position) {
            Log.e(TAG,"position:"+position);
            Log.e(TAG,"getItemViewType");
            if (position == getItemCount() - 1)//如果是最后一个item,则是底部布局
                return VIEW_TYPE_FOOTER;
            return VIEW_TYPE_NOMAL;  //正常item
        }
    
        @Override
        public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            Log.e(TAG,"onCreateViewHolder");
            if (viewType == VIEW_TYPE_FOOTER)
                return MyViewHolder.createViewHolder(parent, R.layout.item_root_footer);//返回底部布局
            return MyViewHolder.createViewHolder(parent, R.layout.item_normal); //返回正常item
        }
    
        @Override
        public void onBindViewHolder(MyViewHolder holder, int position) {
            Log.e(TAG,"onBindViewHolder");
            int viewType = getItemViewType(position);
            if (viewType == VIEW_TYPE_NOMAL) {//正常item需要绑定数据
                TextView textView=holder.getView(R.id.item_TV);
                textView.setText(list.get(position).toString());
            }
        }
    
        /**
         * 返回item的数量
         * 因为在原有数据数量基础上加了一个底部布局,所以总的item数量应该+1
         * @return
         */
        @Override
        public int getItemCount() {
            int count = list.size();
            count++;   //因为多了一个footerView,所有item的总数应该+1
            return count;
        }
    
        /**
         * 刷新数据
         * @param mList
         */
        public void notifyAllDatas(List<Integer> mList,MyRecyclerView recyclerView) {
            this.list = mList;
            recyclerView.post(new Runnable() {
                @Override
                public void run() {
                    notifyDataSetChanged();
                }
            });
        }
    }
    

    4.自定义一个RecyclerView

    public class MyRecyclerView extends RecyclerView{
        private static final String TAG="MyRecyclerView";
        private OnFooterAutoLoadMoreListener listener;//监听底部
        public static final int VIEW_TYPE_NOMAL = 0;//item的类型-正常的item
        public static final int VIEW_TYPE_FOOTER = 200;//item的类型-底部
    
        public MyRecyclerView(Context context) {
            this(context,null);
        }
    
        public MyRecyclerView(Context context, @Nullable AttributeSet attrs) {
            this(context, attrs,0);
        }
    
        public MyRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
            super(context, attrs, defStyle);
            addOnScrollListener(mOnScrollListener);//添加底部加载接口
        }
    
        /**
         * 滑动监听
         * 滑动到最后一个item的底部时加载更多信息
         */
        private OnScrollListener mOnScrollListener = new OnScrollListener() {
            @Override
            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                super.onScrollStateChanged(recyclerView, newState);
            }
    
            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                if(!canScrollVertically(1)){
                    if (listener != null) {
                            listener.loadMore();
                    }
                }
            }
        };
        /**
         * 添加底部加载接口
         * @param listener
         */
        public void addFooterAutoLoadMoreListener(OnFooterAutoLoadMoreListener listener){
            this.listener=listener;
        }
    }
    
    

    5.使用MyRecyclerView

    布局如下

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout 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="com.tinymonster.myrecyclerview.MainActivity">
    
        <android.support.v4.widget.SwipeRefreshLayout
            android:id="@+id/swipeRefreshLayout"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            >
    
            <com.tinymonster.myrecyclerview.MyRecyclerView
                android:id="@+id/myRecyclerView"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                />
        </android.support.v4.widget.SwipeRefreshLayout>
    
    </LinearLayout>
    
    

    代码如下

    package com.tinymonster.myrecyclerview;
    
    import android.content.Intent;
    import android.support.v4.widget.SwipeRefreshLayout;
    import android.support.v7.app.AppCompatActivity;
    import android.os.Bundle;
    import android.support.v7.widget.LinearLayoutManager;
    
    import java.util.ArrayList;
    import java.util.List;
    
    public class MainActivity extends AppCompatActivity implements OnFooterAutoLoadMoreListener{
        private SwipeRefreshLayout swipeRefreshLayout;
        private MyRecyclerView myRecyclerView;
        private List<Integer> dataList=new ArrayList<>();
        private MyAdapter myAdapter;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            swipeRefreshLayout = (SwipeRefreshLayout)findViewById(R.id.swipeRefreshLayout);
            myRecyclerView=(MyRecyclerView)findViewById(R.id.myRecyclerView);
            myRecyclerView.setLayoutManager(new LinearLayoutManager(this));
            myRecyclerView.addFooterAutoLoadMoreListener(this);
            myAdapter=new MyAdapter(dataList);
            myRecyclerView.setAdapter(myAdapter);
            loadMore();
            swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
                @Override
                public void onRefresh() {
                    myAdapter.notifyAllDatas(dataList,myRecyclerView);
                    dataList.clear();
                    dataList.addAll(Model.getData());
                    swipeRefreshLayout.setRefreshing(false);
                }
            });
        }
    
        @Override
        public void loadMore() {
            myAdapter.notifyAllDatas(dataList,myRecyclerView);
            List<Integer> list=Model.getData();
            dataList.addAll(list);
            swipeRefreshLayout.setRefreshing(false);
        }
    }
    

    代码已经上传到GitHub,如果你觉得有帮助,请帮忙点一个星星。


    image.png

    相关文章

      网友评论

        本文标题:安卓RecyclerView那些事-(二)实现一个下拉刷新、划到

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