在去年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 viewType) {
// 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。