Android 高级UI设计笔记07:RecyclerView 的详解

1. RecyclerView介绍

      在Android应用程序中列表是一个非常重要的控件,适用场合非常多,如新闻列表、应用列表、消息列表等等,但是从Android 一出生到现在并没有非常好用的列表控件,早期的 ListView 用法非常复杂,尤其是自定义列表,简直就是地狱,因为其中还涉及到很多效率优化的问题,新手很难写出高效率的基于列表应用,而且 ListView 只能垂直方向呈现内容,使用很不灵活,为了解决这个缺陷,Android 官方推出了 RecyclerView 控件,用来替代 ListView。

(1)RecyclerView的控件相对于ListView,他好在哪里呢?

  1)它封装了viewholder的回收复用。

      2)RecyclerView使用布局管理器管理子view的位置,也就是说你再不用拘泥于ListView的竖直线性展示方式。通过设置LayoutManager来快速实现listview、gridview、瀑布流的效果;而且还可以设置横向和纵向显示

  3)带了ItemAnimation,可以设置加载和移除时的动画,方便做出各种动态浏览的效果。

  4)分开的view :

我们平时用listview的时候,adapter一般这么写的:

 if (convertView == null) {
holder = new ViewHolder();
LayoutInflater inflater = ((Activity) mContext).getLayoutInflater();
convertView = inflater.inflate(
R.layout.list_device_binding, parent, false); holder.deviceImage = (ImageView) convertView
.findViewById(R.id.bluetoothDeviceImage);
holder.deviceName = (TextView) convertView
.findViewById(R.id.bluetoothDeviceName);
holder.deviceType = (TextView) convertView
.findViewById(R.id.bluetoothDeviceType);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}

但是,到了这里,RecyclerView分隔开了,如下:

 @Override
public A onCreateViewHolder(ViewGroup parent, int viewType) {
final View view = LayoutInflater.from(mContext).
inflate(R.layout.listitem_track_history, parent, false);
return new ViewHolder(view);
} @Override
public void onBindViewHolder(A holder, int position) {
Data da=getData(position);
holder.tvDate.setText(da.getDate());
}

  5)相对简单的层次结构:

我们看下Listview他背后的继承关系:

 public class ListView extends AbsListView
public abstract class AbsListView extends AdapterView<ListAdapter>
public abstract class AdapterView<T extends Adapter> extends ViewGroup

三重继承,内容还挺多的,不是直接继承ViewGroup,而相反的RecycleView却是直接继承自ViewGroup的。

(2)RecyclerView的控件相对于ListView,他有什么缺陷呢?(目前只知道1个缺点)

  1)不能简单的添加点击事件onListItemClickListener 

我们在使用listview设置子item的点击事件的时候,只需要像下面这么写

 listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) { User user= parent.getItemAtPosition(position);
}
});

但是,如果你使用这个RecycleView,会发现没有这个接口了。RecyclerView不再负责Item视图的布局及显示,所以RecyclerView也没有为Item开放OnItemClick等点击事件。

这就需要我们自己实现,见我的博客:Android 高级UI设计笔记20:RecyclerView 的详解之RecyclerView添加Item点击事件

(3)RecyclerView 核心内容:
  • Android RecyclerView 的用法
  • Android RecyclerView 横向布局
  • Android RecyclerView 垂直布局
  • Android RecyclerView 表格布局

这里我们使用的Android Studio,首先新建Android工程之后,右击app,打开  "Open Module Setting" ,然后找到"Dependencies"选项,添加RecyclerView依赖库,如下:

在gradle中添加依赖

dependencies {

compile fileTree(dir: 'libs', include: ['*.jar'])

testCompile 'junit:junit:4.12'

compile 'com.android.support:appcompat-v7:23.4.0'

compile 'com.android.support:recyclerview-v7:23.4.0'

}

当然也有很多朋友使用Eclipse开发工具(建议大家尽快使用AS,AS比Eclipse高效太多了),如果要使用RecyclerView也很简单,只要添加一个jar包到工程的libs文件下,然后构建路径就可以使用了,当然静态布局设计的时候还是注意要引用RecyclerView的全路径。

