Fwl的小花园

  博客园 :: 首页 :: 博问 :: 闪存 :: :: 联系 :: :: 管理 ::

众所周知,RecyclerView是Google公司推出的V7包中的一个重要的控件,非常方便,可以替代现有的ListView和Gridview等控件,它功能很强大,灵活性好,扩展性强,还自带VIewHolder,不再需要自己去写,这点非常方便,缺点也是有的,那就是比较原始,因为灵活性好,所以封装的比较浅,原汁原味,所以它不具备点击事件和长按事件等。

 

下面先来看看Recycler的简单的使用:

直接上代码:

public class MainActivity extends AppCompatActivity {

    private RecyclerView rv;
    private List<ItemEntity> rvList;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        rvList = initRvList();
        rv = (RecyclerView) findViewById(R.id.rv);
        //默认的设置一个纵向的LinearLayoutManage,这一步,不能忘,很重要
        rv.setLayoutManager(new LinearLayoutManager(this));
        rv.setAdapter(new RecyclerViewAdapter(this, rvList));
    }

    public class ItemEntity {
        private int id;
        private String name;

        public ItemEntity(int ids, String names) {
            this.id = ids;
            this.name = names;
        }
        public int getId() {
            return id;
        }
        public void setId(int id) {
            this.id = id;
        }
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
    }

    private List<ItemEntity> initRvList() {
        List<ItemEntity> ll = new ArrayList<ItemEntity>();
        ll.add(new ItemEntity(1, "张三"));
        ll.add(new ItemEntity(2, "李四"));
        ll.add(new ItemEntity(3, "王五"));
        ll.add(new ItemEntity(4, "赵六"));
        ll.add(new ItemEntity(5, "钱七"));
        ll.add(new ItemEntity(6, "孙八"));
        ll.add(new ItemEntity(7, "吴九"));
        ll.add(new ItemEntity(8, "周十"));
        return ll;
    }

    public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.RecyclerViewViewHolder> {

        private Context mContext;
        private List<ItemEntity> mList;

        //构造方法,提供context上下文和RexyclerView的数据源
        public RecyclerViewAdapter(Context context, List<ItemEntity> list) {
            this.mContext = context;
            this.mList = list;
        }

        @Override
        public RecyclerViewAdapter.RecyclerViewViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            //通过一个itemView创建一个ViewHolder
            View itemViews = getLayoutInflater().inflate(R.layout.item_recyclerview,parent, false);
            return new RecyclerViewViewHolder(itemViews);
        }

        @Override
        public void onBindViewHolder(RecyclerViewViewHolder holder, int position) {
            //这里为item项的每个一个控件赋值
            holder.id.setText(String.valueOf(mList.get(position).getId()));
            holder.name.setText(mList.get(position).getName());
        }

        @Override
        public int getItemCount() {
            //返回值item的总数
            return mList == null ? 0 : mList.size();
        }

        public class RecyclerViewViewHolder extends RecyclerView.ViewHolder {

            public TextView id;
            public TextView name;

            public RecyclerViewViewHolder(View itemView) {
                super(itemView);
                id = (TextView) itemView.findViewById(R.id.id);
                name = (TextView) itemView.findViewById(R.id.name);
            }
        }
    }
}

这里附上itemView的布局文件的代码:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <TextView
        android:id="@+id/id"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:textColor="#f80"
        android:textSize="13sp" />

    <TextView
        android:id="@+id/name"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="3dp"
        android:gravity="center"
        android:textColor="#08f"
        android:textSize="16sp" />

</LinearLayout>

整个示例就这样了,写的比较简单,仅供各位参考。

温馨提醒一句,因为之前习惯使用ListView,所以,刚开始使用RecyclerView的时候总是忘了设置LayoutManage,这样出来的结果是一片空白,一脸懵逼,自此,每次都要去检查一下有没有LayoutManage。哈哈。

