美文网首页
RecyclerView 的基本使用

RecyclerView 的基本使用

作者: 大豆油 | 来源:发表于2017-01-22 00:15 被阅读276次

    RecyclerView 的基本使用

    2017年1月21日

    为了加强理解 RecyclerView 的使用方法,同时让自己的写作能力得到锻炼,所以实践一把,都是一些基本的使用,主要分析下拉刷新与上拉加载部分,细节很重要,以此来激励自己写出高质量的文章。

    • 基本用法
      • 基本介绍
      • 代码实现
    • SwipeRefreshLayout 配合实现下拉刷新
      • 基本介绍
      • 代码实现
    • RecyclerView 实现上拉加载
      • 滚动事件的分析
      • 添加 FooterView 实现上拉加载
    • 参考文章及总结

    效果图

    基本使用

    RecyclerView 的基本介绍

    RecyclerView 是谷歌V7包下新增的控件,用来替代 ListView 的使用,在 RecyclerView 标准化了 ViewHolder 类似于 ListView中 convertView 用来做视图缓存.

    相信大家对于 ListView 都很熟悉,在我刚学 Android 开发的时候接触就是这个强大的控件,也被称为最难的控件之一。自从 RecyclerView 已经面市很久,也在很多应用中得到广泛的使用,在整个开发者圈子里面也拥有很不错的口碑,那说明 RecyclerView 拥有比 ListView,GridView 之类控件有很多的优点。

    • 设置布局管理器以控制Item的布局方式,横向、竖向以及瀑布流方式
    • 可设置Item操作的动画(删除或者添加等)
    • 可设置Item的间隔样式(可绘制)

    但是对于点击事件需要自己写回调借口实现,下面会详细介绍,并且没有了 ListView 强大的 addFootView() 的方法,需要自己去实现,不过还好拓展性很强。

    代码实现

    1.添加库依赖:首先要用这个控件,你需要在gradle文件中添加包的引用(配合官方CardView使用)

    compile 'com.android.support:recyclerview-v7:25.0.0'
    compile 'com.android.support:cardview-v7:25.0.0'
    

    2.然后在 xml 文件里实现

    <android.support.v7.widget.RecyclerView
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/recycler_view"
        android:layout_centerVertical="true"
        android:layout_centerHorizontal="true"/>
    

    3.接着就是在 Activity 里面设置

    public class MainActivity extends AppCompatActivity {
    
        private RecyclerView mRecyclerView;
        private RecyclerAdapter mAdapter;
        private LinearLayoutManager mLayoutManager;
        private List<ShareBean> mData = new ArrayList<>();
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            initData();//初始化数据
            initView();//初始化布局
            setListener();//设置监听事件
        }
    
        private void initData() {
            for (int i = 0; i < 4; i++) {
                mData.add(new ShareBean(R.mipmap.ic_image, "Android vs IOS"));
            }
        }
    
        private void initView() {
            mRecyclerView = (RecyclerView) findViewById(R.id.recycler);
            //设置布局管理器
            mLayoutManager = new LinearLayoutManager(this);
            mRecyclerView.setLayoutManager(mLayoutManager);
            //确保尺寸是一个常数,避免计算每个item的size
            mRecyclerView.setHasFixedSize(true);
            //设置显示动画
            mRecyclerView.setItemAnimator(new DefaultItemAnimator());
            mAdapter = new RecyclerAdapter(this, mData);
            mRecyclerView.setAdapter(mAdapter);
        }
    
        private void setListener() {
            //设置Items的点击事件
            mAdapter.setOnItemClickListener(new OnItemClickListener() {
                @Override
                public void onItemClick(final View view, int position) {
                    onClick(view, position);
                }
            });
          }
    }
    

    4.适配器 Adapter 的代码

    public class RecyclerAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    
        public  OnItemClickListener itemClickListener;
        private List<ShareBean> mData = null;
        private LayoutInflater mInflater;
    
        public RecyclerAdapter(Context context, List<ShareBean> mData) {
            this.mData = mData;
            this.mInflater = LayoutInflater.from(context);
        }
    
        //将布局转化为 View 并传递给 RecyclerView 封装好的 ViewHolder
        @Override
        public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType){    
            // 实例化展示的view
            View view = mInflater.inflate(R.layout.item_recyclerview, parent, false);
            // 实例化viewholder
            return new ItemViewHolder(view);
        }
    
        //将数据与视图进行绑定
        @Override
        public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
            ((ItemViewHolder) holder).mImageView.setImageResource(mData.get(position).getImage_id());
            ((ItemViewHolder) holder).mTextView.setText(mData.get(position).getName());
        }
    
        //返回 Item 的数量
        @Override
        public int getItemCount() {
            return mData == null ? 0 : mData.size();;
        }
    
        public  class ItemViewHolder extends RecyclerView.ViewHolder {
    
            CardView mCardView;
            CircleImageView mImageView;
            TextView mTextView;
    
            public ItemViewHolder(View itemView) {
                super(itemView);
                mTextView = (TextView) itemView.findViewById(R.id.textView);
                mCardView = (CardView) itemView.findViewById(R.id.cardView);
                mImageView = (CircleImageView) itemView.findViewById(R.id.image);
                mCardView.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View view) {
                        if (itemClickListener != null) {
                            itemClickListener.onItemClick(view, getPosition());
                        }
                    }
                });
            }
        }
    
        //以下为点击事件的接口回调部分
        public void setOnItemClickListener(OnItemClickListener itemClickListener) {
            this.itemClickListener = itemClickListener;
        }
    
        public interface OnItemClickListener {
            void onItemClick(View view, int position);
        }
    }
    

    以上是适配器基本的写法,这个自定义 Adapter 和我们在使用 Listview 时候的 Adapter 相比还是有点不太一样的,首先这边我们需要继承 RecyclerView.Adaper 类,然后实现两个重要的方法 onBindViewHodler() 以及 onCreateViewHolder() ,这边我们看出来区别,使用 RecyclerView 控件我们就可以把 ItemView 视图创建和数据绑定这两步进行分来进行管理,用法就更加方便而且灵活了,并且我们可以定制打造千变万化的布局。同时这边我们还需要创建一个 ViewHolder 类,该类必须继承自 RecyclerView.ViewHolder 类,现在 Google 也要求我们必须要实现 ViewHolder 来承载 Item 的视图。

    特别注意一下最下面的点击事件的回调函数,主要是对接口的熟悉程度,Java 是硬伤 55555.

    同时 RecyclerView 有三种实现方式(上面介绍有),通过一下代码设置

    mRecyclerView.setLayoutManager(new LinearLayoutManager(this));//这里用线性显示 类似于listview
    mRecyclerView.setLayoutManager(new GridLayoutManager(this, 2));//这里用线性宫格显示 类似于grid view
    mRecyclerView.setLayoutManager(new StaggeredGridLayoutManager(2, OrientationHelper.VERTICAL));//这里用线性宫格显示 类似于瀑布流
    
    

    SwipeRefreshLayout 配合实现下拉刷新

    基本介绍

    SwipeRefrshLayout 是 Google 官方更新的一个 Widget ,可以实现下拉刷新的效果。该控件集成自 ViewGroup 在 support-v4 兼容包下,不过我们需要升级 supportlibrary 的版本到19.1以上。基本使用的方法如下:

    • setOnRefreshListener(OnRefreshListener):添加下拉刷新监听器
    • setRefreshing(boolean):显示或者隐藏刷新进度条
    • isRefreshing():检查是否处于刷新状态
    • setColorSchemeResources():设置进度条的颜色主题,最多设置四种,以前的setColorScheme()方法已经弃用了
    代码实现

    1.首先先看一下 xml 布局,在 RecyclerView 布局外部嵌套一层 SwipeRefreshLayout 布局即可。

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
                  android:layout_width="match_parent"
                  android:layout_height="match_parent"
                  android:orientation="vertical">
    
        <android.support.v4.widget.SwipeRefreshLayout
            android:id="@+id/swipe_layout"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
    
            <android.support.v7.widget.RecyclerView
                android:id="@+id/recycler"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:visibility="visible"/>
    
        </android.support.v4.widget.SwipeRefreshLayout>
    </LinearLayout>
    
    

    2.在 Activity 中获取 SwipeRefreshLayout 控件并且设置 OnRefreshListener 监听器,同时实现里边的 onRefresh() 方法,在该方法中进行网络请求最新数据,然后刷新 RecyclerView 列表同时设置 SwipeRefreshLayout 的进度Bar的隐藏或者显示效果。具体代码如下:

    mSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
              @Override
              public void onRefresh() {
                  addTopData();
              }
          });
    
      private void addTopData() {
               new Handler().postDelayed(new Runnable() {
                   @Override
                   public void run() {
                       for (int i = 0; i < 5; i++) {
                           mData.add(i, new ShareBean(R.mipmap.ic_image_h, "下拉刷新数据" + i));
                       }
                       mAdapter.notifyDataSetChanged();
                       mSwipeRefreshLayout.setRefreshing(false);
                   }
               }, 1000);
           }
    

    RecyclerView 实现上拉加载

    滚动事件的分析

    1.RecyclerView 本身已经提供了滑动的监听接口,OnScrollListener,这个接口包含了以下的方法,代码注释介绍。

    mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
          //当recycleView的滑动状态改变时回调
           @Override
           public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
               super.onScrollStateChanged(recyclerView, newState);
           }
    
          //当RecycleView滑动之后被回调
           @Override
           public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
               super.onScrolled(recyclerView, dx, dy);
          }
    });
    

    2.RecyclerView 的滑动状态

    • 当前的recycleView不滑动(滑动已经停止时):
      public static final int SCROLL_STATE_IDLE = 0;

    • 当前的recycleView被拖动滑动:
      public static final int SCROLL_STATE_DRAGGING = 1;

    • 当前的recycleView在滚动到某个位置的动画过程,但没有被触摸滚动.调用 scrollToPosition(int) 应该会触发这个状态:
      public static final int SCROLL_STATE_SETTLING = 2;

    3.滑动位置的监听
    3.1 以下是最简单的顶部/底部的判断方式:

    mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
          //当recycleView的滑动状态改变时回调
           @Override
           public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
               super.onScrollStateChanged(recyclerView, newState);
    
              //获取最后一个可见view的位置
              int lastItemPosition = linearManager.findLastVisibleItemPosition();
              //获取第一个可见view的位置
              int firstItemPosition =linearManager.findFirstVisibleItemPosition();
    
              if (newState == RecyclerView.SCROLL_STATE_IDLE && lastItemPosition + 1 == adapter.getItemCount()) {
              //最后一个itemView的position为adapter中最后一个数据时,说明该itemView就是底部的view了
              //需要注意position从0开始索引,adapter.getItemCount()是数据量总数
              }
    
              //同理检测是否为顶部itemView时,只需要判断其位置是否为0即可
              if (newState == RecyclerView.SCROLL_STATE_IDLE && firstItemPosition == 0) {}
           }
    });
    

    以上的实现方式存在诸多问题,比如:

    • itemView会在滑动过程中只显示一部分或者一半
    • 检测数据不够时的RecycleView

    3.2 对于显示不全的问题
    也就是说:当某一个itemView只显示一部分的时候,此时已经算是一个position了,此时很可能去触发判断条件。所以官方 Api 里有一下两个方法:

    • findFirstCompletlyVisibleItemPosition()
    • findLastCompletlyVisibleItemPosition()

    就可以尽可能的避免这种问题。

    3.3 检测数据不够时的状态

    如果为了避免这种情况,我们就需要对在检测时多进行一步,若当前的RecycleView显示的itemView不满屏的情况下,其实并不存在滑动的说法(没有加载更多,因为根本数据还没有满屏显示),至于下拉刷新的,还是可以的,但一般都会使用SwipeRefreshLayout实现下拉刷新了.因此这里主要考虑的是关于滑动到底部加载更多的问题.

    3.4 上拉加载更多代码实现

    mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
    
        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            super.onScrolled(recyclerView, dx, dy);
            if (dy > 0) {//向下滚动
                int visibleItemCount = mLayoutManager.getChildCount();
                int totalItemCount = mLayoutManager.getItemCount();
                int pastVisiblesItems = mLayoutManager.findFirstVisibleItemPosition();
    
                if (!loading && (visibleItemCount + pastVisiblesItems) == totalItemCount) {
                    loading = true;
                    new Handler().postDelayed(new Runnable() {
                        @Override
                        public void run() {
                            for (int i = 0; i < 5; i++) {
                                mData.add(new ShareBean(R.mipmap.ic_image_s, "上拉加载数据" + i));
                            }
                            mAdapter.notifyDataSetChanged();
                            loading = false;
                        }
                    }, 3000);
                }
            }
        }
    });
    
    添加 FooterView 实现上拉加载

    接下来完善UI,让用户知道确实在上拉加载的过程。用RecyclerView实现,使用 getItemType() 方法,就与 ListView 差不多了。不说多了,直接上完整代码(注释很清晰):

    public class RecyclerAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    
        public  OnItemClickListener itemClickListener;
        private List<ShareBean> mData = null;
        private LayoutInflater mInflater;
    
        //1.加入布局状态标志-用来判断此时加载是普通Item还是footView
        //没有更多了
        public static final int NO_DATA_MORE = 0;
        //上拉加载更多
        public static final int PULLUP_LOAD_MORE = 1;
        //正在加载中
        public static final int LOADING_MORE = 2;
    
        private int load_more_status = 0;
    
        private static final int TYPE_ITEM = 0;  //普通Item View
        private static final int TYPE_FOOTER = 1;  //顶部FootView
    
        public RecyclerAdapter(Context context, List<ShareBean> mData) {
            this.mData = mData;
            this.mInflater = LayoutInflater.from(context);
        }
    
        //4.接着onCreateViewHolder(ViewGroup parent,int viewType)加载布局的时候根据viewType的类型来选择指定的布局创建,返回即可:
        @Override
        public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    //        将布局转化为View并传递给Recycler封装好的ViewHolder
            if (viewType == TYPE_ITEM) {
                View view = mInflater.inflate(R.layout.item_recyclerview, parent, false);
                return new ItemViewHolder(view);
            } else if (viewType == TYPE_FOOTER) {
                View foot_view = mInflater.inflate(R.layout.part_footer, parent, false);
                return new BottomViewHolder(foot_view);
            }
            return null;
        }
    
        //5.最后进行判断数据的时候(onBindViewHolder),判断holder的类型来进行判定数据即可.
        @Override
        public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
            if (holder instanceof ItemViewHolder) {
                ((ItemViewHolder) holder).mImageView.setImageResource(mData.get(position).getImage_id());
                ((ItemViewHolder) holder).mTextView.setText(mData.get(position).getName());
            } else if (holder instanceof BottomViewHolder) {
                BottomViewHolder footViewHolder = (BottomViewHolder) holder;
                switch (load_more_status) {
                    case PULLUP_LOAD_MORE:
                        footViewHolder.mFooterTv.setText("上拉加载更多...");
                        footViewHolder.mProgressBar.setVisibility(View.VISIBLE);
                        break;
                    case LOADING_MORE:
                        footViewHolder.mFooterTv.setText("正在加载中...");
                        footViewHolder.mProgressBar.setVisibility(View.VISIBLE);
                        break;
                    case NO_DATA_MORE:
                        footViewHolder.mFooterTv.setText("----我是有底线的----");
                        footViewHolder.mProgressBar.setVisibility(View.GONE);
                        break;
                }
            }
        }
    
        //3.重写getItemViewType方法来判断返回加载的布局的类型
        @Override
        public int getItemViewType(int position) {
            if (position + 1 == getItemCount()) {
                return TYPE_FOOTER;
            } else {
                return TYPE_ITEM;
            }
        }
    
        //2.返回item数量
        @Override
        public int getItemCount() {
            return mData.size() + 1;
        }
    
        /**
         * //上拉加载更多
         * PULLUP_LOAD_MORE=0;
         * //正在加载中
         * LOADING_MORE=1;
         * //加载完成已经没有更多数据了
         * NO_MORE_DATA=2;
         *
         * @param status
         */
        public void changeMoreStatus(int status) {
            load_more_status = status;
            notifyDataSetChanged();
        }
    
        public  class ItemViewHolder extends RecyclerView.ViewHolder {
    
            CardView mCardView;
            CircleImageView mImageView;
            TextView mTextView;
    
            public ItemViewHolder(View itemView) {
                super(itemView);
                mTextView = (TextView) itemView.findViewById(R.id.textView);
                mCardView = (CardView) itemView.findViewById(R.id.cardView);
                mImageView = (CircleImageView) itemView.findViewById(R.id.image);
                mCardView.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View view) {
                        if (itemClickListener != null) {
                            itemClickListener.onItemClick(view, getPosition());
                        }
                    }
                });
            }
        }
    
         /**
         * 底部FootView布局
         */
        public class BottomViewHolder extends RecyclerView.ViewHolder {
    
            ProgressBar mProgressBar;
            TextView mFooterTv;
    
            public BottomViewHolder(View itemView) {
                super(itemView);
                mProgressBar = (ProgressBar) itemView.findViewById(R.id.progress_bar);
                mFooterTv = (TextView) itemView.findViewById(R.id.footer_tv);
            }
        }
    
        public void setOnItemClickListener(OnItemClickListener itemClickListener) {
            this.itemClickListener = itemClickListener;
        }
    
    }
    

    终于分析完了,感觉滑动需要继续探究,,,嗯嗯,先就这样吧。


    参考文章及总结

    以上说对基础知识的一些梳理,存在诸多不足,请多指正。

    主要参考文章:

    相关文章

      网友评论

          本文标题:RecyclerView 的基本使用

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