一、优缺点
RecyclerView主要是和ListView进行比较,下面列举几个优缺点:
缺点:
- listview可以通过addHeaderView()和addFooterView()添加头视图和尾视图。
- listview可以通过android:divider设置自定义分割线。
- listview可以setOnItemClickListener()和setOnItemLongClickListener()设置点击事件和长按事件。
优点:
- 容易实现各种布局。
- 默认实现了View的复用,不需要类似if(convertview==null)的实现,而且回收机制更加的完善。
- 默认支持局部刷新。
- 容易实现添加item,删除item的动画效果。
- 容易实现拖拽,侧滑删除等功能
- listview只提供notifyDataSetChanged()更新整个视图,这是很不合理的。RecyclerView提供了notifyItemInserted(),notifyItemRemove(),notifyItemChanged()等API更新单个或某个范围的Item视图。
二、四大基础部分
RecyclerView属于新增加的控件,为了让RecyclerView在所有版本上都能使用,将RecyclerView定义在support库中,因此,想要使用,首先需要在项目的build.gradle中添加相应的依赖库:
compile 'com.android.support:recyclerview-v7:24.2.0'
添加后,在布局文件中直接添加recyclerview控件。
RecyclerView需要进行四大设置:
1.Adapter(必选)
负责提供数据,一个适配器,继承自RecyclerView.Adapter。适配器需要一个构造函数将数据传入;重写三个方法,分别是onCreateViewHolder,onBindViewHolder,getItemCount方法;定义一个内部类ViewHolder,继承RecyclerView.ViewHolder。在构造函数中传入view参数,这个参数就是子项的布局。
2.LayoutManager(必选,负责布局)
LayoutManager是RecyclerView的一个抽象内部类,一般我们使用它都是使用它的子类,常用的有:
- LinearLayoutManager(横向和纵向)
- GridLayoutManager(网格式)
- StaggeredGridLayoutManager(瀑布式)
纵向为默认;
横向实现方式:
layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
网格式实现方式:
GridLayoutManager gridLayoutManager = new GridLayoutManager(this,4);
第二个参数为每一行的个数。
瀑布流式实现方式:
StaggeredGridLayoutManager layoutmanager = new StaggeredGridLayoutManager(3,StaggeredGridLayoutManager.VERTICAL);
除此之外还需要在adapter的onBindViewHolder中设置一个随机高度。
ViewGroup.LayoutParams layoutParams = viewHolder.linearLayout.getLayoutParams(); layoutParams.height = 300+(int)(Math.random()*100);
实现效果:
3.Item Decoration(可选,默认为空)
负责Item之间的间隔,RecyclerView通过addItemDecoration()方法添加item之间的分割线。android并没有提供实现好的Decoration,因此任何分割样式都需要自己实现。
ItemDecoration类主要是三个方法:
public void onDraw(Canvas c, RecyclerView parent, State state)
public void onDrawOver(Canvas c, RecyclerView parent, State state)
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state)
getItemOffsets实现的就是类似padding的效果;
onDraw实现类似背景绘制,内容在上面;
onDrawOver可以绘制在内容的上面,覆盖内容;
举个例子,实现分割线:
要实现分割线效果需要 getItemOffsets()和 onDraw()2个方法,首先用 getItemOffsets给item下方空出一定高度的空间(例子中是1dp),然后用onDraw绘制这个空间
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
outRect.bottom = dividerHeight;
}
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
int childCount = parent.getChildCount();
int left = parent.getPaddingLeft();
int right = parent.getWidth() - parent.getPaddingRight();
for (int i = 0; i < childCount - 1; i++) {
View view = parent.getChildAt(i);
float top = view.getBottom();
float bottom = view.getBottom() + dividerHeight;
c.drawRect(left, top, right, bottom, dividerPaint);
}
}
实现效果:
实现标签:
现在很多电商app会给商品加上一个标签,比如“推荐”,“热卖”,“秒杀”等等,可以看到这些标签都是覆盖在内容之上的,这就可以用onDrawOver()来实现,我们这里简单实现一个有趣的标签。
@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 child = parent.getChildAt(i);
int pos = parent.getChildAdapterPosition(child);
boolean isLeft = pos % 2 == 0;
if (isLeft) {
float left = child.getLeft();
float right = left + tagWidth;
float top = child.getTop();
float bottom = child.getBottom();
c.drawRect(left, top, right, bottom, leftPaint);
} else {
float right = child.getRight();
float left = right - tagWidth;
float top = child.getTop();
float bottom = child.getBottom();
c.drawRect(left, top, right, bottom, rightPaint);
}
}
}
实现效果:
4.Item Animator(可选,默认为 DefaultItemAnimator)
负责增加,删除item的动画 。
RecyclerView.setItemAnimator(new DefaultItemAnimator());
注意,这里更新数据集不是用adapter.notifyDataSetChanged()而是
notifyItemInserted(position)与notifyItemRemoved(position)
否则没有动画效果。
三、拓展功能
1.万能适配器
这里我们只针对RecyclerView,聊聊万能适配器出现的原因。为了创建一个RecyclerView的Adapter,每次我们都需要去做重复劳动,包括重写onCreateViewHolder(),getItemCount()、创建ViewHolder,并且实现过程大同小异,因此万能适配器出现了,他能通过以下方式快捷地创建一个Adapter:
public abstract class QuickAdapter extends RecyclerView.Adapter {
private List mData;
public QuickAdapter(List mData){
this.mData = mData;
}
public abstract int getLayoutId(int viewType);
@Override
public VH onCreateViewHolder(ViewGroup parent,int viewType){
return VH.get(parent,getLayoutId(viewType));
}
@Override
public void onBindViewHolder(VH holder,int position){
convert(holder,mData.get(position),position);
}
@Override
public int getItemCount(){
return mData.size();
}
public abstract void convert(VH holder,T data,int position);
static class VH extends RecyclerView.ViewHolder{
private SparseArray mViews;
private View mConvertView;
private VH(View view){
super(view);
mConvertView = view;
mViews = new SparseArray<>();
}
public static VH get(ViewGroup parent,int layoutid){
View convertView = LayoutInflater.from(parent.getContext()).inflate(layoutid,parent,false);
return new VH(convertView);
}
public T getView(int id){
View view = mViews.get(id);
if(view == null){
view = mConvertView.findViewById(id);
mViews.put(id,view);
}
return (T)view;
}
public void setText(int id,String values){
TextView textView = getView(id);
textView.setText(values);
}
}
}
使用:
QuickAdapter adapter = new QuickAdapter(mData) {
@Override
public int getLayoutId(int viewType) {
return R.layout.item;
}
@Override
public void convert(VH holder, String data, int position) {
holder.setText(R.id.item_text,data);
}
};
2.添加setOnItemClickListener接口
RecyclerView没有像ListView一样提供onItemClickListener却让人比较难过,之前都是通过给每个item添加onClickListener来模仿一个伪onItemClickListener,这种为每个item添加点击监听的解决方案不用多想也知道是浪费性能的方法,这里参照了网上的一种方法。
使用:
recyclerView.addOnItemTouchListener(new OnRecyclerItemClickListener(recyclerView) {
@Override
public void onItemClick(RecyclerView.ViewHolder vh) {
//item点击事件
}
});
OnRecyclerItemClickListener是自定义的一个触摸监听器,代码如下:
public abstract class OnRecyclerItemClickListener implements RecyclerView.OnItemTouchListener{
private GestureDetectorCompat mGestureDetector;
private RecyclerView recyclerView;
public OnRecyclerItemClickListener(RecyclerView recyclerView){
this.recyclerView = recyclerView;
mGestureDetector = new GestureDetectorCompat(recyclerView.getContext(),new ItemTouchHelperGestureListener());
}
@Override
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
mGestureDetector.onTouchEvent(e);
return false;
}
@Override
public void onTouchEvent(RecyclerView rv, MotionEvent e) {
mGestureDetector.onTouchEvent(e);
}
@Override
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
}
private class ItemTouchHelperGestureListener extends GestureDetector.SimpleOnGestureListener {
@Override
public boolean onSingleTapUp(MotionEvent e) {
View child = recyclerView.findChildViewUnder(e.getX(), e.getY());
if (child!=null) {
RecyclerView.ViewHolder vh = recyclerView.getChildViewHolder(child);
onItemClick(vh);
}
return true;
}
//长点击事件,本例不需要不处理
//@Override
//public void onLongPress(MotionEvent e) {
// View child = recyclerView.findChildViewUnder(e.getX(), e.getY());
// if (child!=null) {
// RecyclerView.ViewHolder vh = recyclerView.getChildViewHolder(child);
// onItemLongClick(vh);
// }
//}
public abstract void onItemClick(RecyclerView.ViewHolder vh);
//public abstract void onItemLongClick(RecyclerView.ViewHolder vh);
}
查阅RecyclerView的api发现虽然没有提供onItemClickListener但是提供了addOnItemTouchListener方法:
3.添加HeaderView和FooterView
这里引入装饰器(Decorator)设计模式,该设计模式通过组合的方式,在不破话原有类代码的情况下,对原有类的功能进行扩展。
public class ExtendAdapter extends RecyclerView.Adapter {
enum ITEM_TYPE{
HEADER,
NORMAL,
FOOTER
}
private MyAdapter adapter;
private View mHeaderView;
private View mFooterView;
public ExtendAdapter(MyAdapter adapter){
this.adapter = adapter;
}
@Override
public int getItemViewType(int position){
if (position==0){
return ITEM_TYPE.HEADER.ordinal();
}else if (position == adapter.getItemCount()+1){
return ITEM_TYPE.FOOTER.ordinal();
}else{
return ITEM_TYPE.NORMAL.ordinal();
}
}
@Override
public int getItemCount(){
return adapter.getItemCount()+2;
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder,int position){
if (position==0){
return;
}else if (position==adapter.getItemCount()+1){
return;
}else {
adapter.onBindViewHolder((MyAdapter.MyViewHolder) holder,position-1);
}
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent,int viewType){
if (viewType==ITEM_TYPE.HEADER.ordinal()){
return new RecyclerView.ViewHolder(mHeaderView){};
}else if (viewType==ITEM_TYPE.FOOTER.ordinal()){
return new RecyclerView.ViewHolder(mFooterView) {};
}else {
return adapter.onCreateViewHolder(parent,viewType);
}
}
public void addHeaderView(View view){
this.mHeaderView = view;
}
public void addFooterView(View view){
this.mFooterView = view;
}
}
使用:
MyAdapter myAdapter = new MyAdapter(mData);
ExtendAdapter extendAdapter = new ExtendAdapter(myAdapter);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
View headerView = LayoutInflater.from(this).inflate(R.layout.item_header, recyclerView, false);
View footerView = LayoutInflater.from(this).inflate(R.layout.item_footer, recyclerView, false);
extendAdapter.addHeaderView(headerView);
extendAdapter.addFooterView(footerView);
recyclerView.setAdapter(myAdapter);
效果:
4.拖拽、侧滑删除
Android提供了ItemTouchHelper类,使得RecyclerView能够轻易地实现滑动和拖拽,此处我们要实现上下拖拽和侧滑删除。首先创建一个继承自ItemTouchHelper.Callback的类,并重写以下方法:
- getMovementFlags(): 设置支持的拖拽和滑动的方向,此处我们支持的拖拽方向为上下,滑动方向为从左到右和从右到左,内部通过makeMovementFlags()设置。
- onMove(): 拖拽时回调。
- onSwiped(): 滑动时回调。
- onSelectedChanged(): 状态变化时回调,一共有三个状态,分别是ACTION_STATE_IDLE(空闲状态),ACTION_STATE_SWIPE(滑动状态),ACTION_STATE_DRAG(拖拽状态)。此方法中可以做一些状态变化时的处理,比如拖拽的时候修改背景色。
- clearView(): 用户交互结束时回调。此方法可以做一些状态的清空,比如拖拽结束后还原背景色。
- isLongPressDragEnabled(): 是否支持长按拖拽,默认为true。如果不想支持长按拖拽,则重写并返回false。
实现:
public class MyItemTouchCallback extends ItemTouchHelper.Callback {
private MyAdapter adapter;
private List mData;
public MyItemTouchCallback(MyAdapter adapter,List mData){
this.adapter = adapter;
this.mData = mData;
}
@Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder){
int dragFlag = ItemTouchHelper.UP | ItemTouchHelper.DOWN; //s上下拖拽
int swipeFlag = ItemTouchHelper.START | ItemTouchHelper.END; //左->右和右->左滑动
return makeMovementFlags(dragFlag,swipeFlag);
}
@Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
int from = viewHolder.getAdapterPosition();
int to = target.getAdapterPosition();
Collections.swap(mData, from, to);
adapter.notifyItemMoved(from, to);
return true;
}
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
int pos = viewHolder.getAdapterPosition();
mData.remove(pos);
adapter.notifyItemRemoved(pos);
}
@Override
public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
super.onSelectedChanged(viewHolder, actionState);
if(actionState != ItemTouchHelper.ACTION_STATE_IDLE){
MyAdapter.MyViewHolder holder = (MyAdapter.MyViewHolder)viewHolder;
holder.itemView.setBackgroundColor(0xffbcbcbc); //设置拖拽和侧滑时的背景色
}
}
@Override
public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
super.clearView(recyclerView, viewHolder);
MyAdapter.MyViewHolder holder = (MyAdapter.MyViewHolder) viewHolder;
holder.itemView.setBackgroundColor(0xffeeeeee); //背景色还原
}
}
使用:
MyAdapter myAdapter = new MyAdapter(mData);
recyclerView.setAdapter(myAdapter);
MyItemTouchCallback itemTouchCallback = new MyItemTouchCallback(myAdapter,mData);
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(itemTouchCallback);
itemTouchHelper.attachToRecyclerView(recyclerView);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
效果:
参考文章:
- RecyclerView 必知必会
- android开发游记:RecyclerView无法添加onItemClickListener最佳的高效解决方案