上面的示例是一个纵向的线性的LayoutManage,如果要搞成横向的或者Grid的直接设置另外一个LayoutManage就可以了,还可以很方便的实现瀑布流布局。

     //设置一个LinearLayoutManager
        rv.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false));
        //设置一个GridLayoutManager
        rv.setLayoutManager(new GridLayoutManager(this, 3));
        //设置一个纵向的,有3列的瀑布流的布局
        rv.setLayoutManager(new StaggeredGridLayoutManager(3,StaggeredGridLayoutManager.VERTICAL));

 

最简单的示例先写到这里,后续更新。哈哈。2016-12-28  14:56

 

示例看完,仔细的看一下代码,其实也很繁琐的,而且实际使用中还有诸多的不方便,例如没有getItem()等方法。所以,我们对这进行做一些封装。使得我们使用的时候更加方便,也让代码变得更加简洁。

public abstract class BaseAdapter<T> extends RecyclerView.Adapter<BaseViewHolder<T>> {

    private List<T> list;
    private Context mContext;

    public BaseAdapter(List<T> list, Context mContext) {
        this.list = list;
        this.mContext = mContext;
    }

    @Override
    public BaseViewHolder<T> onCreateViewHolder(ViewGroup parent, int viewType) {
        LayoutInflater layoutInflater = (LayoutInflater)
                mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        return onViewHolderCreate(layoutInflater, parent, viewType);
    }

    @Override
    public void onBindViewHolder(BaseViewHolder<T> holder, int position) {
        holder.onBind(list.get(position), position);
    }

    @Override
    public int getItemCount() {
        return list == null ? 0 : list.size();
    }

    public void setList(List<T> list) {
        this.list = list;
    }

    public T getItem(int pos) {
        return list.get(pos);
    }

    abstract BaseViewHolder<T> onViewHolderCreate(LayoutInflater layoutInflater, ViewGroup parent, int viewType);
}

上面就是Adapter的封装。下面来看看ViewHolder的封装。

public abstract class BaseViewHolder<T> extends RecyclerView.ViewHolder {

    public BaseViewHolder(View itemView) {
        super(itemView);
    }

    public abstract void onBind(T t,int position);
}

最后来看看是怎么使用我们已经封装好的Adapter和ViewHolder。

  public class Adapters extends BaseAdapter<ItemEntity> {

        public Adapters(List<ItemEntity> list, Context mContext) {
            super(list, mContext);
        }

        @Override
        BaseViewHolder<ItemEntity> onViewHolderCreate(LayoutInflater layoutInflater, ViewGroup parent, int viewType) {
            View view = layoutInflater.inflate(R.layout.item_recyclerview, parent, false);
            return new ViewHolders(view);
        }
    }

    public class ViewHolders extends BaseViewHolder<ItemEntity> {

        private TextView id;
        private TextView name;

        public ViewHolders(View itemView) {
            super(itemView);
            id = (TextView) itemView.findViewById(R.id.id);
            name = (TextView) itemView.findViewById(R.id.name);
        }

        @Override
        public void onBind(ItemEntity itemEntity, int position) {
            id.setText(String.valueOf(itemEntity.getId()));
            name.setText(itemEntity.getName());
        }
    }

 现在来看看我们封装的Adapter和VIewHolder,先来看简单的ViewHolder,ViewHolder中很简单,只有一个构造方法和一个抽象的onBind() 这有啥作用呢?其实很简单,看最后使用的时候,使用的时候,onBind() 可以直接写itemView的控件的赋值就可以了,简单明了。再来看看Adapter,Adapter也很简单,第一个是构造方法,中间三个是复写父类的,接下来的两个是自己加的,方便以后Adapter里面操作一些和业务相关的一些东西,别问为什么,只是这两个方法在以后的日子里,用上的概率会比较大。现在来看看onCreateViewHolder() 先不要看封装的,先看看最前面的那个小示例,示例里面的onCreateViewHolder里面做的是直接返回一个ViewHolder对象,而new一个ViewHolder需要一个itemView,而itenView需要inflater一个Layout来得到一个View,这,明显不对,为啥呢?因为我们这是封装,而不是实际的使用,实际使用的时候才涉及到这一块,所以,对于未知的东西,我们可以写一个抽象的方法来返回,而inflater一个view需要的一些东西我们可以事先做好,例如获取一个inflater就可以在这里做,然后直接在使用的时候调用就可以了,所以这里直接调用一个抽象的方法来获取ViewHolder.再来看看OnBindViewHolder(),这里我们直接调用ViewHolder的那个抽象的onBind(),看看最前面的那个小示例,示例里面,我们做的是给itemView的每一个控件赋值,现在这样封装,可以将Adapter里面做的操作转到ViewHolder,这样分工更加明确,看看最后的使用就知道了,Adapter里面只有几行代码,只需要在onViewHolder()方法里面操作即可,最后看看ViewHolder,所有涉及到的控件的findView,赋值都在ViewHolder里面进行。这样分工就明确了,代码也一眼看懂。里面要给itemView的每一个控件添加点击事件也很方便,如果要给整个的ItemView添加点击事件可以直接使用RecyclerView的ViewHolder里面的itemView既可(itemView.setOnClickListener)。

 