这个jar包为android-support-v7-recyclerview.jar  和 android-support-v4.jar,这个两个jar包往往会因为版本不一致产生冲突,从而会报错:

java.lang.RuntimeException: Unable to start activity ComponentInfo

这里是安全使用的v4 和 v7 库的下载地址为:http://download.csdn.net/detail/hebao5201314/9567555

那么这里我先讲解使用RecyclerView主要方法,如下:

   1)setLayoutManager(layout):控制显示方式。

RecyclerView提供了三种LayoutManager:LinearLayoutManager、GridLayoutManager、StaggeredGridLayoutManager

   2)setItemAnimator():控制item的增删动画。

   3)addItemDecoration(): 控制item间的间隔,这里还可以自定义间隔的样式。

——Adapter:使用RecyclerView时,我们需要一个集成RecyclerView.Adapter的适配器,作用是将数据与每个item的界面进行绑定。

——LayoutManager:用来确定每个item的布局,何时展示和隐藏。回收或重用的时候LayoutManager会向适配器请求新的数据来替换旧的数据,这种机制和ListView的原理类似,都避免了创建过多的View和频繁的调用findViewById方法。

目前SDK中提供了三种自带的LayoutManager:

 1)LinearLayoutManager

 2)GridLayoutManager

 3)StaggeredGridLayoutManager

2. 接下来我们就可以使用RecyclerView。

(1)首先我们来到MainActivity里面,如下:

 package com.example.hebao.learnrv;

 import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView; public class MainActivity extends AppCompatActivity { private RecyclerView rv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
rv = new RecyclerView(this);
//先加载RecyclerView
setContentView(rv);
//然后动态设置RecyclerView
rv.setLayoutManager(new LinearLayoutManager(this));
rv.setAdapter(new RecyclerView.Adapter(){
class ViewHolder extends RecyclerView.ViewHolder {
private TextView tv; public ViewHolder(TextView itemView) {
super(itemView);
tv = itemView;
} public TextView getTv() {
return tv;
}
} /**
* 创建ViewHolder
* @param viewGroup
* @param i
* @return
*/
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
return new ViewHolder(new TextView(viewGroup.getContext()));
} /**
* 对上面自己创建的ViewHolder携带数据进行处理
* @param viewHolder
* @param i
*/
@Override
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int i) {
ViewHolder vh = (ViewHolder) viewHolder;
vh.getTv().setText("Item "+i);
} /**
*
* 获取RecyclerView的子对象数量
*/
@Override
public int getItemCount() {
return 100;
}
} );
} }

布署程序到模拟器上,如下:(可以上下滑动滚动)

Android 高级UI设计笔记07:RecyclerView 的详解

(2)利用数组承载数据源:

 package com.example.hebao.learnrv;

 import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView; public class MainActivity extends AppCompatActivity { private RecyclerView rv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
rv = new RecyclerView(this); setContentView(rv); rv.setLayoutManager(new LinearLayoutManager(this));
rv.setAdapter(new RecyclerView.Adapter(){
private String[] data = new String[] {"hello", "二胎时代","九阴真经"};
class ViewHolder extends RecyclerView.ViewHolder {
private TextView tv; public ViewHolder(TextView itemView) {
super(itemView);
tv = itemView;
} public TextView getTv() {
return tv;
}
} /**
* 创建ViewHolder
* @param viewGroup
* @param i
* @return
*/
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
return new ViewHolder(new TextView(viewGroup.getContext()));
} /**
* 对上面自己创建的ViewHolder携带数据进行处理
* @param viewHolder
* @param i
*/
@Override
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int i) {
ViewHolder vh = (ViewHolder) viewHolder;
vh.getTv().setText(data[i]);
} /**
*
* 获取RecyclerView的子对象数量
*/
@Override
public int getItemCount() {
return data.length;
}
});
} }

布署程序到模拟器上,如下:

Android 高级UI设计笔记07:RecyclerView 的详解

3. 使用资源文件自定义列表项:
(1)首先来到MainActivity,如下:
 package com.example.hebao.learnrv;

 import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView; public class MainActivity extends AppCompatActivity { private RecyclerView rv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
rv = new RecyclerView(this); setContentView(rv); rv.setLayoutManager(new LinearLayoutManager(this));
rv.setAdapter(new MyAdapter());
} }

