众所周知,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