有关于Adapter和ViewHolder的封装写到这里。后续更新,2016-12-28 18:01

 

RecyclerView.ItemDecoration

RecyclerView 是不带分割线的,所以分割线要自己写,而分割线实现起来也是比较简单的,最傻瓜式的就是在布局文件中预留出来。然后再最后一个的时候隐藏掉,就这个简单,但是,如果遇到比较复杂的就难搞了,像gridview一样,要考虑横竖的情况就比较麻烦了。但不用怕,官方有一个现成的抽象类,你只需要继承这个类,实现相应的方法即可。哈哈,那就是ItemDecoration。ItemDecoration中有三个重要的方法,onDraw,onDrawOver,getItemOffsets,其中,onDraw和onDrawOver只需要实现一个即可,看名字也知道啥意思了,一个是在draw item 之前调用,一个是在之后调用,所以只需要实现一个即可,一般实现onDraw比较多。getItemOffsets,这个方法其实就是计算分割线的偏移量。说白了就是执行分割线的大小。

  1 public class LinearManageItemDecoracition extends RecyclerView.ItemDecoration {
  2 
  3     public int mOrientation = LinearLayoutManager.VERTICAL;
  4     public Drawable dividerLineHorizontal = null;
  5     public Drawable dividerLineVertical = null;
  6     public ColorDrawable cd;
  7     private Paint paint;
  8 
  9 
 10     public LinearManageItemDecoracition(Context context, int orientation) {
 11         if (orientation == LinearLayoutManager.VERTICAL || orientation == LinearLayoutManager.HORIZONTAL)
 12             mOrientation = orientation;
 13         else
 14             throw new RuntimeException("orientation is Incorrect");
 15         dividerLineHorizontal = context.getResources().getDrawable(R.drawable.divider_drawable_horizontal);
 16         dividerLineVertical = context.getResources().getDrawable(R.drawable.divider_drawable_vertical);
 17         //纯色可以不用Drawable,直接用颜色
 18         cd = new ColorDrawable(Color.parseColor("#FF8800"));
 19         paint = new Paint();
 20         paint.setColor(Color.BLUE);
 21     }
 22 
 23     @Override
 24     public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
 25         super.onDraw(c, parent, state);
 26 //        drawMethod1(c, parent, state);
 27 //        drawMethod2(c, parent, state);
 28         drawMethod3(c, parent, state);
 29     }
 30 
 31     @Override
 32     public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
 33         super.onDrawOver(c, parent, state);
 34     }
 35 
 36     @Override
 37     public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
 38         super.getItemOffsets(outRect, view, parent, state);
 39         outRect.left = 0;
 40         outRect.top = 0;
 41         if (mOrientation == LinearLayoutManager.VERTICAL) {
 42             outRect.right = 0;
 43             if (dividerLineHorizontal != null) {
 44                 outRect.bottom = dividerLineHorizontal.getIntrinsicHeight();
 45             } else {
 46                 outRect.bottom = 5;
 47             }
 48         } else if (mOrientation == LinearLayoutManager.HORIZONTAL) {
 49             outRect.bottom = 0;
 50             if (dividerLineVertical != null) {
 51                 outRect.right = dividerLineVertical.getIntrinsicWidth();
 52             } else {
 53                 outRect.right = 5;
 54             }
 55         }
 56     }
 57 
 58 
 59     public void drawMethod1(Canvas c, RecyclerView parent, RecyclerView.State state) {
 60         int childCount = parent.getChildCount();
 61         if (mOrientation == LinearLayoutManager.VERTICAL) {
 62             for (int pos = 0; pos < childCount - 1; pos++) {
 63                 View view = parent.getChildAt(pos);
 64                 RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) view.getLayoutParams();
 65                 int left = view.getLeft() - layoutParams.leftMargin;
 66                 int right = view.getRight() + layoutParams.rightMargin;
 67                 int top = view.getBottom() + layoutParams.bottomMargin;
 68                 int bottom = top + dividerLineHorizontal.getIntrinsicHeight();
 69                 dividerLineHorizontal.setBounds(left, top, right, bottom);
 70                 dividerLineHorizontal.draw(c);
 71             }
 72 
 73         } else if (mOrientation == LinearLayoutManager.HORIZONTAL) {
 74             int top = parent.getPaddingTop();
 75             int bottom = parent.getHeight() - parent.getPaddingBottom();
 76             for (int i = 0; i < childCount; i++) {
 77                 View child = parent.getChildAt(i);
 78                 RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
 79                 int left = child.getRight() + params.rightMargin;
 80                 int right = left + dividerLineVertical.getIntrinsicWidth();
 81                 dividerLineVertical.setBounds(left, top, right, bottom);
 82                 dividerLineVertical.draw(c);
 83             }
 84         }
 85     }
 86 
 87     public void drawMethod2(Canvas c, RecyclerView parent, RecyclerView.State state) {
 88         int childCount = parent.getChildCount();
 89         int dp1 = dpToPx(1);
 90         if (mOrientation == LinearLayoutManager.VERTICAL) {
 91             int left = parent.getPaddingLeft();
 92             int right = parent.getWidth() - parent.getPaddingRight();
 93             for (int index = 0; index < childCount - 1; index++) {
 94                 View childView = parent.getChildAt(index);
 95                 RecyclerView.LayoutParams childViewParams = (RecyclerView.LayoutParams) childView.getLayoutParams();
 96                 //childViewParams.bottomMargin->画在childview下面,自然要加上margin
 97                 //ViewCompat.getTranslationY(childView) ,纵向的平移,在动画的时候用得上,如果item没有动画则可有可无
 98                 int top = childView.getBottom() + childViewParams.bottomMargin + Math.round(ViewCompat.getTranslationY(childView));
 99                 int bottom = top + dp1;
100                 c.drawRect(left, top, right, bottom, paint);
101             }
102         } else if (mOrientation == LinearLayoutManager.HORIZONTAL) {
103             int top = parent.getPaddingTop();
104             int bottom = top + parent.getHeight();
105             for (int index = 0; index < childCount - 1; index++) {
106                 View childView = parent.getChildAt(index);
107                 RecyclerView.LayoutParams childViewParams = (RecyclerView.LayoutParams) childView.getLayoutParams();
108                 int left = childView.getRight() + childViewParams.rightMargin;
109                 int right = left + dp1;
110                 c.drawRect(left, top, right, bottom, paint);
111             }
112         }
113     }
114 
115     public void drawMethod3(Canvas c, RecyclerView parent, RecyclerView.State state) {
116         int childCount = parent.getChildCount();
117         if (mOrientation == LinearLayoutManager.VERTICAL) {
118             for (int pos = 0; pos < childCount - 1; pos++) {
119                 View view = parent.getChildAt(pos);
120                 RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) view.getLayoutParams();
121                 int left = view.getLeft() - layoutParams.leftMargin;
122                 int right = view.getRight() + layoutParams.rightMargin;
123                 int top = view.getBottom() + layoutParams.bottomMargin;
124                 //纯色的情况下,高度要写死了
125                 int bottom = top + dpToPx(1);
126                 cd.setBounds(left, top, right, bottom);
127                 cd.draw(c);
128             }
129         } else if (mOrientation == LinearLayoutManager.HORIZONTAL) {
130             int top = parent.getPaddingTop();
131             int bottom = parent.getHeight() - parent.getPaddingBottom();
132             for (int i = 0; i < childCount; i++) {
133                 View child = parent.getChildAt(i);
134                 RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
135                 int left = child.getRight() + params.rightMargin;
136                 //同理。纯色的情况下,宽度是写死的
137                 int right = left + dpToPx(1);
138                 cd.setBounds(left, top, right, bottom);
139                 cd.draw(c);
140             }
141         }
142     }
143 
144     public int dpToPx(int dp) {
145         return (int) (dp * Resources.getSystem().getDisplayMetrics().density);
146     }
147 }