(2)此时MyAdapter,如下:

 package com.example.hebao.learnrv;

 import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView; /**
* Created by hebao on 11/16/15.
*/
class MyAdapter extends RecyclerView.Adapter {
private ItemData[] data = new ItemData[] {new ItemData("东邪", "黄药师"), new ItemData("西狂","杨过")}; class ViewHolder extends RecyclerView.ViewHolder {
private View root;
private TextView tvTitle, tvContent; public ViewHolder(View root) {
super(root);
tvTitle = (TextView) root.findViewById(R.id.tvTitle);
tvContent = (TextView) root.findViewById(R.id.tvContent);
} public TextView getTvContent() {
return tvContent;
} public TextView getTvTitle() {
return tvTitle;
}
} /**
* 创建ViewHolder
*
* @param viewGroup
* @param i
* @return
*/
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
return new ViewHolder(LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.list_cell,
null));
} /**
* 对上面自己创建的ViewHolder携带数据进行处理
*
* @param viewHolder
* @param i
*/
@Override
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int i) {
ViewHolder vh = (ViewHolder) viewHolder;
ItemData id = data[i];
vh.getTvTitle().setText(id.title);
vh.getTvContent().setText(id.content); } /**
* 获取RecyclerView的子对象数量
*/
@Override
public int getItemCount() {
return data.length;
} }

此处我们用到了一个实体数据类ItemData,如下:

 package com.example.hebao.learnrv;

 /**
* Created by hebao on 11/16/15.
*/
public class ItemData {
public String title = "title";
public String content ="content"; public ItemData(String title, String content) {
this.title = title;
this.content = content;
}
}

自定义布局文件资源为list_cell.xml:

 <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent"> <TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
android:text="Large Text"
android:id="@+id/tvTitle"
android:layout_gravity="center_horizontal" /> <TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="New Text"
android:id="@+id/tvContent"
android:layout_gravity="center_horizontal" />
</LinearLayout>

(3)程序布署到模拟器上,如下:

Android 高级UI设计笔记07:RecyclerView 的详解
 
相信大家看到上面的显示效果Item之间没有分割线,该怎么办呢?

这里我们需要用到RecyclerView.ItemDecoration给每一项Item视图添加子View,可以进行画分隔线之类的东西

那么具体如何使用呢

    我们可以创建一个类继承RecyclerView.ItemDecoration类来绘制分隔线,通过ItemDecoration可 以让我们每一个Item从视觉上面相互分开来,实现一个ItemDecoration,系统提供的ItemDecoration是一个抽象类,因为当我们RecyclerView在进行绘制的时候会进行绘制。

下面是转载别人实现的RecycleViewDivider,如下:

 package com.himi.recyclerviewdemo;

 import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.support.v4.content.ContextCompat;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView.ItemDecoration;
