RecyclerView可以说是做Android应用开发使用最广的几个控件之一。它是在Android 5.0版本引入进来的,位于support-v7包中,可以通过在build.gradle中添加如下代码将它引入到项目中:
implementation 'com.android.support:recyclerview-v7:27.1.1'
在RecyclerView出现之前Android中的复用列表大多通过ListView实现的,RecyclerView并不会完全替代ListView,至少现在来看ListView并没有被废弃掉。RecyclerView和ListView互有优势。
RecylerView的使用几个必须的步骤如下:
- 创建Adapter,并设置给RecylerView
- 为RecylerView设置LayoutManager
这两个是RecylerView使用的必须步骤,可以看出
RecylerView和ListView的使用方法相比只是多了一步设置LayoutManager。
通过代码来看Adapter的具体实现:
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {
private Context context;
private List mDatas;
public MyAdapter(Context context , List mDatas){
this.context = context;
this.mDatas = mDatas;
}
@NonNull
@Override
public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return new MyViewHolder(LayoutInflater.from(context).inflate(R.layout.item_layout,parent,false));
}
@Override
public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
holder.textView.setText("第"+position);
}
@Override
public int getItemCount() {
return mDatas.size();
}
public class MyViewHolder extends RecyclerView.ViewHolder{
private TextView textView;
public MyViewHolder(View itemView) {
super(itemView);
textView = itemView.findViewById(R.id.textview);
}
}
}
将Adapter和LayoutManager设置给RecylerView:
myAdapter = new MyAdapter(this,datas);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
mRl.setLayoutManager(linearLayoutManager);
mRl.setAdapter(myAdapter);
这样一个最基本的RecyclerView就实现了。
在前面基本使用的代码中,给RecyclerView设置的是LinearLayoutManager,其实RecyclerView还提供了另外两种布局管理器,这就是GridLayoutManager和StaggeredGridLayoutManager,通过它们可以很简单的分别实现表格布局和瀑布流布局。
通过GridLayoutManager可以让RecyclerVie实现GridView的效果。GridLayoutManager的使用方式如下:
//4可以理解为一行显示4列
GridLayoutManager linearLayoutManager = new GridLayoutManager(this,4);
mRl.setLayoutManager(linearLayoutManager);
通过StaggeredGridLayoutManager可以让RecyclerVie实现瀑布流的效果。StaggeredGridLayoutManager的使用方式如下:
StaggeredGridLayoutManager linearLayoutManager = new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);
当然也可以根据需求自定义LayoutManager.通过源码就可以可以看到GridLayoutManager其实也是继承了LinearLayoutManager,相当于自定义一个LinearLayoutManager,把一行一列,变为一行多列。
ItemDecoration其实翻译过来就是item装饰品,所以它不仅仅是用来绘制分割线,它可以绘制出各式各样的的itemview的装饰界面。只是由于RecyclerView中并没有提供类似android:divider的方法来实现分割线,所以往往通过ItemDecoration来实现各种分割线效果。
ItemDecoration是一个抽象类,主要有下面3个方法:
//用于撑开整个itemview
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state)
//用于绘制具体的分隔线形状,在itemview前面绘制,有可能被itemview覆盖
public void onDraw(Canvas c, RecyclerView parent, State state)
//它是在itemview之后绘制,可以覆盖itemview的内容
public void onDrawOver(Canvas c, RecyclerView parent, State state)
通过代码来实现一条绿色的分割线:
public class MyItemDecoration extends RecyclerView.ItemDecoration{
private int mDiverHeight;
private Paint mPaint;
public MyItemDecoration(){
mPaint = new Paint();
mPaint.setColor(Color.GREEN);
}
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDraw(c, parent, state);
int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
View view = parent.getChildAt(i);
if (parent.getChildAdapterPosition(view) !=0){
float top = view.getTop()-mDiverHeight;
float left = parent.getPaddingLeft();
float right = parent.getWidth()-parent.getPaddingRight();
float bottom = view.getTop();
c.drawRect(left,top,right,bottom,mPaint);
}
}
}
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDrawOver(c, parent, state);
//
// int childCount = parent.getChildCount();
//
// for (int i = 0; i < childCount; i++) {
// View view = parent.getChildAt(i);
//
// if (parent.getChildAdapterPosition(view) !=0){
// float top = view.getTop()-mDiverHeight;
// float left = parent.getPaddingLeft();
// float right = parent.getWidth()-parent.getPaddingRight();
// float bottom = view.getTop();
//
// c.drawRect(left,top,right,bottom,mPaint);
// }
// }
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
if (parent.getChildLayoutPosition(view) != 0){
outRect.top = 10;
mDiverHeight = 40;
}
}
}
分析一下上面的代码,
1. 首先在getItemOffsets中利用 outRect.top在itemview的顶部撑开了10px的高度,将mDiverHeight的值设置为40px.
2. onDraw方法中具体绘制分隔线样式
3. onDrawOver方法中同样的代码绘制分割线
分别看下在不同方法中绘制出来的效果:
图一,虽然mDiverHeight的高度设置为40px,但并没有显示那么高,因为在getItemOffsets方法中只将itemview撑开了10px,进一步验证了onDraw绘制的分隔线有可能被itemview覆盖。
图二,mDiverHeight的高度设置为40px,实际显示也是40px,虽然在getItemOffsets方法中只将itemview撑开了10px,但是分隔线覆盖掉了itemview部分界面,也验证了onDrawOver绘制的分隔线会覆盖itemview。
总结,在熟悉了onDraw和onDrawOver方法之后可以利用它们绘制出多种多样的itemview装饰界面。
通过ItemAnimator可以设置Item添加、删除、更新的动画,即使没有设置它也会看到动画,因为RecyclerView设置了默认动画DefaultItemAnimator,通过代码来看下DefaultItemAnimator的效果:
DefaultItemAnimator defaultItemAnimator = new DefaultItemAnimator();
defaultItemAnimator.setAddDuration(1000);//设置时间长一点,容易看出效果
mRl.setItemAnimator(defaultItemAnimator);
@Override
public boolean animateAdd(final RecyclerView.ViewHolder holder) {
resetAnimation(holder);
holder.itemView.setAlpha(0);
mPendingAdditions.add(holder);
//新增平移动画,默认将itemview左移自身宽度
ViewCompat.setTranslationX(holder.itemView,-holder.itemView.getWidth());
mPendingAdditions.add(holder);
return true;
}
void animateAddImpl(final RecyclerView.ViewHolder holder) {
final View view = holder.itemView;
final ViewPropertyAnimator animation = view.animate();
mAddAnimations.add(holder);
//新增translationX(0),将itemview平移出来
animation.alpha(1).translationX(0).setDuration(getAddDuration())
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animator) {
dispatchAddStarting(holder);
}
@Override
public void onAnimationCancel(Animator animator) {
ViewCompat.setAlpha(view, 1);
}
@Override
public void onAnimationEnd(Animator animator) {
animation.setListener(null);
dispatchAddFinished(holder);
mAddAnimations.remove(holder);
dispatchFinishedWhenDone();
}
}).start();
}
效果如下:
RecyclerView的item侧滑功能主要是通过ItemTouchHelper实现的,它是一个支持RecyclerView滑动删除和拖拽的实用工具类,使用也很简单:
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(mCallBack);
itemTouchHelper.attachToRecyclerView(mRl);
所以只需要关注这里的mCallBack,看下它的实现:
public ItemTouchHelper.Callback mCallBack = new ItemTouchHelper.Callback() {
/**
* 指定可以拖拽(drag)和滑动(swipe)的方向
* @param recyclerView
* @param viewHolder
* @return
*/
@Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
return 0;
}
/**
* 拖拽回调
* @param recyclerView
* @param viewHolder
* @param target
* @return
*/
@Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
return false;
}
/**
* 滑动回调
* @param viewHolder
* @param direction
*/
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
}
};
但是在实际使用的时候更多的是通过SimpleCallback,它是继承至ItemTouchHelper.Callback,通过代码来实现一个简单的带有侧滑和拖拽的效果:
ItemTouchHelper.SimpleCallback mCallback =
new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP |
ItemTouchHelper.DOWN | ItemTouchHelper.LEFT |ItemTouchHelper.RIGHT,
ItemTouchHelper.START | ItemTouchHelper.END) {
@Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
int fromPosition = viewHolder.getAdapterPosition();//当前ViewHolder的position
int toPosition = target.getAdapterPosition();//目标ViewHolder的position
//交换位置
if (fromPosition < toPosition) {
for (int i = fromPosition; i < toPosition; i++) {
Collections.swap(datas, i, i + 1);
}
} else {
for (int i = fromPosition; i > toPosition; i--) {
Collections.swap(datas, i, i - 1);
}
}
myAdapter.notifyItemMoved(fromPosition, toPosition);
return true;
}
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
int position = viewHolder.getAdapterPosition();
datas.remove(position);//删除数据
myAdapter.notifyItemRemoved(position);
}
@Override
public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
//滑动时改变Item的透明度
final float alpha = 1 - Math.abs(dX) / (float) viewHolder.itemView.getWidth();
viewHolder.itemView.setAlpha(alpha);
viewHolder.itemView.setTranslationX(dX);
}
}
};
然后看下效果图:
RecyclerView的常用功能里面还有就是多种状态布局,也就是用一个RecyclerView实现有多种状态的布局,比如给RecyclerView增加一个Header和Footer
然后中间是常规数据,主要通过Adapter的getItemViewType方法来区分不同的布局。修改Adapter的代码如下:
public class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private Context context;
private List mDatas;
public static final int ITME_TYPE_HEADER = 1;
public static final int ITME_TYPE_CONTENT = 2;
public static final int ITME_TYPE_BOTTOM = 3;
private int mHeaderCount = 1;
private int mBottmoCount = 1;
public MyAdapter(Context context , List mDatas){
this.context = context;
this.mDatas = mDatas;
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
switch (viewType){
case ITME_TYPE_HEADER:
return new HeadViewHolder(LayoutInflater.from(context).inflate(R.layout.item_header,parent,false));
case ITME_TYPE_CONTENT:
return new MyViewHolder(LayoutInflater.from(context).inflate(R.layout.item_layout,parent,false));
case ITME_TYPE_BOTTOM:
return new BottomViewHolder(LayoutInflater.from(context).inflate(R.layout.item_header,parent,false));
}
return null;
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
if (holder instanceof HeadViewHolder){
((HeadViewHolder) holder).textView_header.setText("我是头");
}else if (holder instanceof MyViewHolder){
//注意这里要减去头的数量
((MyViewHolder) holder).textView.setText("第"+(position-mHeaderCount));
}else if (holder instanceof BottomViewHolder){
((BottomViewHolder) holder).textView_bottom.setText("我是尾");
}
}
/**
* 要加上头尾
* @return
*/
@Override
public int getItemCount() {
return mDatas.size()+mHeaderCount+mBottmoCount;
}
/**
* 根据position来区分布局类型
* @param position
* @return
*/
@Override
public int getItemViewType(int position) {
if (positionreturn ITME_TYPE_HEADER;
}else if(position>= mHeaderCount+mDatas.size()){
return ITME_TYPE_BOTTOM;
}else{
return ITME_TYPE_CONTENT;
}
}
public class MyViewHolder extends RecyclerView.ViewHolder{
private TextView textView;
public MyViewHolder(View itemView) {
super(itemView);
textView = itemView.findViewById(R.id.textview);
}
}
//头部ViewHolder
public static class HeadViewHolder extends RecyclerView.ViewHolder {
private TextView textView_header;
public HeadViewHolder(View itemView) {
super(itemView);
textView_header = itemView.findViewById(R.id.textView1);
}
}
//尾部ViewHodler
public static class BottomViewHolder extends RecyclerView.ViewHolder {
private TextView textView_bottom;
public BottomViewHolder(View itemView) {
super(itemView);
textView_bottom = itemView.findViewById(R.id.textView1);
}
}
}
效果如图:
RecyclerView真的很强大,很好用。