前言
Android5.0推出了RecyclerView控件来取代旧的ListView、GridView等控件,这个控件的功能非常强大,最重要的是它的整体性能要高于老的ListView、GridView控件。在设计上也更加注重职责分离,比如ListView包含了布局和回收功能、分隔线、顶部底部装饰布局等各种功能,RecyclerView则采用了可插拔式设计,能够根据用户设置不同的布局策略、分隔样式展现不同的界面效果。除此之外,RecyclerView还自带ViewHolder优化功能,用户所有的界面操作都面向ViewHolder,避免老的Adapter需要用户自己手动添加ViewHolder功能。
线性布局
首先开始学习如何使最简单的线性布局,这种布局就是从上向下的竖向或者从左到右横向一次展示每个数据条目视图。在开始使用时直接在XML文件中定义RecyclerView布局,设置宽高都是MATCH_PARENT。
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.RecyclerView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:background="@color/colorAccent"
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.design.RecyclerViewActivity">
</android.support.v7.widget.RecyclerView>
在Activity中查找到RecyclerView控件,并且为它设置布局方式为LinearLayoutManager,布局的方向为竖向布局,这样就能实现ListView样式的展示。
recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
// 设置线性布局管理器
recyclerView.setLayoutManager(layoutManager);
// 增加分割线效果
recyclerView.addItemDecoration(new LineDecoration());
// 设置Adapter生成视图
recyclerView.setAdapter(new RecyclerAdapter(this));
最后设置的Adapter和普通的ListView设置的Adapter是不相同的,这个Adapter需要继承自RecyclerView.Adapter,这个类的实现代码如下:
public class RecyclerAdapter extends RecyclerView.Adapter<RecyclerViewHolder> {
private List<User> users = new ArrayList<User>() {
{
add(new User(R.drawable.beach, "刘备", "唯贤唯德,能服于人"));
add(new User(R.drawable.bamboo, "诸葛亮", "淡泊以明志,宁静以致远"));
add(new User(R.drawable.road, "关羽", "安能与老兵同列"));
add(new User(R.drawable.flower, "赵云", "子龙一身是胆"));
add(new User(R.drawable.lake, "曹操", "宁教我负天下人,不教天下人负我"));
add(new User(R.drawable.rain, "司马懿", "老而不死是为贼"));
add(new User(R.drawable.sea, "司马昭", "司马昭之心路人皆知"));
add(new User(R.drawable.moon, "孙权", "生子当如孙仲谋"));
add(new User(R.drawable.peach, "周瑜", "既生瑜何生亮"));
add(new User(R.drawable.pool, "吕蒙", "士别三日当刮目相待"));
}
};
private Context context;
private LayoutInflater inflater;
public RecyclerAdapter(Context context) {
this.context = context;
this.inflater = LayoutInflater.from(context);
}
// 根据viewType生成对应的ViewHolder对象,ViewHolder对象
@Override
public RecyclerViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = inflater.inflate(R.layout.user_item, parent, false);
return new RecyclerViewHolder(itemView);
}
// 调用数据和视图绑定功能
@Override
public void onBindViewHolder(RecyclerViewHolder holder, int position) {
holder.bindView(users.get(position));
}
// 数据条数
@Override
public int getItemCount() {
return users.size();
}
}
这个Adapter是最简单的实现方式,只有一种视图布局类型,最后就是ViewHolder的实现了,这个类也需要继承自RecyclerView.ViewHolder类,可以看到RecyclerView封装了ViewHolder模式。
public class RecyclerViewHolder extends RecyclerView.ViewHolder {
private ImageView portrait;
private TextView name;
private TextView desc;
public RecyclerViewHolder(View itemView) {
super(itemView);
name = (TextView) itemView.findViewById(R.id.name);
desc = (TextView) itemView.findViewById(R.id.desc);
portrait = (ImageView) itemView.findViewById(R.id.portrait);
}
public void bindView(User user) {
name.setText(user.getName());
desc.setText(user.getDesc());
portrait.setImageResource(user.getPortrait());
}
}
代码实现都很简单,现在可以运行一下看下实现效果。
上面的实现效果和ListView基本一致,但是看不出来条目之间的分隔位置,这个只需要添加一个ItemDecoration对象就可以了,改对象有三个需要用户实现的接口方法,这里给出LinearItemDecoration的实现代码。
public class LineDecoration extends RecyclerView.ItemDecoration {
private static final String TAG = "LineDecoration";
private Paint paint;
// 初始化画笔控件
public LineDecoration() {
paint = new Paint();
paint.setAntiAlias(true);
paint.setDither(true);
paint.setStyle(Paint.Style.FILL);
paint.setColor(Color.BLUE);
}
// 画出的内容在条目下方
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
Log.d(TAG, "onDraw()");
int count = parent.getChildCount();
for (int i = 0; i < count; i++) {
View view = parent.getChildAt(i);
int bottom = view.getBottom();
c.drawRect(parent.getPaddingLeft(), bottom - CommonUtils.dp2px(25),
parent.getWidth() - parent.getPaddingRight(), bottom + CommonUtils.dp2px(5), paint);
}
}
// 画出来的内容在条目上方
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
// Log.d(TAG, "onDrawOver()");
// int count = parent.getChildCount();
// for (int i = 0; i < count; i++) {
// View view = parent.getChildAt(i);
// int bottom = view.getBottom();
// c.drawRect(parent.getPaddingLeft(), bottom - CommonUtils.dp2px(25),
// parent.getWidth() - parent.getPaddingRight(), bottom + CommonUtils.dp2px(5), paint);
// }
}
// 为展示条目设置额外的空间
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
Log.d(TAG, "getItemOffsets()");
outRect.bottom = CommonUtils.dp2px(5);
}
}
getItemOffsets方法就是为了每个条目设置额外的空间,用户可以在这些空间里绘制元素。如图所示outRect的各个属性对应着各个空间位置。
最后两张图分别对应着onDraw和onDrawOver的不同之处,用户可以清晰的看出它们之间的区别。
网格布局
网格布局采用了和GridView一样的布局方式,需要为RecyclerView设置网格布局管理器,定义网格布局的每行有三个元素。同时为了能够看到将数据分到不同的组中,这里采用了多布局样式功能。网格布局的Adapter对象定义如下:
public class GridRecyclerAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
public static List<User> users = new ArrayList<User>() {
{
add(new User("蜀国"));
add(new User(R.drawable.beach, "刘备", "唯贤唯德,能服于人"));
add(new User(R.drawable.bamboo, "诸葛亮", "淡泊以明志,宁静以致远"));
add(new User(R.drawable.road, "关羽", "安能与老兵同列"));
add(new User(R.drawable.flower, "赵云", "子龙一身是胆"));
add(new User("魏国"));
add(new User(R.drawable.lake, "曹操", "宁教我负天下人,不教天下人负我"));
add(new User(R.drawable.rain, "司马懿", "老而不死是为贼"));
add(new User(R.drawable.sea, "司马昭", "司马昭之心路人皆知"));
add(new User("吴国"));
add(new User(R.drawable.moon, "孙权", "生子当如孙仲谋"));
add(new User(R.drawable.peach, "周瑜", "既生瑜何生亮"));
add(new User(R.drawable.pool, "吕蒙", "士别三日当刮目相待"));
}
};
private Context context;
private LayoutInflater inflater;
public GridRecyclerAdapter(Context context) {
this.context = context;
this.inflater = LayoutInflater.from(context);
}
// 根据不同的布局样式生成不同的布局ViewHolder
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
RecyclerView.ViewHolder viewHolder = null;
if (viewType == R.layout.grid_user_item) {
View itemView = inflater.inflate(R.layout.grid_user_item, parent, false);
viewHolder = new RecyclerViewHolder(itemView);
} else {
View itemView = inflater.inflate(R.layout.state_item, parent, false);
viewHolder = new StateViewHolder(itemView);
}
return viewHolder;
}
// 根据不同布局样式绑定布局与数据
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (getItemViewType(position) == R.layout.grid_user_item) {
((RecyclerViewHolder) holder).bindView(users.get(position));
} else {
((StateViewHolder) holder).bindView(users.get(position));
}
}
@Override
public int getItemCount() {
return users.size();
}
// 使用布局资源来区分布局的类型
@Override
public int getItemViewType(int position) {
User user = users.get(position);
if (user.isHuman()) {
return R.layout.grid_user_item;
} else {
return R.layout.state_item;
}
}
}
RecyclerViewHolder的实现前面已经贴出来了,这里在贴出StateViewHolder:
public class StateViewHolder extends RecyclerView.ViewHolder {
private TextView text;
public StateViewHolder(View itemView) {
super(itemView);
this.text = (TextView) itemView.findViewById(R.id.text);
}
public void bindView(User user) {
text.setText(user.getName());
}
}
最后是单元格之间的分隔,单元格是二维对象,需要在右边和下边都有分隔,实现代码如下:
public class GridItemDecoration extends RecyclerView.ItemDecoration {
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
}
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
outRect.right = CommonUtils.dp2px(1);
outRect.bottom = CommonUtils.dp2px(3);
}
}
最终的实现效果如下:
瀑布流布局
瀑布流布局需要定义好方向,还需要定义在这个方向上有几行(列),感觉特别像人写字,展示几行就是写几行字,定义好写几行之后在开始定义怎么写,如果是竖行需要从上向下写,如果是横行就需要从左向右写,每一行写完后再到第二行接着写,直到所有的文字都写完。
recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
StaggeredGridLayoutManager staggeredGridLayoutManager =
new StaggeredGridLayoutManager(3, StaggeredGridLayoutManager.HORIZONTAL);
recyclerView.setLayoutManager(staggeredGridLayoutManager);
recyclerView.addItemDecoration(new GridItemDecoration());
recyclerView.setAdapter(new GridRecyclerAdapter(this));
设置瀑布流管理布局很简单,其他的都和GridLayoutManager一样,设置完成之后展示结果如下图:
查看全部实现代码请点击查看源码
总结
RecyclerView的功能非常强大,除了上面提到的能够支持多种布局样式,它还提供了视图局部刷新功能,复杂的装饰布局支持,这些高级的用法还有待进一步学习。