这个Decoration中,实现了几种方式。onDraw提供了3种绘制分割线的方式,但getItemOffsets只实现drawable的时候

第一种方式,是在分割线处画一个Drawable,这里的drawable可以是自己写的一个shape的drawable文件,也可以是图片,等等。如果是自己写的drawable,那么要写上宽或高。

第二种方式,是直接在分割线处画一个矩形的纯色区域,也很好理解。

第三种方式,和第一种。换汤不换药。也就是在第一种的情况下,如果drawable是纯色的话,就可以直接用colorDrawable,就不用那么麻烦自己去写一个shape的drawable了。这时候。高度(或宽度)就要自己自定义了,而不是在drawable里面写死。其实也很好理解。

 

2017-1-4 10:34 更新,后续再更新gridview的divider吧

 

RecyclerView还有一个和ListView比较常用不同是:当没数据的时候,也就是emptyView,ListView 可以直接设就可以了,而RecyclerView 没有提供相应的api,这就要我们自己去实现了。

实现的原理也很简单。那就是控件的显示和隐藏嘛。

虽然没有提供设置emptyView的api,但是提供了一个数据的观察相应的api,也就是给Adapter注册一个观察者,这个观察者可以观察整个Recycler的数据的变化。这就很简单啦,只要数据是空的时候,那就显示EmptyView,否则显示RecyclerView。都弄清楚了,就看代码。

1 rvAdapter.registerAdapterDataObserver(obs = new RecyclerView.AdapterDataObserver() {
2     @Override
3     public void onChanged() {
4         super.onChanged();
5         setEmptyView();
6     }
7 });
8 rv.setAdapter(rvAdapter);
9 setEmptyView();
1     private void setEmptyView() {
2         if (rvAdapter.getItemCount() > 0) {
3             emptyView.setVisibility(View.GONE);
4             rv.setVisibility(View.VISIBLE);
5         } else {
6             emptyView.setVisibility(View.VISIBLE);
7             rv.setVisibility(View.GONE);
8         }
9     }

这里,注册完了。最后一步,在RecyclerView的数据加载完毕的时候再调一下SemptyView,因为很多时候,加载出来的数据就是空的。所以,这里要设一下。设置EmptyView是否显示也就根据Adapter的返回的getItemCount来判断。

都好了,最后别忘了反注册。注册的观察者要在Activity 销毁的时候反注册。

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (obs != null)
            rvAdapter.unregisterAdapterDataObserver(obs);
    }

OK ,一切完毕。

更新于2017-1-4 11:46 

posted on 2016-12-28 14:56  Fwl的小花园  阅读(252)  评论(0编辑  收藏  举报