import android.view.View; public class RecycleViewDivider extends ItemDecoration {
private Paint mPaint;
private Drawable mDivider;
private int mDividerHeight = 2;//分割线高度,默认为1px
private int mOrientation;//列表的方向:LinearLayoutManager.VERTICAL或LinearLayoutManager.HORIZONTAL
private static final int[] ATTRS = new int[]{android.R.attr.listDivider}; /**
* 默认分割线:高度为2px,颜色为灰色
*
* @param context
* @param orientation 列表方向
*/
public RecycleViewDivider(Context context, int orientation) {
if (orientation != LinearLayoutManager.VERTICAL && orientation != LinearLayoutManager.HORIZONTAL) {
throw new IllegalArgumentException("请输入正确的参数!");
}
mOrientation = orientation; final TypedArray a = context.obtainStyledAttributes(ATTRS);
mDivider = a.getDrawable(0);
a.recycle();
} /**
* 自定义分割线
*
* @param context
* @param orientation 列表方向
* @param drawableId 分割线图片
*/
public RecycleViewDivider(Context context, int orientation, int drawableId) {
this(context, orientation);
mDivider = ContextCompat.getDrawable(context, drawableId);
mDividerHeight = mDivider.getIntrinsicHeight();
} /**
* 自定义分割线
*
* @param context
* @param orientation 列表方向
* @param dividerHeight 分割线高度
* @param dividerColor 分割线颜色
*/
public RecycleViewDivider(Context context, int orientation, int dividerHeight, int dividerColor) {
this(context, orientation);
mDividerHeight = dividerHeight;
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(dividerColor);
mPaint.setStyle(Paint.Style.FILL);
} //获取分割线尺寸
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
outRect.set(0, 0, 0, mDividerHeight);
} //绘制分割线
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDraw(c, parent, state);
if (mOrientation == LinearLayoutManager.VERTICAL) {
drawVertical(c, parent);
} else {
drawHorizontal(c, parent);
}
} //绘制横向 item 分割线
private void drawHorizontal(Canvas canvas, RecyclerView parent) {
final int left = parent.getPaddingLeft();
final int right = parent.getMeasuredWidth() - parent.getPaddingRight();
final int childSize = parent.getChildCount();
for (int i = 0; i < childSize; i++) {
final View child = parent.getChildAt(i);
RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) child.getLayoutParams();
final int top = child.getBottom() + layoutParams.bottomMargin;
final int bottom = top + mDividerHeight;
if (mDivider != null) {
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(canvas);
}
if (mPaint != null) {
canvas.drawRect(left, top, right, bottom, mPaint);
}
}
} //绘制纵向 item 分割线
private void drawVertical(Canvas canvas, RecyclerView parent) {
final int top = parent.getPaddingTop();
final int bottom = parent.getMeasuredHeight() - parent.getPaddingBottom();
final int childSize = parent.getChildCount();
for (int i = 0; i < childSize; i++) {
final View child = parent.getChildAt(i);
RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) child.getLayoutParams();
final int left = child.getRight() + layoutParams.rightMargin;
final int right = left + mDividerHeight;
if (mDivider != null) {
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(canvas);
}
if (mPaint != null) {
canvas.drawRect(left, top, right, bottom, mPaint);
}
}
}
}

使用方法:

添加默认分割线:高度为2px,颜色为灰色

  mRecyclerView.addItemDecoration(new RecycleViewDivider(mContext, LinearLayoutManager.VERTICAL));

添加自定义分割线:可自定义分割线drawable

  mRecyclerView.addItemDecoration(new RecycleViewDivider(mContext, LinearLayoutManager.VERTICAL, R.drawable.divider_mileage));

添加自定义分割线:可自定义分割线高度和颜色

  mRecyclerView.addItemDecoration(new RecycleViewDivider(mContext, LinearLayoutManager.VERTICAL, 10, getResources().getColor(R.color.divide_gray_color)));

使用很简单,这里我就不添加代码实现了。

4. RecyclerView的布局样式
(1)RecyclerView的横向布局:
修改2上面代码,如下:
list_cell.xml
 <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent"> <TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
android:text="Large Text"
android:id="@+id/tvTitle"
android:layout_gravity="center_horizontal" /> <TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="New Text"
android:id="@+id/tvContent"
android:layout_gravity="center_horizontal" />
</LinearLayout>

同时来到MainActivity,如下:

 package com.example.hebao.learnrv;

 import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView; public class MainActivity extends AppCompatActivity { private RecyclerView rv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
rv = new RecyclerView(this); setContentView(rv);
//设置RecyclerView为横向布局
rv.setLayoutManager(new LinearLayoutManager(this,LinearLayoutManager.HORIZONTAL,false));
rv.setAdapter(new MyAdapter());
} }

布署程序到模拟器上,如下:

Android 高级UI设计笔记07:RecyclerView 的详解
 
 
(2)RecyclerView的垂直布局
rv.setLayoutManager(new LinearLayoutManager(this,LinearLayoutManager.VERTICAL,false));
 
(3)RecyclerView的表格布局:
rv.setLayoutManager(new GridLayoutManager(this,3));
上一篇:bzoj千题计划105:bzoj3503: [Cqoi2014]和谐矩阵(高斯消元法解异或方程组)


下一篇:Android 高级UI设计笔记06:仿微信图片选择器(转载)