RecyclerView 是Android L版本中新添加的一个用来取代ListView的SDK,它的灵活性与可替代性比listview更好。
概述
RecyclerView出现已经有一段时间了,相信大家肯定不陌生了,大家可以通过导入support-v7对其进行使用。
据官方的介绍,该控件用于在有限的窗口中展示大量数据集,其实这样功能的控件我们并不陌生,例如:ListView、GridView。
那么有了ListView、GridView为什么还需要RecyclerView这样的控件呢?整体上看RecyclerView架构,提供了一种插拔式的体验,高度的解耦,异常的灵活,通过设置它提供的不同LayoutManager,ItemDecoration , ItemAnimator实现令人瞠目的效果。
- 你想要控制其显示的方式,请通过布局管理器LayoutManager
- 你想要控制Item间的间隔(可绘制),请通过ItemDecoration
- 你想要控制Item增删的动画,请通过ItemAnimator
- 你想要控制点击、长按事件,请自己写(擦,这点尼玛。)
基本使用
RecyclerView 与 ListView、GridView 类似,都是可以显示同一种类型 View 的集合的控件。
首先看看最简单的用法,四步走:
接入 build.gradle 文件中加入
compile 'com.android.support:recyclerview-v7:24.0.0'
创建对象
RecyclerView recyclerview = (RecyclerView) findViewById(R.id.recyclerview);
设置显示规则
recyclerview.setLayoutManager(new LinearLayoutManager( this, LinearLayoutManager.VERTICAL, false));
RecyclerView 将所有的显示规则交给一个叫 LayoutManager 的类去完成了。
LayoutManager 是一个抽象类,系统已经为我们提供了三个默认的实现类,分别是:- LinearLayoutManager
- GridLayoutManager
- StaggeredGridLayoutManager
从名字我们就能看出来了,分别是,线性显示、网格显示、瀑布流显示。当然你也可以通过继承这些类来扩展实现自己的 LayougManager。
设置适配器
recyclerview.setAdapter(adapter);
适配器,同 ListView 一样,用来设置每个item显示内容的。
通常,我们写 ListView 适配器,都是首先继承 BaseAdapter,实现四个抽象方法,创建一个静态 ViewHolder , getView() 方法中判断 convertView 是否为空,创建还是获取 viewholder 对象。
而 RecyclerView 也是类似的步骤,首先继承RecyclerView.Adapter类,实现三个抽象方法,创建一个静态的 ViewHolder。不过 RecyclerView 的 ViewHolder 创建稍微有些限制,类名就是上面继承的时候泛型中声明的类名(好像反了,应该是上面泛型中的类名应该是这个holder的类名);并且 ViewHolder 必须继承自RecyclerView.ViewHolder类。
相比较于ListView的代码,ListView可能只需要去设置一个adapter就能正常使用了。而RecyclerView基本需要上面一系列的步骤,那么为什么会添加这么多的步骤呢?
那么就必须解释下RecyclerView的这个名字了,从它类名上看,RecyclerView代表的意义是,我只管Recycler View,也就是说RecyclerView只管回收与复用View,其他的你可以自己去设置。可以看出其高度的解耦,给予你充分的定制自由(所以你才可以轻松的通过这个控件实现ListView,GirdView,瀑布流等效果)。
MainActivity.java
public class MainActivity extends Activity { private RecyclerView recyclerView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //获取RecyclerView控件 recyclerView = (RecyclerView) findViewById(R.id.recyclerView); //设置显示规则 recyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)); //设置适配器 final MyAdapter myAdapter = new MyAdapter(); myAdapter.setOnItemClickListener(new MyAdapter.OnItemClickLitener() { @Override public void onItemClick_Add(View view, List<String> datas, int position) { datas.add(position-1, "hello"); myAdapter.notifyItemInserted(position); } @Override public void onItemClick_Delete(View view, List<String> datas, int position) { datas.remove(position); myAdapter.notifyItemRemoved(position); } }); recyclerView.setAdapter(myAdapter); //设置分割线 MyItemDecoration divider = new MyItemDecoration(new ColorDrawable(0xffff0000), OrientationHelper.VERTICAL); divider.setMargin(50,50,50,50); divider.setHeight(20); recyclerView.addItemDecoration(divider); } }
MyAdapter.java
public class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { /** * 定义RecyclerView的列表类型 */ private static final int TYPE_HEADER = 1; private static final int TYPE_ITEM = 2; private static final int TYPE_FOOTER = 3; /** * OnItemClick点击事件 */ public interface OnItemClickLitener { void onItemClick_Add(View view, List<String> datas, int position); void onItemClick_Delete(View view, List<String> datas, int position); } private OnItemClickLitener mOnItemClickLitener; public void setOnItemClickListener(OnItemClickLitener onItemClickLitener) { mOnItemClickLitener = onItemClickLitener; } private List<String> datas; public MyAdapter() { datas = new ArrayList(2); datas.add("01"); datas.add("02"); datas.add("03"); datas.add("04"); datas.add("05"); } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { LayoutInflater inflater = LayoutInflater.from(parent.getContext()); switch (viewType) { case TYPE_HEADER: return new HeadViewHolder(inflater.inflate(R.layout.layout_recycler_head, parent, false)); case TYPE_ITEM: return new ItemViewHolder(inflater.inflate(R.layout.layout_recycler_item, parent, false)); case TYPE_FOOTER: return new FooterViewHolder(inflater.inflate(R.layout.layout_recycler_footer, null)); } return null; } @Override public void onBindViewHolder(final RecyclerView.ViewHolder holder, final int position) { if (holder instanceof HeadViewHolder) { ((HeadViewHolder) holder).mTextView.setText("我是一个Header"); } else if (holder instanceof FooterViewHolder) { ((FooterViewHolder) holder).mTextView.setText("我是一个Footer"); } else { ((ItemViewHolder)holder).txtInfo.setText(datas.get(position-1)); ((ItemViewHolder) holder).btnAdd.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { int pos = holder.getLayoutPosition(); mOnItemClickLitener.onItemClick_Add(v, datas, pos); } }); ((ItemViewHolder) holder).btnDelete.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { int pos = holder.getLayoutPosition(); mOnItemClickLitener.onItemClick_Delete(v, datas, pos); } }); } } @Override public int getItemCount() { return datas.size() + 2; } @Override public int getItemViewType(int position) { if (position == 0) { return TYPE_HEADER; } else if (position == datas.size() + 1) { return TYPE_FOOTER; } return TYPE_ITEM; } //自定义的ViewHolder,持有每个Item的的所有界面元素 public static class ItemViewHolder extends RecyclerView.ViewHolder { public TextView txtInfo; public Button btnAdd; public Button btnDelete; public ItemViewHolder(View view) { super(view); txtInfo = (TextView) view.findViewById(R.id.txtInfo); btnAdd = (Button) view.findViewById(R.id.btnAdd); btnDelete = (Button) view.findViewById(R.id.btnDelete); } } public static class HeadViewHolder extends RecyclerView.ViewHolder { public TextView mTextView; public HeadViewHolder(View view) { super(view); mTextView = (TextView) view.findViewById(R.id.txtHeader); } } public static class FooterViewHolder extends RecyclerView.ViewHolder { public TextView mTextView; public FooterViewHolder(View view) { super(view); mTextView = (TextView) view.findViewById(R.id.txtFooter); } } }
MyItemDecoration.java
public class MyItemDecoration extends RecyclerView.ItemDecoration { private Drawable mDivider; private int leftMargin, rightMargin, topMargin, bottomMargin; private int width, height; private int mOrientation; public MyItemDecoration(Drawable divider, int orientation) { setDivider(divider); setOrientation(orientation); } private void setDivider(Drawable divider) { this.mDivider = divider; if (mDivider == null) { mDivider = new ColorDrawable(0xffff0000); } width = mDivider.getIntrinsicWidth(); height = mDivider.getIntrinsicHeight(); } private void setOrientation(int orientation) { if (orientation != LinearLayoutManager.HORIZONTAL && orientation != LinearLayoutManager.VERTICAL) { throw new IllegalArgumentException("invalid orientation"); } mOrientation = orientation; } public void setMargin(int left, int top, int right, int bottom) { this.leftMargin = left; this.topMargin = top; this.rightMargin = right; this.bottomMargin = bottom; } public void setHeight(int height) { this.height = height; } public void setWidth(int width) { this.width = width; } public int getHeight() { return height; } public int getWidth() { return width; } @Override public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { super.onDraw(c, parent, state); if (mOrientation == LinearLayoutManager.HORIZONTAL) { drawHorizontal(c, parent); } else { drawVertical(c, parent); } } public void drawHorizontal(Canvas c, RecyclerView parent) { final int top = parent.getPaddingTop() + topMargin; final int bottom = parent.getHeight() - parent.getPaddingBottom() - bottomMargin; 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 + leftMargin; final int right = left + width; mDivider.setBounds(left, top, right, bottom); mDivider.draw(c); } } public void drawVertical(Canvas c, RecyclerView parent) { final int left = parent.getPaddingLeft() + leftMargin; final int right = parent.getWidth() - parent.getPaddingRight() - rightMargin; 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 top = child.getBottom() + params.bottomMargin + topMargin; final int bottom = top + height; mDivider.setBounds(left, top, right, bottom); mDivider.draw(c); } } @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { super.getItemOffsets(outRect, view, parent, state); if (mOrientation == LinearLayoutManager.HORIZONTAL) { outRect.set(0, 0, leftMargin + width + rightMargin, 0); } else { outRect.set(0, 0, 0, topMargin + height + bottomMargin); } } }
layout_recycler_head.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#FFFF00"
android:orientation="horizontal" >
<TextView
android:id="@+id/txtHeader"
android:text="Header"
android:layout_width="match_parent"
android:layout_height="175dp"
android:textColor="#ff000000"
android:gravity="center"
android:textSize="20sp" />
</LinearLayout>
- yout_recycler_item.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="horizontal" >
<TextView
android:id="@+id/txtInfo"
android:layout_width="0dp"
android:layout_height="80dp"
android:layout_weight="1"
android:textColor="#ff000000"
android:gravity="center"
android:layout_gravity="center"
android:text="Hello"
android:textSize="20sp" />
<Button
android:id="@+id/btnAdd"
android:text="新增"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_weight="1"/>
<Button
android:id="@+id/btnDelete"
android:text="删除"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_weight="1"/>
</LinearLayout>
- layout_recycler_footer.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#FFFF00"
android:orientation="horizontal">
<TextView
android:id="@+id/txtFooter"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:text="Footer"
android:textColor="#ff000000"
android:textSize="20sp"/>
</LinearLayout>
在自定义RecyclerAdapter的时候,在重写onCreateViewHolder方法是使用了
@Override
public H onCreateViewHolder(ViewGroup parent, int viewType) {
View view=View.inflate(context,layoutId,null);
return view;
}
进行生成布局,结果发现生成的布局没有LayoutParams。以前自定义View的时候发现,LayoutParams是由于ViewGroup生成的,因为这里添加的ViewGroup为null。所以并不会生成LayoutParams。结果在RecyclerView的getViewForPosition方法中检查了有没有LayoutParams如果没有的话就调用LayoutManager的generateDefaultLayoutParams生成默认的LayoutParames。代码段如下:
final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
final LayoutParams rvLayoutParams;
if (lp == null) {
rvLayoutParams = (LayoutParams) generateDefaultLayoutParams();
holder.itemView.setLayoutParams(rvLayoutParams);
} else if (!checkLayoutParams(lp)) {
rvLayoutParams = (LayoutParams) generateLayoutParams(lp);
holder.itemView.setLayoutParams(rvLayoutParams);
} else {
rvLayoutParams = (LayoutParams) lp;
}
而在LinearLayoutManager中generateDefaultLayoutParams方法实现如下。
/**
* {@inheritDoc}
*/
@Override
public LayoutParams generateDefaultLayoutParams() {
return new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
}
最终会造成RecycleView的显示效果与布局文件不一致。后来使用了LayoutInflater来填充布局。
@Override
public H onCreateViewHolder(ViewGroup parent, int viewType) {
View view = mInflater.inflate(layoutId, parent, false);
return getInstanceOfH(view);
}
查看LayoutInflater源码发现inflate最后的参数如果是false的话就不会将生成的View添加到parent。但是会根据parent产生相应的LayoutParams 。源码如下:
* @param attachToRoot Whether the inflated hierarchy should be attached to
* the root parameter? If false, root is only used to create the
* correct subclass of LayoutParams for the root view in the XML.
因为在onCreateViewHolder中产生的View不能由我们手动添加到RecycleView中所以最后的参数只能是false;
吐槽
OnItemTouchListener 什么鬼?
用习惯了 ListView 的 OnItemClickListener ,RecyclerView 你的 OnItemClickListener 呢?
Tell me where do I find, something like ListView listener ?
好吧,翻遍了 API 列表,就找到了个 OnItemTouchListener ,这特么什么鬼,我干嘛要对每个 item 监听触摸屏事件。
万万没想到,最终我还是在 Google IO 里面的介绍找到了原因。原来是 Google 的工程师分不清究竟是改给 listview 的 item 添加点击事件,还是应该给每个 item 的 view 添加点击事件,索性就不给 OnItemClickListener 了,然后在 support demo 里面,你就会发现,RecyclerView 的 item 点击事件都是写在了 adapter 的 ViewHolder 里面。
RecyclerView是一个高度自由的控件,它把控件位置的摆放交给LayoutManager去管理,RecyclerView的数据交给Adapter类去管理,RecyclerView的分割线交给RecyclerView.ItemDecoration的实现类去管理(需用户自己实现,系统未给出默认实现类),