• Introducing RecyclerView(二)


    文/poberWong(简书作者)
    原文链接:http://www.jianshu.com/p/7fdfea845937
    著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。

    正文:

    RecyclerView的优势:

    • 它自带ViewHolder来实现View的复用机制,再也不用ListView那样在getView()里自己写了
    • 使用LayoutManager可以实现ListView,GridView以及流式布局的列表效果
    • 通过setItemAnimator(ItemAnimator animator)可以实现增删动画(懒的话,可以使用默认的ItemAnimator对象,效果也不错)
    • 控制item的间隔,可以使用addItemDecoration(ItemDecoration decor),不过里边的ItemDecoration是一个抽象类,需要自己去实现...

    用法介绍:

    1. 导入RecyclerView的v7库:
      RecyclerView是一个android.support.v7库里的控件,因此在使用的时候我们需要在gradle配置文件里加上compile 'com.android.support:recyclerview-v7:22.2.1'来引入google官方的这个库
    2. xml布局中,使用常规的控件引入方式,来引入RecyclerView,如下:

       <android.support.v7.widget.RecyclerView
        android:id="@+id/recyclerview_content"
        style="?recyclerview_style"
        android:scrollbars="vertical"
        android:fadeScrollbars="true"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginBottom="-55dp" />
    3. 代码中的写法基本和ListView相差无几,但还是要重点说一下:

      1. 在实例化RecyclerView之后,我们需要使用setLayoutManager()给它设置布局管理器,其中的实参即就是LayoutManager,这里总共有两种LayoutManager:

        1. StaggeredGridLayoutManager,是我们之前提到的流式布局:
          它有一个构造方法StaggeredGridLayoutManager(int spanCount, int orientation),第一个是网格的列数,第二个参数是数据呈现的方向
          (如果是竖直,那么第一个参数的意义就是列数,反之为行数。而且第二个参数在StaggeredGridLayoutManager里也有同样名称的常量,请同学们自行采纳[这里还是建议大家使用库里自带的常量,因为他们一般都是整型值,这样可以避免各个类里所判别的常量值不一样而导致的其他问题]);
        2. GridLayoutManager,网格布局(流式布局应该是它的一个特殊情况):
          GridLayoutManager(Context context, int spanCount)
          GridLayoutManager(Context context, int spanCount, int orientation, boolean reverseLayout)需要说明的是,最后一个参数表示的是是否逆向布局(意思是将数据反向显示,原先从左向右,从上至下。设为true之后全部逆转)。
          小提示:在这两个LayoutManager中,默认的orientation为vertical,reverseLayout为false。对应的参数在GridLayoutManager中都有对应的方法来进行补充设置。而在StaggeredGridLayoutManager中所有的方法都针对reverseLayout做了判断,然而它并没有给出这个参数设定值的api。

        3. 线性布局, LinearLayoutManager
          LinearLayoutManager(Context context)构建一个默认布局方向为VERTICAL的RecyclerView,这种样式也是ListView默认的。
          LinearLayoutManager(Context context, int orientation, boolean reverseLayout)。发现没有,线性布局和网格布局几乎是一样的。只是少了一个spanCount参数。

      2. 和ListView一样,为RecyclerView设置Adapter

         class HomeAdapter extends RecyclerView.Adapter<HomeAdapter.MyViewHolder>
         {
        
         @Override
         public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
         {
             MyViewHolder holder = new MyViewHolder(LayoutInflater.from(
                     HomeActivity.this).inflate(R.layout.item_home, parent,
                     false));
             return holder;
         }
        
         @Override
         public void onBindViewHolder(MyViewHolder holder, int position)
         {
             holder.tv.setText(mDatas.get(position));
         }
        
         @Override
         public int getItemCount()
         {
             return mDatas.size();
         }
        
         class MyViewHolder extends ViewHolder
         {
             TextView tv;
        
             public MyViewHolder(View view)
             {
                 super(view);
                 tv = (TextView) view.findViewById(R.id.id_num);
             }
         }
        }

        如上,即就是Adapter的写法。其中的ViewHolder就是拿来负责View的回收和复用的,这样就不需要我们自己写完ViewHolder之后,还要在getView(int position, View convertView, ViewGroup parent)里一顿判断,一顿绑定,一顿find了。而且这里的ViewHolder成为了RecyclerView中必须继承的一部分,重写完后就需要放入 RecyclerView.Adapter< >这里来对基类的范型初始化。

      在这里,Recyclerview已经为你封装好了:

      getItemCount()就不必多说了,和ListView是一样的

      getItemViewType(int position)是用来根据position的不同来实现RecyclerView中对不同布局的要求。从这个方法中所返回的值会在onCreateViewHolder中用到。比如头部,尾部,等等的特殊itemView(这里说成ViewHolder比较好)都可以在这里进行判断。

      onCreateViewHolder(ViewGroup parent, int viewType)是用来配合写好的ViewHolder来返回一个ViewHolder对象。这里也涉及到了条目布局的加载。viewType则表示需要给当前position生成的是哪一种ViewHolder,这个参数也说明了RecyclerView对多类型ItemView的支持。

      onBindViewHolder(MyViewHolder holder, int position)专门用来绑定ViewHolder里的控件和数据源中position位置的数据。
      这里,会有人问,那么item的子控件findViewById 去哪儿了?我们把它交给了ViewHolder的构造方法(其他方法也可以),它的本质是在onCreateViewHolder方法里生成ViewHolder的时候执行的。

    提升:

    • 代码重构:
      在上边看了这么多,有木有觉得,ViewHolder的功能并不是非常明确?它既负责了子控件的查询,又负责了子控件的装载工作。而布局加载和数据绑定却交给了Adapter......
      我们来看看掘金的做法:
      首先,它把Adapter和ViewHolder的功能以一种较为低耦合的方式进行了职能分离,让ViewHolder里所有的逻辑代码全部都只出现在ViewHolder中。我们现在就对前边提到的代码进行重构:

      ViewHodler:

      class MyViewHolder extends ViewHolder{
         TextView tv;        
         public MyViewHolder(Context context, View view){
                super(view);
                tv = (TextView) view.findViewById(R.id.id_num);
      
                //当然,我们也可以在这里使用view来对RecyclerView的一个item进行事件监听,也可以使用  
                //tv等子控件来实现item的子控件的事件监听。这也是我之所以要传context的原因之一呢~
                ......
         }
      
         public static MyViewHolder newInstance(Activity context, ViewGroup parent){
                 View view = LayoutInflater.from(
                    context).inflate(R.layout.item_home, parent,false);
               return new MyViewHolder(context, view);
         }
        }    
      
        //你没看错,数据绑定也被整合进来了,  
        //将adapter里的数据根据position获取到后传进来。当然,也可以根据具体情况来做调整。
         public void onBinViewHolder(String data){     
             tv.setText(data);//既然这里也有子控件,那么这里也可以做item子控件的事件监听喽
      }

      RecyclerView

      class HomeAdapter extends RecyclerView.Adapter<MyViewHolder>{
        @Override
        public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType){//如需要,还要对viewType做判断
            return MyViewHolder.newInstance(this, parent)
        }
      
        @Override
        public void onBindViewHolder(MyViewHolder holder, int position){
            holder.onBindViewHolder(mDatas.get(position));
        }
      
        @Override
        public int getItemCount(){
            return mDatas.size();
        }
      }
    • 抽取一个条目点击事件,让它更像ListView:

      class HomeAdapter extends RecyclerView.Adapter<MyViewHolder>{
        private OnItemClickListener mOnItemClickListener;
      
        public void setOnItemClickLitener(OnItemClickLitener mOnItemClickLitener)  
        {  
            this.mOnItemClickLitener = mOnItemClickLitener;  
        } 
      
        @Override
        public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType){//如需要,还要对viewType做判断
            return MyViewHolder.newInstance(this, parent)
        }
      
        @Override
        public void onBindViewHolder(MyViewHolder holder, int position){
            holder.onBindViewHolder(mDatas.get(position));
      
            //如果设置了回调,则设置点击事件  
            if (mOnItemClickLitener != null) {  
                viewHolder.itemView.setOnClickListener(new OnClickListener() {  
                    @Override  
                    public void onClick(View v) {  
                       mOnItemClickLitener.onItemClick(viewHolder.itemView, i);  
                    }  
                });  
            }  
        }
      
        @Override
        public int getItemCount(){
            return mDatas.size();
        }
      
        public interface OnItemClickLitener {  
            void onItemClick(View view, int position);  
        } 
      }

      接口调用

        mAdapter.setOnItemClickLitener(new OnItemClickLitener() {  
        @Override  
        public void onItemClick(View view, int position) { 
            ......
        }  
      });

    External

    上边提到了

    控制item的间隔,可以使用addItemDecoration(ItemDecoration decor),不过里边的ItemDecoration是一个抽象类,需要自己去实现...

    这个问题,那我们来实际解决一下:

    • 这是羊神实现的一个子类
    package com.zhy.sample.demo_recyclerview;
    
    /*
     * Copyright (C) 2014 The Android Open Source Project
     *
     * Licensed under the Apache License, Version 2.0 (the "License");
     * limitations under the License.
     */
    
    import android.content.Context;
    import android.content.res.TypedArray;
    import android.graphics.Canvas;
    import android.graphics.Rect;
    import android.graphics.drawable.Drawable;
    import android.support.v7.widget.LinearLayoutManager;
    import android.support.v7.widget.RecyclerView;
    import android.support.v7.widget.RecyclerView.State;
    import android.util.Log;
    import android.view.View;
    
    
    /**
     * This class is from the v7 samples of the Android SDK. It's not by me!
     * <p/>
     * See the license above for details.
     */
    public class DividerItemDecoration extends RecyclerView.ItemDecoration {
    
        private static final int[] ATTRS = new int[]{
                android.R.attr.listDivider
        };
    
        public static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL;
    
        public static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL;
    
        private Drawable mDivider;
    
        private int mOrientation;
    
        public DividerItemDecoration(Context context, int orientation) {
            final TypedArray a = context.obtainStyledAttributes(ATTRS);
            mDivider = a.getDrawable(0);
            a.recycle();
            setOrientation(orientation);
        }
    
        public void setOrientation(int orientation) {
            if (orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST) {
                throw new IllegalArgumentException("invalid orientation");
            }
            mOrientation = orientation;
        }
    
        @Override
        public void onDraw(Canvas c, RecyclerView parent) {
            Log.v("recyclerview - itemdecoration", "onDraw()");
    
            if (mOrientation == VERTICAL_LIST) {
                drawVertical(c, parent);
            } else {
                drawHorizontal(c, parent);
            }
    
        }
    
    
        public void drawVertical(Canvas c, RecyclerView parent) {
            final int left = parent.getPaddingLeft();
            final int right = parent.getWidth() - parent.getPaddingRight();
    
            final int childCount = parent.getChildCount();
            for (int i = 0; i < childCount; i++) {
                final View child = parent.getChildAt(i);
                android.support.v7.widget.RecyclerView v = new android.support.v7.widget.RecyclerView(parent.getContext());
                final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
                        .getLayoutParams();
                final int top = child.getBottom() + params.bottomMargin;
                final int bottom = top + mDivider.getIntrinsicHeight();
                mDivider.setBounds(left, top, right, bottom);
                mDivider.draw(c);
            }
        }
    
        public void drawHorizontal(Canvas c, RecyclerView parent) {
            final int top = parent.getPaddingTop();
            final int bottom = parent.getHeight() - parent.getPaddingBottom();
    
            final int childCount = parent.getChildCount();
            for (int i = 0; i < childCount; i++) {
                final View child = parent.getChildAt(i);
                final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
                        .getLayoutParams();
                final int left = child.getRight() + params.rightMargin;
                final int right = left + mDivider.getIntrinsicHeight();
                mDivider.setBounds(left, top, right, bottom);
                mDivider.draw(c);
            }
        }
    
        @Override
        public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) {
            if (mOrientation == VERTICAL_LIST) {
                outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
            } else {
                outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
            }
        }
    }
    • 然后就是为我们的RecyclerView实例添加分割线

      addItemDecoration(new DividerItemDecoration(this,
      DividerItemDecoration.VERTICAL_LIST));
    • 系统默认的分割线往往达不到我们产品和设计师的要求,怎么办呢?

      <item name="android:listDivider">@drawable/your_custom_divider</item>

    通过以上xml属性,将系统的listDivider设为自己画出来的分割线,只需要放在你对应activity的主题下即可。
    (不会画的,请自行google,关键字:android shape

  • 相关阅读:
    flume和kafka整合(转)
    Flume目录
    Flume的安装配置
    HBase系统架构及数据结构(转)
    toolbar ,textfield,图片拉伸,Bundle
    poj 1017 Packets
    jQuery使用serialize(),serializeArray()方法取得表单数据+字符串和对象类型两种表单提交的方法
    Android数据加密概述及多种加密方式 聊天记录及账户加密 提供高质量的数据保护
    LintCode-落单的数 III
    LeetCode90:Subsets II
  • 原文地址:https://www.cnblogs.com/krislight1105/p/5257101.html
Copyright © 2020-2023  润新知