在去年Google I/0大会,Google开放了一个全新的视图类RecyclerView,它被用来代替ListView以及GridView,提供更为高效的回收复用机制,同时实现管理与视图的解耦合,今天对这个新的控件来进行一次总结。
概述
首先,让我们来看一下RecyclerView类之下都有哪些重要的类,以及他们的作用:
RecyclerView.Adapter:托管数据集合,为每个Item创建视图;
RecyclerView.ViewHolder:承载Item视图的子视图;
RecyclerView.LayoutManager:负责Item视图的布局;
RecyclerView.ItemDecoration:为每个Item视图添加子视图,在Demo中被用来绘制Divider;
RecyclerView.ItemAnimator:负责添加、删除数据时的动画效果;
基本用法
首先让我来看一下RecyclerView的基本用法:
1.创建一个线性布局管理器LayoutManager
// 创建一个线性布局管理器 mLayoutManager = new LinearLayoutManager(this); // 默认是Vertical,可以不写 mLayoutManager.setOrientation(LinearLayoutManager.VERTICAL); mRecyclerView.setLayoutManager(mLayoutManager);
2.重新一个adapter继承RecyclerView.Adapter《VH》,VH就表示我们平时在ListView中用的ViewHolder类(该类必须继承RecyclerView.ViewHolder);
在adapter中有几个重要的方法需要我们自己补充:
public int getItemCount():返回显示Item总数;
public void onBindViewHolder(ViewHolder vh, int position):绑定View到Item上vh就是我们在继承RecyclerView.Adapter传入的VH类型,在这个方法中处理数据显示到Item上;
public ViewHolder onCreateViewHolder(ViewGroup view, int position):在该方法中我们创建一个ViewHolder并返回,ViewHolder必须有一个带有View的构造函数,这个View就是我们Item的根布局,在这里我们可以自定义Item的布局;
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> { private List<String> dataList; public MyAdapter(List<String> list) { this.dataList = list; } @Override public int getItemCount() { // TODO Auto-generated method stub return dataList.size(); } @Override public void onBindViewHolder(ViewHolder viewHolder, int position){ // TODO Auto-generated method stub viewHolder.textView.setText(dataList.get(position)); } @Override public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int position) { // TODO Auto-generated method stub View view = LayoutInflater.from(viewGroup.getContext()).inflate( R.layout.item, null); ViewHolder holder = new ViewHolder(view); return holder; } public class ViewHolder extends RecyclerView.ViewHolder { public TextView textView; public ViewHolder(View view) { super(view); // TODO Auto-generated constructor stub textView = (TextView) view.findViewById(R.id.item_text); } }
3.设置数据集
List<String> list = new ArrayList<String>(); for (int i = 0; i < 100; i++) { list.add("item "+ i); } MyAdapter adapter = new MyAdapter(list); mRecyclerView.setAdapter(adapter);
改变LinearLayoutManager的方向可以设置为横向的ListView显示,这也是RecyclerView的强大之处,可以实现回收管理和视图的解耦。
mLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
如何为Item添加分割线
简单的使用之后我们看到的RecyclerView连基本的分割线都没有,接下来就来看看如何为它添加分割线吧:使用addItemDecoration方法可以为RecyclerView添加一个ItemDecoration,利用ItemDecoration为我们绘制分割线。
ItemDecoration下有三个方法,ItemDecoration并没有对其实现,需要我们自己完成:
onDraw方法:其绘制将会在每个Item被绘制之前进行;
onDrawOver:在绘制完Item后进行绘制;
getItemOffsets 可以通过outRect.set()为每个Item设置一定的偏移量;
让我们来看看代码:
public class ItemDivider extends ItemDecoration { private Drawable mDrawable; public ItemDivider(Context context, int resId) { //在这里我们传入作为Divider的Drawable对象 mDrawable = context.getResources().getDrawable(resId); } @Override public void onDrawOver(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); final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child .getLayoutParams(); //以下计算主要用来确定绘制的位置 final int top = child.getBottom() + params.bottomMargin; final int bottom = top + mDrawable.getIntrinsicHeight(); mDrawable.setBounds(left, top, right, bottom); mDrawable.draw(c); } } @Override public void getItemOffsets(Rect outRect, int position, RecyclerView parent) { outRect.set(0, 0, 0, mDrawable.getIntrinsicWidth()); } }
在这里我写了一个shape来代替分割线:
<?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" > <solid android:color="#000" /> <size android:height="2dp" /> </shape>
添加分割线的效果:
如何添加点击事件 ##
在Adapter中设置自己OnItemClickListener以及OnItemLongClickListener
在ViewHolder中公开Item的根布局View,之后在onBindViewHolder方法获得根布局View并设置点击的listener,调用自己设置的listener
1.首先在MyAdapter中创建自己的接口:
public interface OnItemClickListener { public void onClick(View parent, int position); } public interface OnItemLongClickListener { public boolean onLongClick(View parent, int position); }
2.接着在ViewHolder中将根布局View公开:
public class ViewHolder extends RecyclerView.ViewHolder { public TextView textView; public View itemView; public ViewHolder(View view) { super(view); // TODO Auto-generated constructor stub itemView = view; textView = (TextView) view.findViewById(R.id.item_text); } }
3.最后在onBindViewHolder对itemView设置点击事件:
viewHolder.itemView.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub if (onItemClickListener != null) { onItemClickListener.onClick(v, position); } } }); viewHolder.itemView.setOnLongClickListener(new OnLongClickListener() { @Override public boolean onLongClick(View v) { // TODO Auto-generated method stub if (onItemLongClickListener != null) { return onItemLongClickListener.onLongClick(v, position); } return false; } });
如何判断是否滑动到达尾部或顶部
LinearLayoutManager提供了如下几个方法来帮助开发者获取屏幕上的顶部item和底部item:
findFirstVisibleItemPosition()
findFirstCompletelyVisibleItemPosition()
findLastVisibleItemPosition()
findLastCompletelyVisibleItemPosition()
这样我们就得到了思路:对RecyclerView设置滑动监听事件,在其中进行判断:
mRecyclerView.setOnScrollListener(new OnScrollListener() { boolean isShowTop = false; boolean isShowBottom = false; @Override public void onScrolled(int arg0, int arg1) { // TODO Auto-generated method stub if (mLayoutManager.findLastCompletelyVisibleItemPosition() == 99) { if (!isShowTop) { Toast.makeText(MainActivity.this, "滑动到底部", Toast.LENGTH_SHORT).show(); } isShowTop = true; } else { isShowTop = false; } if (mLayoutManager.findFirstCompletelyVisibleItemPosition() == 0) { if (!isShowBottom) { Toast.makeText(MainActivity.this, "滑动到顶部", Toast.LENGTH_SHORT).show(); } isShowBottom = true; } else { isShowBottom = false; } } @Override public void onScrollStateChanged(int arg0) { // TODO Auto-generated method stub } });
来看一下效果:
添加或移除数据
RecyclerView.Adapter中提供了两个方法来做出添加数据或删除数据的调整:
public final void notifyItemInserted(int position)
public final void notifyItemRemoved(int position)
这样我们只需在自己的Adapter中提供添加或删除的方法,并在方法之中调用上述方法即可:
public void insert(String data, int position){ dataList.add(position, data); notifyItemInserted(position); } public void remove(int position){ dataList.remove(position); notifyItemRemoved(position); }
接下来我在Activity中对RecyclerView设置点击添加数据,长按删除数据;
adapter.setOnItemClickListener(new OnItemClickListener() { @Override public void onClick(View parent, int position) { // TODO Auto-generated method stub adapter.insert("Insert", position); } }); adapter.setOnItemLongClickListener(new OnItemLongClickListener() { @Override public boolean onLongClick(View parent, int position) { // TODO Auto-generated method stub adapter.remove(position); return true; } });
来看一下效果吧:
ItemAnimator可以设置加载和移除时的动画,我们可以通过setItemAnimator方法设置,但目前只提供了DefaultItemAnimator。