RecyclerView控件已经出来很久了,以往只是看看了api说明,没有怎么使用过。以前都是使用listview控件或gridview控件。在此不对RecyclerView的控件概念做详细介绍,如果不了解的可以在官网api说明中查看。总之,此控件比较强大,可以实现ListView、GridView以及瀑布流的UI效果。本文主要介绍怎么使用RecyclerView。
一、首先必须引入相应的依赖包
在Android Studio的模块build.gradle中加入依赖项compile 'com.android.support:recyclerview-v7:25.3.1'当然,这里我使用的是25.3.1版本,你可以使用其他的版本,当然最好使用最新的。
二、得到对应RecyclerView对象
像其他android控件一样,在layout布局中,增加RecyclerView节点,然后在Activity或Fragment中获取此控件对象(findViewById)。
三、设置相关属性
private void setRecyclerView() {
mMyRecyclerView.setHasFixedSize(true);
mAdapter = new MyAdapter(this, new ArrayList<BaseBean>(0));
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
//设置布局管理器
mMyRecyclerView.setLayoutManager(linearLayoutManager);
//设置适配器
mMyRecyclerView.setAdapter(mAdapter);
//设置Item增加、移除动画
mMyRecyclerView.setItemAnimator(new DefaultItemAnimator());
//添加分割线
mMyRecyclerView.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL_LIST));
}
这里我们以实现线性的列表为例说明(类似于ListView的效果)。
1、设置布局管理器
这里我们使用的是线性布局管理器LinearLayoutManager,你也可以使用网格布局管理器GridLayoutManager或者瀑布布局管理器StaggeredGridLayoutManager,
当然你的分割线绘制类实现也要同时做对应的修改。
2、设置适配器
这个没有什么好说的,和ListView或者GridView一样,都需要设置适配器。不过这里的适配器必须是RecyclerView.Adapter<VH>的子类,并且VH也派生于RecyclerView.ViewHolder
3、设置相关动画
RecyclerView在数据更新,造成item增加、减少、移动时可以产生对应的动画,而这里的动画就会起作用。
4、设置分割线
在ListView控件中,一般通过layout布局属性设置后会在每个item之间产生分割线,但是在RecyclerView控件布局中,并没有这样 的属性可以设置,但是 RecyclerView更加灵活,可以通过设置响应的渲染达到这样的分割线,也就是说在分割线都是按照这里设置的 渲染类实现来绘制出来的。
5、每个item点击响应
RecyclerView中并没有默认的点击响应,这个用户需要自己实现。
四、相关更新
上面设置后,那么给予数据,那么RecyclerView的实现的UI就可以显示了。而在更新时,请尽量不要使用notifyDataSetChanged,对于
RecyclerView有自己的更好的更新方式(注意:当Adaper数据集更新时,请紧接着调用这些更新方法,并且两者在同一个线程中执行,否则
你会发现有时候会产生IndexOutOfBound索引越界异常):
//相当于调用notifyItemRangeChanged(position, 1, null)
notifyItemChanged(int position)
//相当于调用notifyItemRangeChanged(positionStart, itemCount, null)
notifyItemRangeChanged(int positionStart, int itemCount)
//注意:这个是指item数据部分更新,不是结构性的改变。从positionStart开始的位置总共有itemCount的item项需要更新。
//这些新数据在payload里,如果payload为null,则更新item的所有数据
notifyItemRangeChanged(int positionStart, int itemCount, Object payload)
//相当于调用notifyItemRangeInserted(position, 1)
notifyItemInserted(int position)
//注意:这个是结构性的改变。itemCount个item被插入到从positionStart开始的位置,并且以前位于positionStart位置的item会下移到positionStart+itemCount的位置。
//数据集中此前已经有的数据依然被认为是最新的,虽然某些位置改变了,但是不会造成数据的重新绑定(即这部分数据不会调用onBindViewHolder)。
notifyItemRangeInserted(int positionStart, int itemCount)
//相当于调用notifyItemRangeRemoved(position, 1)
notifyItemRemoved(int position)
//注意:这是个结构性的改变。从positionStart开始的itemCount个item会被移除,位于positionStart+itemCount以及之后的item将会位于oldPosition - itemCount,
//数据集中此前已有的数据依然被认为是最新的,虽然某些item位置改变了,但是不会造成这些数据的重新绑定(即这部分数据不会调用onBindViewHolder)
notifyItemRangeRemoved(int positionStart, int itemCount)
//注意:这个是结构性的改变。把item位于fromPosition移动到toPosition。
//其他位置的数据依然被认为是最新的,虽然某些item位置改变了,但是不会造成从新绑定(即这部分数据不会调用onBindViewHolder)
notifyItemMoved(int fromPosition, int toPosition)
总结:RecyclerView中,notify*Inserted会造成结构性改变,则会通过 onBindViewHolder (两个形参)重新进行数据的显示绑定;notify*Changed只是造成数据的更新,
可以通过 onBindViewHolder (三个形参) 进行局部内容更新,而不需要重新进行数据显示到view的所有操作。关于 onBindViewHolder(VH holder, int position, List<Object> payloads) , 如果不重新,默认直接调用 onBindViewHolder(holder, position); ,并且调用顺序是三个参数的onBindViewHolder 在逻辑上每次必须执行并且先执行。所以我们可以通过在 onBindViewHolder(VH holder, int position, List<Object> payloads)的重写实现中采用下面的逻辑实现一个item的全部更新和部分更新
if (payloads.isEmpty()) { //payloads参数根据官方api说明,肯定不会是null
//表明是首次初始化,需要全部item的更新
onBindViewHolder(holder, position);
} else {
//TODO:根据payloads的设置,进行逻辑判断,实现部分刷新
}
五、结合DiffUtil的使用
好了,上面只是简单的使用,如果更好更方便的使用,不得不提DiffUtil这个类,这个类可以把新旧数据进行比较(通过Myers算法), 找出两者差异,从而自己执行相应的更新(其实是封装调用了notify*几个方法)。
下面通过demo的使用来说明RecyclerView和DiffUtil的使用。
我们demo的设计是:根据类型的不同,实现不同的布局,并且单击item,可以在item上显示高亮效果,并且只有一个item被选中(即单选效果)。 这里的Bean类是Teacher类和Student类,并且是BaseBean的派生类(BaseBean中有个type字段,用于表示bean类类型,从而找到对应的布局), 应用在显示时,前两个是Teacher类相应的布局,后面紧跟着Student类的布局,然后点击更新按钮(第一次点击) 会造成 Student类的数据增加一条, 反应到UI上就是某个位置被插入了这个item显示这条数据(演示添加一条数据);再次点击更新按钮(第二次点击)会造成刚刚添加的item被移除(演示删除一条数据); 第三次点击更新按钮第2条的Teacher类对应数据(position==1)改变了(演示内容修改)以及插入一条Teacher类的布局(position==4),当然此位置之前是个 Student类的布局(演示更换布局)。当然,下面如果你再次点击更新按钮,那么会显示应用刚启动时候的UI,然后再次点击更新按钮就会按照上面第一次第二次点击所述那样。 看描述文字都是枯燥的,想了解具体情况可以执行一下,看看效果就立即清楚了,我在这里贴出代码,以及执行的log,供参考,并且代码注释也已经很清楚了。唯一需要提示一下的是, 为了达到点击item高亮显示,并且只能有一个是高亮色的效果,我们在点击监听里,通过notifyItemChanged对以前选中的行,以及当前选中的行,进行强制刷新,从而来清除或设置背景色, 并且在重新刷新数据时,对于areItemsTheSame方法中对mSelectPos == oldItemPosition的item返回false,从而最终触发调用onBindViewHolder对高亮色清除或添加(当然这样做的设计逻辑 有个副作用,那就是我们强制使被选中的那行在更新前后是不同的item(实际情况可能是这行在更新数据前后是一样的,只是背景色需要不同。),这样就造成了可能多余的操作: DiffUtil机制调用了需要先remove掉那个这个item(调用onRemoved方法),再添加这个item(调用onInserted))。
1、分割线渲染类
/**
* RecyclerView使用的分割线类
*/
class DividerItemDecoration extends RecyclerView.ItemDecoration {
private final int[] ATTRS = new int[] {
android.R.attr.listDivider
};
public static final int VERTICAL = LinearLayoutManager.VERTICAL;
public static final int HORIZONTAL = LinearLayoutManager.HORIZONTAL;
private Drawable mDivider;
private int mOrientation;
public DividerItemDecoration(Context context, int orientation) {
// TypedArray a = context.obtainStyledAttributes(ATTRS);
// mDivider = a.getDrawable(0);
// a.recycle();
mDivider = context.getResources().getDrawable(R.drawable.divider_bg);
setOrientation(orientation);
}
private void setOrientation(int orientation) {
if (orientation != HORIZONTAL && orientation != VERTICAL) {
throw new IllegalArgumentException("invalid orientation");
}
mOrientation = orientation;
}
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
if (layoutManager instanceof LinearLayoutManager) {
if (mOrientation == HORIZONTAL) {
drawHorizontal(c, parent);
} else {
drawVertical(c, parent);
}
} else { //TODO: 网格 或者瀑布流
}
}
/**
* 当是垂直滑动的线性布局时,item之间通过此方法绘制分割线
* @param c
* @param parent
*/
private void drawVertical(Canvas c, RecyclerView parent) {
final int left = parent.getPaddingLeft();
final int right = parent.getWidth() - parent.getPaddingRight();
final int childCount = parent.getChildCount();
//针对每个item来绘制分割线
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
final RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) child.getLayoutParams();
final int top = child.getBottom() + layoutParams.bottomMargin;
final int bottom = top + mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
/**
* 水平滑动时,item之间分割线绘制
* @param c
* @param parent
*/
private void drawHorizontal(Canvas c, RecyclerView parent) {
final int top = parent.getPaddingTop();
final int bottom = parent.getHeight() - parent.getPaddingBottom();
final int childCount = parent.getChildCount();
//针对每个item来绘制分割线
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
final RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) child.getLayoutParams();
final int left = layoutParams.rightMargin + child.getRight();
final int right = left + mDivider.getIntrinsicWidth();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
//用于告诉RecyclerView在绘制每个item的时候在left top right bottom (outRect)应该偏移的像素数
//我们这里把分割线的造成的偏移添加进去
if (mOrientation == VERTICAL) {
outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
} else {
outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
}
}
}
2、使用的适配器类
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.BaseViewHolder> {
private static final int NO_SELECT_POSITION = -1;
private static final String KEY_STUDENT_NAME = "student_name";
private static final String KEY_STUDENT_GRADE = "student_grade";
private static final java.lang.String KEY_TEACHER_NAME = "teacher_name";
private String KEY_STUDENT_ADDRESS = "student_address";
private final LayoutInflater mInflater;
private Context mContext;
//数据集
private List<BaseBean> mDataList;
//item点击响应接口
private OnItemClickListener onItemClickListener;
//用于记录点击的position
private int mSelectPos = NO_SELECT_POSITION;
//表示布局类型
public static final int ITEM_TYPE_ONE = 0;
public static final int ITEM_TYPE_TWO = 1;
public interface OnItemClickListener {
void onItemClick(View view, int position, BaseBean data);
}
public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
this.onItemClickListener = onItemClickListener;
}
public MyAdapter(Context context, List<BaseBean> list) {
mContext = context;
mDataList = list;
mInflater = LayoutInflater.from(mContext);
}
@Override
public BaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
BaseViewHolder holder = null;
if (viewType == ITEM_TYPE_ONE) {
holder = new TeacherViewHolder(mInflater.inflate(R.layout.teacher_item, parent, false));
} else if (viewType == ITEM_TYPE_TWO){
holder = new StudentViewHolder(mInflater.inflate(R.layout.student_item, parent, false));
}
return holder;
}
/**
* 当{@link DiffCallBack#getChangePayload}检测到不同内容或者首次初始化时调用
* 只要布局要获取新的item view时(增加item view)或者某个item内容更新 都会先调用这里
* @param holder
* @param position
* @param payloads
*/
@Override
public void onBindViewHolder(BaseViewHolder holder, int position, List<Object> payloads) {
if (payloads.isEmpty()) {
//表明是首次初始化,注意,payloads肯定不会为null(参看官方api说明)
System.out.println("----------三个参数--------");
onBindViewHolder(holder, position);
} else {
//表明某个item的内容进行了更新
System.out.println("------------------只是更新内容-------------payloads--------onBindView-------position:" + position);
/**
* 对一行的内容进行局部更新
*/
Bundle diffBundle = (Bundle) payloads.get(0);
if (getItemViewType(position) == ITEM_TYPE_ONE) {
TeacherViewHolder teacherViewHolder = (TeacherViewHolder)holder;
String teacherName = diffBundle.getString(KEY_TEACHER_NAME);
teacherViewHolder.teacherNameView.setText(teacherName);
} else {
StudentViewHolder studentViewHolder = (StudentViewHolder)holder;
String studentName = diffBundle.getString(KEY_STUDENT_NAME);
if (studentName != null) {
studentViewHolder.studentNameView.setText(studentName);
}
String studentGrade = diffBundle.getString(KEY_STUDENT_GRADE);
if (studentGrade != null) {
studentViewHolder.studentGradeView.setText(studentGrade);
}
String studentAddress = diffBundle.getString(KEY_STUDENT_ADDRESS);
if (studentAddress != null) {
studentViewHolder.studentAddressView.setText(studentAddress);
}
}
}
}
/**
* 实际执行初始化item view对象操作
* @param holder
* @param position
*/
@Override
public void onBindViewHolder(final BaseViewHolder holder, int position) {
if (getItemViewType(position) == ITEM_TYPE_ONE) {
TeacherViewHolder teacherViewHolder = (TeacherViewHolder)holder;
Teacher teacher = (Teacher)mDataList.get(position);
teacherViewHolder.teacherNameView.setText(teacher.getTeacherName());
System.out.println("--------------------teacher--获取view-----------------onBindView-------position:" + position + " itemcount:" + mDataList.size());
} else {
StudentViewHolder studentViewHolder = (StudentViewHolder)holder;
Student student = (Student) mDataList.get(position);
System.out.println("----------------------student--获取view---------------onBindView-------position:" + position + " itemcount:" + mDataList.size());
studentViewHolder.studentNameView.setText(student.getStudentName());
studentViewHolder.studentAddressView.setText(student.getAddress());
studentViewHolder.studentGradeView.setText(student.getGrade());
}
if (position == mSelectPos) {
//点击的位置和这个刷新的位置是同一个,高亮选中行
holder.itemView.setBackgroundDrawable(mContext.getResources().getDrawable(android.R.color.holo_orange_light));
} else {
/**
* 1、点击的位置和刷新行位置不相同,说明需要对此行清除高亮色
* 2、由于holder是复用机制,所以要对每行都要设置无背景色(情形:例如 选中一行高亮后,然后刷新重新加载数据显示的时候,可能会有一行具有高亮色,而按照用户角度,此时不应该有被选中的行)
*/
holder.itemView.setBackgroundDrawable(null);
}
/**单击中不要使用上面的参数position,应使用getAdapterPosition()获取位置
* 参见{@link RecyclerView.Adapter#onBindViewHolder(RecyclerView.ViewHolder, int)注释}
*/
if (onItemClickListener != null) {
holder.itemView.setOnClickListener(new View.OnClickListener() {
int oldSelectPos = NO_SELECT_POSITION;
@Override
public void onClick(View v) {
final int pos = holder.getAdapterPosition();// 每次获得最新的adapter数据集中的位置(mDataList中)
oldSelectPos = mSelectPos;
//更新选中行位置
mSelectPos = pos;
if (oldSelectPos != pos) { //对上次点击的行再次点击,则不会刷新高亮了
if (oldSelectPos != NO_SELECT_POSITION) {
/**
* 先对上一次选中的行进行刷新,用于清除高亮背景
* notifyItemChanged会触发{@link RecyclerView.Adapter#onBindViewHolder(RecyclerView.ViewHolder, int)}
*/
notifyItemChanged(oldSelectPos);
}
//对点击的行进行刷新,用于增加高亮背景
notifyItemChanged(mSelectPos);
}
//回调,通知单击了某一行
onItemClickListener.onItemClick(v, pos, mDataList.get(mSelectPos));
}
});
}
}
@Override
public int getItemViewType(int position) {
return mDataList.get(position).getType();
}
@Override
public int getItemCount() {
return mDataList.size();
}
abstract class BaseViewHolder extends RecyclerView.ViewHolder {
public BaseViewHolder(View itemView) {
super(itemView);
}
}
class TeacherViewHolder extends BaseViewHolder {
private TextView teacherNameView;
TeacherViewHolder(View itemView) {
super(itemView);
teacherNameView = (TextView) itemView.findViewById(R.id.tv_teacher_name);
}
}
class StudentViewHolder extends BaseViewHolder {
private TextView studentAddressView;
private TextView studentGradeView;
private TextView studentNameView;
StudentViewHolder(View itemView) {
super(itemView);
studentNameView = (TextView) itemView.findViewById(R.id.tv_student_name);
studentAddressView = (TextView) itemView.findViewById(R.id.tv_student_address);
studentGradeView = (TextView) itemView.findViewById(R.id.tv_student_grade);
}
}
private class DiffCallBack extends DiffUtil.Callback {
//用于存储新旧数据,即更新前是旧数据,更新时使用的数据是新数据
private List<BaseBean> mOldDatas, mNewDatas;
DiffCallBack(List<BaseBean> mOldDatas, List<BaseBean> mNewDatas) {
this.mOldDatas = mOldDatas;
this.mNewDatas = mNewDatas;
}
@Override
public int getOldListSize() {
return mOldDatas != null ? mOldDatas.size() : 0;
}
@Override
public int getNewListSize() {
return mNewDatas != null ? mNewDatas.size() : 0;
}
/**
* 表明对应的item是否相同,用于比较两个对象代表的是否是同一个item
* @param oldItemPosition
* @param newItemPosition
* @return
*/
@Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
if (mOldDatas.get(oldItemPosition).getType() != mNewDatas.get(newItemPosition).getType()) {
//不是同一个类型的数据
return false;
}
//这里我们强制设置false使点击的item必须刷新
if (mSelectPos == oldItemPosition) {
//当刷新和排序操作后,会造成选中的高亮色消失(返回false会触发此行调用onBindViewHolder,从而对高亮色进行了清除)
return false;
}
String oldItem;
String newItem;
//到达这里已经说明两者是同一个类型的了
if (mNewDatas.get(newItemPosition).getType() == ITEM_TYPE_ONE) {
oldItem = ((Teacher)mOldDatas.get(oldItemPosition)).getTeacherId();
newItem = ((Teacher)mNewDatas.get(newItemPosition)).getTeacherId();
} else {
//要根据studyUID来判断新旧数据是否是同一项
oldItem = ((Student) mOldDatas.get(oldItemPosition)).getStudentId();
newItem = ((Student) mNewDatas.get(newItemPosition)).getStudentId();
}
return oldItem.equals(newItem);
}
/**
* 此方法得到执行的条件是{@link #areItemsTheSame(int, int)}返回true
* 用于检验某个item的数据内容是否改变
* @param oldItemPosition
* @param newItemPosition
* @return
*/
@Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
//此时数据肯定已经是同一个类型的数据了
if (mNewDatas.get(newItemPosition).getType() == ITEM_TYPE_ONE) {
String oldTeacherName = ((Teacher)mOldDatas.get(oldItemPosition)).getTeacherName();
String newTeacherName = ((Teacher)mNewDatas.get(newItemPosition)).getTeacherName();
return newTeacherName.equals(oldTeacherName);
} else {
String oldStudentName = ((Student)mOldDatas.get(oldItemPosition)).getStudentName();
String newStudentName = ((Student)mNewDatas.get(newItemPosition)).getStudentName();
String oldAddress = ((Student)mOldDatas.get(oldItemPosition)).getAddress();
String newAddress = ((Student)mNewDatas.get(newItemPosition)).getAddress();
String oldGrade = ((Student)mOldDatas.get(oldItemPosition)).getGrade();
String newGrade = ((Student)mNewDatas.get(newItemPosition)).getGrade();
//根据显示的两列内容完全相同作为某一行在数据集变化前后是否相同
return oldStudentName.equals(newStudentName) && oldAddress.equals(newAddress) && oldGrade.equals(newGrade);
}
}
/**
* 此方法也执行的前提条件是{@link #areItemsTheSame(int, int)}返回true,并且{@link #areContentsTheSame(int, int)}返回false
* 用于获取改变的数据内容
* @param oldItemPosition
* @param newItemPosition
* @return
*/
@Nullable
@Override
public Object getChangePayload(int oldItemPosition, int newItemPosition) {
Bundle diffBundle = new Bundle();
if (mNewDatas.get(newItemPosition).getType() == ITEM_TYPE_ONE) {
String newTeacherName = ((Teacher)mNewDatas.get(newItemPosition)).getTeacherName();
//因为进行显示的内容只有名字,所以不需判断,直接就是这个不同了
diffBundle.putString(KEY_TEACHER_NAME,newTeacherName);
} else {
String oldStudentName = ((Student)mOldDatas.get(oldItemPosition)).getStudentName();
String newStudentName = ((Student)mNewDatas.get(newItemPosition)).getStudentName();
String oldStudentAddress = ((Student)mOldDatas.get(oldItemPosition)).getAddress();
String newStudentAddress = ((Student)mNewDatas.get(newItemPosition)).getAddress();
String oldStudentGrade = ((Student)mOldDatas.get(oldItemPosition)).getGrade();
String newStudentGrade = ((Student)mNewDatas.get(newItemPosition)).getGrade();
if (!newStudentName.equals(oldStudentName)) {
diffBundle.putString(KEY_STUDENT_NAME, newStudentName);
}
if (!newStudentAddress.equals(oldStudentAddress)) {
diffBundle.putString(KEY_STUDENT_ADDRESS, newStudentAddress);
}
if (!newStudentGrade.equals(oldStudentGrade)) {
diffBundle.putString(KEY_STUDENT_GRADE, newStudentGrade);
}
}
return diffBundle;
}
}
ListUpdateCallback listUpdateCallback = new ListUpdateCallback() {
/**
* 从{@code position}开始(包含)添加{@code count}个项时调用
* @param position
* @param count
*/
@Override
public void onInserted(int position, int count) {
MyAdapter.this.notifyItemRangeInserted(position, count);
System.out.println("----------onInserted------------------------------------position: "+position+" count: "+count);
}
/**
* 删除从{@code position}开始(包含)的{@code count}项时调用
* @param position
* @param count
*/
@Override
public void onRemoved(int position, int count) {
MyAdapter.this.notifyItemRangeRemoved(position, count);
System.out.println("----------onRemoved-------------------------------------position: "+position+" count: "+count);
}
/**
*
* @param fromPosition
* @param toPosition
*/
@Override
public void onMoved(int fromPosition, int toPosition) {
MyAdapter.this.notifyItemMoved(fromPosition, toPosition);
System.out.println("--------------onMoved-----------------------------------fromPosition: "+fromPosition+" toPosition: "+toPosition);
}
@Override
public void onChanged(int position, int count, Object payload) {
MyAdapter.this.notifyItemRangeChanged(position, count, payload);
System.out.println("----------onChanged--------------------------------------position: "+position+" count: "+count);
}
};
/**
* 刷新显示,并保存新数据内容
* @param list
*/
public void replaceData(final List<BaseBean> list) {
AsyncTask<Object, Void, DiffUtil.DiffResult> asyncTask = new AsyncTask<Object, Void, DiffUtil.DiffResult>() {
@Override
protected DiffUtil.DiffResult doInBackground(Object... params) {
List<BaseBean> listInfos = (List<BaseBean>) params[0];
DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DiffCallBack(mDataList, listInfos), true);
return diffResult;
}
@Override
protected void onPostExecute(DiffUtil.DiffResult diffResult) {
mSelectPos = NO_SELECT_POSITION;
//注意:我们这里不是使用直接赋值,而是重新生成list集,否则由于java中是索引,会导致数据更新时,这里的mDataList立即就是已经变化后的了
mDataList = new ArrayList<>(list);
/**
*如果没有其他逻辑处理我们可以直接使用diffResult.dispatchUpdatesTo(MyAdapter.this);
* 其实默认的形式和我们这里写的是一样的
*/
diffResult.dispatchUpdatesTo(listUpdateCallback);
}
};
//因为DiffUtil的比较比较耗时,如果数据量大的情况下应该开启一个线程
asyncTask.execute(list);
}
}
3、使用的bean类
public class BaseBean implements Serializable {
private int type;
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
}
public class Student extends BaseBean {
private String studentId;
private String studentName;
private String address;
private String grade;
private boolean isChecked;
public String getGrade() {
return grade;
}
public void setGrade(String grade) {
this.grade = grade;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getStudentId() {
return studentId;
}
public void setStudentId(String studentId) {
this.studentId = studentId;
}
public String getStudentName() {
return studentName;
}
public void setStudentName(String studentName) {
this.studentName = studentName;
}
public boolean isChecked() {
return isChecked;
}
public void setChecked(boolean checked) {
isChecked = checked;
}
}
public class Teacher extends BaseBean {
private String teacherName;
private String teacherId;
public String getTeacherId() {
return teacherId;
}
public void setTeacherId(String teacherId) {
this.teacherId = teacherId;
}
public String getTeacherName() {
return teacherName;
}
public void setTeacherName(String teacherName) {
this.teacherName = teacherName;
}
}
4、MainActivity类
public class MainActivity extends Activity {
private RecyclerView mMyRecyclerView;
private Button mUpdateBtn;
private MyAdapter mAdapter;
private ProgressBar mProgressLoading;
//用于存储模拟的数据
public ArrayList<BaseBean> mDataList;
//为了获取模拟数据而使用的变量
int variantForTest;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initViews();
setRecyclerView();
setListeners();
}
@Override
protected void onResume() {
super.onResume();
//这里只是使刷新数据进入最初始状态
variantForTest = 0;
loadData();
}
private void setRecyclerView() {
mMyRecyclerView.setHasFixedSize(true);
mAdapter = new MyAdapter(this, new ArrayList<BaseBean>(0));
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
//设置布局管理器
mMyRecyclerView.setLayoutManager(linearLayoutManager);
//设置适配器
mMyRecyclerView.setAdapter(mAdapter);
//设置Item增加、移除动画
mMyRecyclerView.setItemAnimator(new DefaultItemAnimator());
//添加分割线
mMyRecyclerView.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL));
}
private void initViews() {
mMyRecyclerView = (RecyclerView) findViewById(R.id.rv_worklist);
mUpdateBtn = (Button) findViewById(R.id.btn_update);
mProgressLoading = (ProgressBar) findViewById(R.id.pb_loading);
}
private void setListeners() {
mUpdateBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
loadData();
}
});
//自定义的处理点击事件
mAdapter.setOnItemClickListener(new MyAdapter.OnItemClickListener() {
@Override
public void onItemClick(View view, int position, BaseBean data) {
Toast.makeText(MainActivity.this, "点击了position:" + position, Toast.LENGTH_SHORT).show();
}
});
}
private void loadData() {
mProgressLoading.setVisibility(View.VISIBLE);
Thread fetchThread = new Thread(new Runnable() {
@Override
public void run() {
SystemClock.sleep(2000);
getData(variantForTest);
runOnUiThread(new Runnable() {
@Override
public void run() {
mProgressLoading.setVisibility(View.INVISIBLE);
}
});
mAdapter.replaceData(mDataList);
variantForTest++;
}
/**
* 模拟每次获取新数据
* 注意:这里每次都是重新初始化{@code mDataList},并且初始化里面的数据对象。
* 如果不是这样做,只是对链表里的数据进行更新,由于java中对象时引用,
* 所以这里的对象修改,立即会导致{@link MyAdapter} 中数据的内容的更改,所以就不能符合{@link android.support.v7.util.DiffUtil}部分更新的原理了
* 当然在实际使用中,由于数据都是从网络上等获取到的,不会出现上面说的问题
* @param test
*/
private void getData(int test) {
mDataList = new ArrayList<>();
int index = test % 4;
if (index == 0) {
int i = 0;
for (i = 0; i < 2; i++) {
Teacher teacher = new Teacher();
teacher.setTeacherId(i + "");
teacher.setType(MyAdapter.ITEM_TYPE_ONE);
teacher.setTeacherName("teacher name" + i);
mDataList.add(teacher);
}
for (i = 0; i < 6; i++) {
Student student = new Student();
student.setStudentId(i + ""); //id必须是唯一的
student.setType(MyAdapter.ITEM_TYPE_TWO);//这个必须设置
student.setStudentName("stu name"+ i);
student.setAddress("stu address"+i);
student.setGrade("grade" + i);
mDataList.add(student);
}
} else if (index == 1) {
int i = 0;
for (i = 0; i < 2; i++) {
Teacher teacher = new Teacher();
teacher.setTeacherId(i + "");
teacher.setType(MyAdapter.ITEM_TYPE_ONE);
teacher.setTeacherName("teacher name" + i);
mDataList.add(teacher);
}
for (i = 0; i < 6; i++) {
Student student = new Student();
student.setStudentId(i + ""); //id必须是唯一的
student.setType(MyAdapter.ITEM_TYPE_TWO);//这个必须设置
student.setStudentName("stu name"+ i);
student.setAddress("stu address"+i);
student.setGrade("grade" + i);
mDataList.add(student);
}
//在位置4处添加一条数据
Student student = new Student();
student.setStudentId(i + ""); //id必须是唯一的
student.setType(MyAdapter.ITEM_TYPE_TWO);//这个必须设置
student.setStudentName("add student");
student.setAddress("add student address");
student.setGrade("add student grade");
mDataList.add(4,student);
} else if (index == 2) {
int i = 0;
for (i = 0; i < 2; i++) {
Teacher teacher = new Teacher();
teacher.setTeacherId(i + "");
teacher.setType(MyAdapter.ITEM_TYPE_ONE);
teacher.setTeacherName("teacher name" + i);
mDataList.add(teacher);
}
//相当于index==1时添加的一行去除
for (i = 0; i < 6; i++) {
Student student = new Student();
student.setStudentId(i + ""); //id必须是唯一的
student.setType(MyAdapter.ITEM_TYPE_TWO);//这个必须设置
student.setStudentName("stu name"+ i);
student.setAddress("stu address"+i);
student.setGrade("grade" + i);
mDataList.add(student);
}
} else { //index==3
int i = 0;
for (i = 0; i < 2; i++) {
Teacher teacher = new Teacher();
teacher.setTeacherId(i + "");
teacher.setType(MyAdapter.ITEM_TYPE_ONE);
teacher.setTeacherName("teacher name" + i);
if (i == 1) {
//更新字段,position==1处的teacher布局中更新内容了
teacher.setTeacherName("update teacher name" + i);
}
mDataList.add(teacher);
}
for (i = 0; i < 6; i++) {
Student student = new Student();
student.setStudentId(i + ""); //id必须是唯一的
student.setType(MyAdapter.ITEM_TYPE_TWO);//这个必须设置
student.setStudentName("stu name"+ i);
student.setAddress("stu address"+i);
student.setGrade("grade" + i);
mDataList.add(student);
}
//这里会导致position==4处student的布局替换成teacher的布局
Teacher teacher = new Teacher();
teacher.setTeacherId(i + "");
teacher.setType(MyAdapter.ITEM_TYPE_ONE);
teacher.setTeacherName("replaced by teacher item " );
mDataList.add(4, teacher);
}
}
});
fetchThread.start();
}
}
5、资源divider_bg.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<solid android:color="@android:color/black"/>
<size android:height="1dp" android:width="1dp"/>
</shape>
6、布局文件
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.recyclerview.demo.MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="40dp"
android:orientation="horizontal">
<Button
android:id="@+id/btn_update"
style="?android:attr/buttonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="update"
android:textAllCaps="false" />
</LinearLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="@+id/rv_worklist"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ProgressBar
android:id="@+id/pb_loading"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_centerInParent="true" />
</RelativeLayout>
</FrameLayout>
</LinearLayout>
student_item.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:minHeight="40dp">
<TextView
android:id="@+id/tv_student_name"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:text="name"
android:singleLine="true"
android:maxLines="1"
android:ellipsize="end"
android:gravity="center_vertical"/>
<ImageView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="@drawable/divider_bg" />
<TextView
android:id="@+id/tv_student_grade"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="grade"
android:layout_weight="1"
android:ems="5"
android:singleLine="true"
android:maxLines="1"
android:ellipsize="end"
android:gravity="center_vertical"/>
<ImageView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="@drawable/divider_bg" />
<TextView
android:id="@+id/tv_student_address"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="adress"
android:layout_weight="1"
android:ems="5"
android:singleLine="true"
android:maxLines="1"
android:ellipsize="end"
android:gravity="center_vertical"/>
</LinearLayout>
teacher_item.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:minHeight="40dp"
android:orientation="horizontal">
<TextView
android:id="@+id/tv_teacher_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:ellipsize="end"
android:gravity="center_vertical"
android:maxLines="1"
android:singleLine="true"
android:text="name" />
</LinearLayout>
六、根据上面代码,打印的log
应用刚启动时打印:
06-25 18:48:36.301 7311-7311/com.recyclerview.demo I/System.out: ----------onInserted------------------------------------position: 0 count: 8
06-25 18:48:36.331 7311-7311/com.recyclerview.demo I/System.out: ----------两个参数--------
06-25 18:48:36.341 7311-7311/com.recyclerview.demo I/System.out: --------------------teacher--获取view-----------------onBindView-------position:0 itemcount:8
06-25 18:48:36.341 7311-7311/com.recyclerview.demo I/System.out: ----------两个参数--------
06-25 18:48:36.341 7311-7311/com.recyclerview.demo I/System.out: --------------------teacher--获取view-----------------onBindView-------position:1 itemcount:8
06-25 18:48:36.341 7311-7311/com.recyclerview.demo I/System.out: ----------两个参数--------
06-25 18:48:36.341 7311-7311/com.recyclerview.demo I/System.out: ----------------------student--获取view---------------onBindView-------position:2 itemcount:8
06-25 18:48:36.351 7311-7311/com.recyclerview.demo I/System.out: ----------两个参数--------
06-25 18:48:36.351 7311-7311/com.recyclerview.demo I/System.out: ----------------------student--获取view---------------onBindView-------position:3 itemcount:8
06-25 18:48:36.351 7311-7311/com.recyclerview.demo I/System.out: ----------两个参数--------
06-25 18:48:36.351 7311-7311/com.recyclerview.demo I/System.out: ----------------------student--获取view---------------onBindView-------position:4 itemcount:8
06-25 18:48:36.361 7311-7311/com.recyclerview.demo I/System.out: ----------两个参数--------
06-25 18:48:36.361 7311-7311/com.recyclerview.demo I/System.out: ----------------------student--获取view---------------onBindView-------position:5 itemcount:8
06-25 18:48:36.361 7311-7311/com.recyclerview.demo I/System.out: ----------两个参数--------
06-25 18:48:36.361 7311-7311/com.recyclerview.demo I/System.out: ----------------------student--获取view---------------onBindView-------position:6 itemcount:8
06-25 18:48:36.371 7311-7311/com.recyclerview.demo I/System.out: ----------两个参数--------
06-25 18:48:36.371 7311-7311/com.recyclerview.demo I/System.out: ----------------------student--获取view---------------onBindView-------position:7 itemcount:8
在position==4处添加student的item时打印:
06-25 18:49:33.991 7311-7311/com.recyclerview.demo I/System.out: ----------onInserted------------------------------------position: 4 count: 1
06-25 18:49:34.001 7311-7311/com.recyclerview.demo I/System.out: ----------两个参数--------
06-25 18:49:34.011 7311-7311/com.recyclerview.demo I/System.out: ----------------------student--获取view---------------onBindView-------position:4 itemcount:9
在position==4处的student的item删除时打印
06-25 18:51:11.711 7311-7311/com.recyclerview.demo I/System.out: ----------onRemoved-------------------------------------position: 4 count: 1
在position==1处的teacher的item内容更新,并且position==4处添加teacher的item(这个位置添加前是student的item)时打印,由于这步进行了两个操作,所以对打印内容需要解释一下第二行和第三行,是对position==1的内容进行更新时打印的log,而剩余的部分是在position==4处添加一行时的log
06-25 18:52:39.201 7311-7311/com.recyclerview.demo I/System.out: ----------onInserted------------------------------------position: 4 count: 1
06-25 18:52:39.201 7311-7311/com.recyclerview.demo I/System.out: ----------onChanged--------------------------------------position: 1 count: 1
06-25 18:52:39.221 7311-7311/com.recyclerview.demo I/System.out: ------------------只是更新内容-------------payloads--------onBindView-------position:1
06-25 18:52:39.221 7311-7311/com.recyclerview.demo I/System.out: ----------两个参数--------
06-25 18:52:39.221 7311-7311/com.recyclerview.demo I/System.out: --------------------teacher--获取view-----------------onBindView-------position:4 itemcount:9
另外,还有个现象在此demo中没有代码实现:上面是在position==4处添加teacher的item,如果不是添加而是替换呢?读者可以模拟这样的数据(其实就是对模拟数据部分的index==3处mDataList.add(4, teacher)修改为mDataList.set(4, teacher)),这里给出结论:首先会执行把position==4处的student的item移除掉,然后再在position==4处插入teacher的item项,对应log如下
06-25 19:02:45.521 22386-22386/com.recyclerview.demo I/System.out: ----------onRemoved-------------------------------------position: 4 count: 1
06-25 19:02:45.521 22386-22386/com.recyclerview.demo I/System.out: ----------onInserted------------------------------------position: 4 count: 1
06-25 19:02:45.521 22386-22386/com.recyclerview.demo I/System.out: ----------onChanged--------------------------------------position: 1 count: 1
06-25 19:02:45.531 22386-22386/com.recyclerview.demo I/System.out: ------------------只是更新内容-------------payloads--------onBindView-------position:1
06-25 19:02:45.541 22386-22386/com.recyclerview.demo I/System.out: ----------两个参数--------
06-25 19:02:45.541 22386-22386/com.recyclerview.demo I/System.out: --------------------teacher--获取view-----------------onBindView-------position:4 itemcount:8