一、概念
从Android 5.0开始,谷歌公司推出了一个用于大量数据展示的新控件RecylerView,可以用来代替传统的ListView,更加强大和灵活。RecyclerView的出现会让很多开源项目被废弃,例如横向滚动的ListView、横向滚动的GridView、瀑布流控件,因为RecyclerView能够实现所有这些功能。
二、特点
● RecyclerView封装了ViewHolder的回收复用,也就是说RecyclerView标准化了ViewHolder,编写Adapter面向的是ViewHolder而不再是View了,复用的逻辑被封装了,写起来更加简单,并且直接省去了ListView中convertView.setTag(holder)和convertView.getTag()这些繁琐的步骤。
● 提供了一种插拔式的体验,高度的解耦,异常的灵活,针对一个Item的显示RecyclerView专门抽取出了相应的类,来控制Item的显示,使其的扩展性非常强。
● 设置布局管理器以控制Item的布局方式,横向、竖向以及瀑布流方式。例如:你想控制横向或者纵向滑动列表效果可以通过LinearLayoutManager这个类来进行控制(与GridView效果对应的是GridLayoutManager,与瀑布流对应的是StaggeredGridLayoutManager等),也就是说RecyclerView不再拘泥于ListView的线性展示方式,它也可以实现GridView等多种效果。
● 可设置Item的间隔样式(可绘制),通过继承RecyclerView的ItemDecoration这个类,然后针对自己的业务需求去书写代码。
● 可以控制Item增删的动画,可以通过ItemAnimator这个类进行控制,当然针对增删的动画,RecyclerView有其自己默认的实现。
● 但是关于Item的点击和长按事件,需要用户自己去实现。
ListView相比RecyclerView,有一些优点:
● addHeaderView()、addFooterView()添加头视图和尾视图。
● 通过"android:divider"设置自定义分割线。
● setOnItemClickListener()和setOnItemLongClickListener()设置点击事件和长按事件。
这些功能在RecyclerView中都没有直接的接口,要自己实现,虽然实现起来也很简单。
RecyclerView相比ListView,有一些优点:
● 默认已经实现了View的复用,不需要类似if(convertView == null)的实现,而且回收机制更加完善。
● 默认支持局部刷新。
● 容易实现添加item、删除item的动画效果。
● 容易实现拖拽、侧滑删除等功能。
RecyclerView是一个插件式的实现,对各个功能进行解耦,从而扩展性比较好。
三、使用
//添加依赖
compile 'com.android.support:recyclerview-v7:23.4.0'
//Activity
recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
//设置布局管理器
recyclerView.setLayoutManager(layoutManager);
//设置为垂直布局,这也是默认的
layoutManager.setOrientation(OrientationHelper.VERTICAL);
//设置Adapter
recyclerView.setAdapter(recycleAdapter);
//设置分隔线
recyclerView.addItemDecoration(new DividerGridItemDecoration(this));
//设置增加或删除条目的动画
recyclerView.setItemAnimator(new DefaultItemAnimator());
四、四大组成
1.LayoutManager
LayoutManager负责RecyclerView的布局,其中包含了Item View的获取与回收。RecyclerView提供了三种布局管理器:
LinerLayoutManager:以垂直或者水平列表方式展示Item;
GridLayoutManager:以网格方式展示Item;
StaggeredGridLayoutManager:以瀑布流方式展示Item。
常用方法:
canScrollHorizontally();//能否横向滚动
canScrollVertically();//能否纵向滚动
scrollToPosition(int position);//滚动到指定位置
setOrientation(int orientation);//设置滚动的方向
getOrientation();//获取滚动方向
findViewByPosition(int position);//获取指定位置的Item View
findFirstCompletelyVisibleItemPosition();//获取第一个完全可见的Item位置
findFirstVisibleItemPosition();//获取第一个可见Item的位置
findLastCompletelyVisibleItemPosition();//获取最后一个完全可见的Item位置
findLastVisibleItemPosition();//获取最后一个可见Item的位置
网格样式:
网格样式的管理器是GridLayoutManager。
常用构造方法:
GridLayoutManager(Context context, int spanCount)
//spanCount,每列或者每行的item个数,设置为1,就是列表样式
//该构造函数默认是竖直方向的网格样式
GridLayoutManager(Context context, int spanCount, int orientation,boolean reverseLayout)
//spanCount,每列或者每行的item个数,设置为1,就是列表样式
//网格样式的方向,水平(OrientationHelper.HORIZONTAL)或者竖直(OrientationHelper.VERTICAL)
//reverseLayout,是否逆向,true:布局逆向展示,false:布局正向显示
例子:
//md_divider.xml
//styles.xml
//MDGridRvDividerDecoration
public class MDGridRvDividerDecoration extends RecyclerView.ItemDecoration {
private static final int[] ATTRS = new int[]{android.R.attr.listDivider};
// 用于绘制间隔样式
private Drawable mDivider;
public MDGridRvDividerDecoration(Context context) {
// 获取默认主题的属性
final TypedArray a = context.obtainStyledAttributes(ATTRS);
mDivider = a.getDrawable(0);
a.recycle();
}
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
// 绘制间隔,每一个item,绘制右边和下方间隔样式
int childCount = parent.getChildCount();
int spanCount = ((GridLayoutManager)parent.getLayoutManager()).getSpanCount();
int orientation = ((GridLayoutManager)parent.getLayoutManager()).getOrientation();
boolean isDrawHorizontalDivider = true;
boolean isDrawVerticalDivider = true;
int extra = childCount % spanCount;
extra = extra == 0 ? spanCount : extra;
for(int i = 0; i < childCount; i++) {
isDrawVerticalDivider = true;
isDrawHorizontalDivider = true;
// 如果是竖直方向,最右边一列不绘制竖直方向的间隔
if(orientation == OrientationHelper.VERTICAL && (i + 1) % spanCount == 0) {
isDrawVerticalDivider = false;
}
// 如果是竖直方向,最后一行不绘制水平方向间隔
if(orientation == OrientationHelper.VERTICAL && i >= childCount - extra) {
isDrawHorizontalDivider = false;
}
// 如果是水平方向,最下面一行不绘制水平方向的间隔
if(orientation == OrientationHelper.HORIZONTAL && (i + 1) % spanCount == 0) {
isDrawHorizontalDivider = false;
}
// 如果是水平方向,最后一列不绘制竖直方向间隔
if(orientation == OrientationHelper.HORIZONTAL && i >= childCount - extra) {
isDrawVerticalDivider = false;
}
if(isDrawHorizontalDivider) {
drawHorizontalDivider(c, parent, i);
}
if(isDrawVerticalDivider) {
drawVerticalDivider(c, parent, i);
}
}
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
int spanCount = ((GridLayoutManager) parent.getLayoutManager()).getSpanCount();
int orientation = ((GridLayoutManager)parent.getLayoutManager()).getOrientation();
int position = parent.getChildLayoutPosition(view);
if(orientation == OrientationHelper.VERTICAL && (position + 1) % spanCount == 0) {
outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
return;
}
if(orientation == OrientationHelper.HORIZONTAL && (position + 1) % spanCount == 0) {
outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
return;
}
outRect.set(0, 0, mDivider.getIntrinsicWidth(), mDivider.getIntrinsicHeight());
}
/* 绘制竖直间隔线
* @param canvas
* @param parent 父布局,RecyclerView
* @param position item在父布局中所在的位置 */
private void drawVerticalDivider(Canvas canvas, RecyclerView parent, int position) {
final View child = parent.getChildAt(position);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
.getLayoutParams();
final int top = child.getTop() - params.topMargin;
final int bottom = child.getBottom() + params.bottomMargin + mDivider.getIntrinsicHeight();
final int left = child.getRight() + params.rightMargin;
final int right = left + mDivider.getIntrinsicWidth();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(canvas);
}
/* 绘制水平间隔线
* @param canvas
* @param parent 父布局,RecyclerView
* @param position item在父布局中所在的位置 */
private void drawHorizontalDivider(Canvas canvas, RecyclerView parent, int position) {
final View child = parent.getChildAt(position);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
.getLayoutParams();
final int top = child.getBottom() + params.bottomMargin;
final int bottom = top + mDivider.getIntrinsicHeight();
final int left = child.getLeft() - params.leftMargin;
final int right = child.getRight() + params.rightMargin + mDivider.getIntrinsicWidth();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(canvas);
}
}
//Activity
// 竖直方向的网格样式,每行四个Item
mLayoutManager = new GridLayoutManager(this, 4, OrientationHelper.VERTICAL, false);
mRecyclerView.setLayoutManager(mLayoutManager);
mRecyclerView.addItemDecoration(new MDGridRvDividerDecoration(this));
瀑布流样式
RecyclerView的瀑布流布局管理器是StaggeredGridLayoutManager。
常用构造方法:
StaggeredGridLayoutManager(int spanCount, int orientation)
//spanCount代表每行或每列的Item个数,orientation代表列表的方向,竖直或者水平。
例子:
//view_rv_staggered_item.xml
//view_rv_staggered_item_two.xml
//layout
//Activity
// 初始化布局管理器
mLayoutManager = new StaggeredGridLayoutManager(2, OrientationHelper.VERTICAL);
// 设置布局管理器
mRecyclerView.setLayoutManager(mLayoutManager);
// 设置adapter
mRecyclerView.setAdapter(mAdapter);
// MDStaggeredRvDividerDecotation类是直接拷贝网格样式的间隔线绘制类,显示有问题,有些地方没有间隔
// 可以在item布局中设置四周间隔padding/margin,以达到设置间隔的目的,该方法有点挫
// mRecyclerView.addItemDecoration(new MDStaggeredRvDividerDecotation(this));
2.Adapter
// ① 创建Adapter
public class NormalAdapter extends RecyclerView.Adapter{
//② 创建ViewHolder
public static class VH extends RecyclerView.ViewHolder{
public final TextView title;
public VH(View v) {
super(v);
title = (TextView) v.findViewById(R.id.title);
}
}
private List mDatas;
public NormalAdapter(List data) {
this.mDatas = data;
}
//③ 在Adapter中实现3个方法
@Override
public void onBindViewHolder(VH holder, int position) {
holder.title.setText(mDatas.get(position));
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//item 点击事件
}
});
}
@Override
public int getItemCount() {
return mDatas.size();
}
@Override
public VH onCreateViewHolder(ViewGroup parent, int viewType) {
//LayoutInflater.from指定写法
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_1, parent, false);
return new VH(v);
}
}
3.Item Decoration间隔样式
RecyclerView通过addItemDecoration()方法添加item之间的分割线。自定义间隔样式需要继承RecyclerView.ItemDecoration类,该类是个抽象类,主要有三个方法:
onDraw(Canvas c, RecyclerView parent, State state):在Item绘制之前被调用,该方法主要用于绘制间隔样式。
onDrawOver(Canvas c, RecyclerView parent, State state):在Item绘制之前被调用,该方法主要用于绘制间隔样式。
getItemOffsets(Rect outRect, View view, RecyclerView parent, State state):设置item的偏移量,偏移的部分用于填充间隔样式,即设置分割线的宽、高;在RecyclerView的onMesure()中会调用该方法。
onDraw()和onDrawOver()这两个方法都是用于绘制间隔样式,我们只需要复写其中一个方法即可。
4.Item Animator动画
RecyclerView能够通过mRecyclerView.setItemAnimator(ItemAnimator animator)设置添加、删除、移动、改变的动画效果。RecyclerView提供了默认的ItemAnimator实现类:DefaultItemAnimator。如果没有特殊的需求,默认使用这个动画即可。
// 设置Item添加和移除的动画
mRecyclerView.setItemAnimator(new DefaultItemAnimator());
DefaultItemAnimator继承自SimpleItemAnimator,SimpleItemAnimator继承自ItemAnimator。
ItemAnimator类常用方法:
animateAppearance():当ViewHolder出现在屏幕上时被调用(可能是add或move)。
animateDisappearance():当ViewHolder消失在屏幕上时被调用(可能是remove或move)。
animatePersistence():在没调用notifyItemChanged()和notifyDataSetChanged()的情况下布局发生改变时被调用。
animateChange():在显式调用notifyItemChanged()或notifyDataSetChanged()时被调用。
runPendingAnimations():RecyclerView动画的执行方式并不是立即执行,而是每帧执行一次,比如两帧之间添加了多个Item,则会将这些将要执行的动画Pending住,保存在成员变量中,等到下一帧一起执行。该方法执行的前提是前面animateXxx()返回true。
isRunning(): 是否有动画要执行或正在执行。
dispatchAnimationsFinished():当全部动画执行完毕时被调用。
SimpleItemAnimator类常用方法:
animateAdd(ViewHolder holder):当Item添加时被调用。
animateMove(ViewHolder holder, int fromX, int fromY, int toX, int toY):当Item移动时被调用。
animateRemove(ViewHolder holder):当Item删除时被调用。
animateChange(ViewHolder oldHolder, ViewHolder newHolder, int fromLeft, int fromTop, int toLeft, int toTop):当显式调用notifyItemChanged()或notifyDataSetChanged()时被调用。
自定义Item Animator好麻烦,需要继承SimpleItemAnimator类,然后实现一堆方法,我们可以使用开源动画recyclerview-animators,该库提供了一系列的Animator,比如FadeInAnimator、ScaleInAnimator等,如果没有满意的动画,该库提供了BaseItemAnimator类,该类继承自SimpleItemAnimator,进一步封装了自定义Item Animator的代码,使得自定义Item Animator更方便,我们只需要关注动画本身。如果要实现DefaultItemAnimator的代码,只需要以下实现:
public class DefaultItemAnimator extends BaseItemAnimator {
public DefaultItemAnimator() {
}
public DefaultItemAnimator(Interpolator interpolator) {
mInterpolator = interpolator;
}
@Override protected void animateRemoveImpl(final RecyclerView.ViewHolder holder) {
ViewCompat.animate(holder.itemView)
.alpha(0)
.setDuration(getRemoveDuration())
.setListener(new DefaultRemoveVpaListener(holder))
.setStartDelay(getRemoveDelay(holder))
.start();
}
@Override protected void preAnimateAddImpl(RecyclerView.ViewHolder holder) {
ViewCompat.setAlpha(holder.itemView, 0); //透明度先变为0
}
@Override protected void animateAddImpl(final RecyclerView.ViewHolder holder) {
ViewCompat.animate(holder.itemView)
.alpha(1)
.setDuration(getAddDuration())
.setListener(new DefaultAddVpaListener(holder))
.setStartDelay(getAddDelay(holder))
.start();
}
}
五、点击事件
RecyclerView并没有像ListView一样暴露出Item点击事件或者长按事件处理的API,也就是说使用RecyclerView时候,需要我们自己来实现Item的点击和长按等事件的处理。可以通过在绑定ViewHolder的时候设置监听,然后通过Apater回调出去。
代码如下:
//Adapter
public class MyAdapter extends RecyclerView.Adapter{
// 展示数据
private ArrayList mData;
// 事件回调监听
private MyAdapter.OnItemClickListener onItemClickListener;
public MyAdapter(ArrayList data) {
this.mData = data;
}
public void updateData(ArrayList data) {
this.mData = data;
notifyDataSetChanged();
}
// 添加新的Item
public void addNewItem() {
if(mData == null) {
mData = new ArrayList<>();
}
mData.add(0, "new Item");
notifyItemInserted(0);
}
// 删除Item
public void deleteItem() {
if(mData == null || mData.isEmpty()) {
return;
}
mData.remove(0);
notifyItemRemoved(0);
}
// ① 定义点击回调接口
public interface OnItemClickListener {
void onItemClick(View view, int position);
void onItemLongClick(View view, int position);
}
// ② 定义一个设置点击监听器的方法
public void setOnItemClickListener(MyAdapter.OnItemClickListener listener) {
this.onItemClickListener = listener;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
// 实例化展示的view
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.view_rv_item, parent, false);
// 实例化viewholder
ViewHolder viewHolder = new ViewHolder(v);
return viewHolder;
}
@Override
public void onBindViewHolder(final ViewHolder holder, int position) {
// 绑定数据
holder.mTv.setText(mData.get(position));
//③ 对RecyclerView的每一个itemView设置点击事件
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(final View v) {
if(onItemClickListener != null) {
int pos = holder.getLayoutPosition();
onItemClickListener.onItemClick(holder.itemView, pos);
}
}
});
holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
if(onItemClickListener != null) {
int pos = holder.getLayoutPosition();
onItemClickListener.onItemLongClick(holder.itemView, pos);
}
//表示此事件已经消费,不会触发单击事件
return true;
}
});
}
@Override
public int getItemCount() {
return mData == null ? 0 : mData.size();
}
public static class ViewHolder extends RecyclerView.ViewHolder {
TextView mTv;
public ViewHolder(View itemView) {
super(itemView);
mTv = (TextView) itemView.findViewById(R.id.item_tv);
}
}
}
//Activity
mAdapter.setOnItemClickListener(new MyAdapter.OnItemClickListener() {
@Override
public void onItemClick(View view, int position) {
Toast.makeText(MDRvActivity.this,"click " + position + " item", Toast.LENGTH_SHORT).show();
}
@Override
public void onItemLongClick(View view, int position) {
Toast.makeText(MDRvActivity.this,"long click " + position + " item", Toast.LENGTH_SHORT).show();
}
});
六、添加HeaderView和FooterView
RecyclerView默认没有提供类似addHeaderView()和addFooterView()的API,如果你已经实现了一个Adapter,现在想为这个Adapter添加addHeaderView()和addFooterView()接口,则需要在Adapter中添加几个Item Type,然后修改getItemViewType()、onCreateViewHolder()、onBindViewHolder()、getItemCount()等方法,并添加switch语句进行判断。
这里引入装饰器(Decorator)设计模式,该设计模式通过组合的方式,在不破坏原有类代码的情况下,对原有类的功能进行扩展。具体实现思路其实很简单,创建一个继承RecyclerView.Adapter
//Adapter
public class NormalAdapterWrapper extends RecyclerView.Adapter{
enum ITEM_TYPE{
HEADER,
FOOTER,
NORMAL
}
private NormalAdapter mAdapter;
private View mHeaderView;
private View mFooterView;
public NormalAdapterWrapper(NormalAdapter adapter){
mAdapter = adapter;
}
@Override
public int getItemViewType(int position) {
if(position == 0){
return ITEM_TYPE.HEADER.ordinal();
} else if(position == mAdapter.getItemCount() + 1){
return ITEM_TYPE.FOOTER.ordinal();
} else{
return ITEM_TYPE.NORMAL.ordinal();
}
}
@Override
public int getItemCount() {
return mAdapter.getItemCount() + 2;
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if(position == 0){
return;
} else if(position == mAdapter.getItemCount() + 1){
return;
} else{
mAdapter.onBindViewHolder(((NormalAdapter.VH)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 mAdapter.onCreateViewHolder(parent,viewType);
}
}
public void addHeaderView(View view){
this.mHeaderView = view;
}
public void addFooterView(View view){
this.mFooterView = view;
}
}
//Activity
NormalAdapter adapter = new NormalAdapter(data);
NormalAdapterWrapper newAdapter = new NormalAdapterWrapper(adapter);
View headerView = LayoutInflater.from(this).inflate(R.layout.item_header, mRecyclerView, false);
View footerView = LayoutInflater.from(this).inflate(R.layout.item_footer, mRecyclerView, false);
newAdapter.addFooterView(footerView);
newAdapter.addHeaderView(headerView);
mRecyclerView.setAdapter(newAdapter);
七、添加setEmptyView
ListView提供了setEmptyView()设置Adapter数据为空时的View视图。RecyclerView虽然没提供直接的API,但是也可以很简单地实现。
● 创建一个继承RecyclerView的类,记为EmptyRecyclerView。
● 通过getRootView().addView(emptyView)将空数据时显示的View添加到当前View的层次结构中。
● 通过AdapterDataObserver监听RecyclerView的数据变化,如果adapter为空,那么隐藏RecyclerView,显示EmptyView。
具体实现如下:
//EmptyRecyclerView
public class EmptyRecyclerView extends RecyclerView{
private View mEmptyView;
private AdapterDataObserver mObserver = new AdapterDataObserver() {
@Override
public void onChanged() {
Adapter adapter = getAdapter();
if(adapter.getItemCount() == 0){
mEmptyView.setVisibility(VISIBLE);
EmptyRecyclerView.this.setVisibility(GONE);
} else{
mEmptyView.setVisibility(GONE);
EmptyRecyclerView.this.setVisibility(VISIBLE);
}
}
public void onItemRangeChanged(int positionStart, int itemCount) {onChanged();}
public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {onChanged();}
public void onItemRangeRemoved(int positionStart, int itemCount) {onChanged();}
public void onItemRangeInserted(int positionStart, int itemCount) {onChanged();}
public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {onChanged();}
};
public EmptyRecyclerView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public void setEmptyView(View view){
this.mEmptyView = view;
((ViewGroup)this.getRootView()).addView(mEmptyView); //加入主界面布局
}
public void setAdapter(RecyclerView.Adapter adapter){
super.setAdapter(adapter);
adapter.registerAdapterDataObserver(mObserver);
mObserver.onChanged();
}
}
//layout
//Activity
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_5);
mRv = (EmptyRecyclerView) findViewById(R.id.rv);
mRv.setLayoutManager(new LinearLayoutManager(this));
mData = new ArrayList<>();
mAdapter = new NormalAdapter(mData);
//View view = LayoutInflater.from(this).inflate(R.layout.empty, null);
View view = findViewById(R.id.text_empty);
mRv.setEmptyView(view);
mRv.setAdapter(mAdapter);
}
八、拖拽、侧滑删除
Android提供了ItemTouchHelper类,使得RecyclerView能够轻易地实现滑动和拖拽,此处我们要实现上下拖拽和侧滑删除。
使用方法:
1.创建ItemTouchHelper.Callback类
首先创建一个继承自ItemTouchHelper.Callback的类,并重写以下方法:
getMovementFlags():设置支持的拖拽和滑动的方向,此处我们支持的拖拽方向为上下,滑动方向为从左到右和从右到左,内部通过makeMovementFlags()设置。
onMove():拖拽时回调。
onSwiped():滑动时回调。
onSelectedChanged():状态变化时回调,一共有三个状态,分别是ACTION_STATE_IDLE(空闲状态),ACTION_STATE_SWIPE(滑动状态),ACTION_STATE_DRAG(拖拽状态)。此方法中可以做一些状态变化时的处理,比如拖拽的时候修改背景色。
clearView():用户交互结束时回调。此方法可以做一些状态的清空,比如拖拽结束后还原背景色。
isLongPressDragEnabled():是否支持长按拖拽,默认为true。如果不想支持长按拖拽,则重写并返回false。
具体实现如下:
public class SimpleItemTouchCallback extends ItemTouchHelper.Callback {
private NormalAdapter mAdapter;
private List mData;
public SimpleItemTouchCallback(NormalAdapter adapter, List data){
mAdapter = adapter;
mData = data;
}
//设置支持的拖拽、滑动的方向
@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);
mAdapter.notifyItemMoved(from, to);
return true;
}
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
int pos = viewHolder.getAdapterPosition();
mData.remove(pos);
mAdapter.notifyItemRemoved(pos);
}
//状态改变时回调
@Override
public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
super.onSelectedChanged(viewHolder, actionState);
if(actionState != ItemTouchHelper.ACTION_STATE_IDLE){
NormalAdapter.VH holder = (NormalAdapter.VH)viewHolder;
holder.itemView.setBackgroundColor(0xffbcbcbc); //设置拖拽和侧滑时的背景色
}
}
//拖拽或滑动完成之后调用,用来清除一些状态
@Override
public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
super.clearView(recyclerView, viewHolder);
NormalAdapter.VH holder = (NormalAdapter.VH)viewHolder;
holder.itemView.setBackgroundColor(0xffeeeeee); //背景色还原
}
}
2.给RecyclerView设置ItemTouchHelper
ItemTouchHelper helper = new ItemTouchHelper(new SimpleItemTouchCallback(adapter, data));
helper.attachToRecyclerView(recyclerview);
触摸拖拽
前面拖拽的触发方式只有长按,如果想支持触摸Item中的某个View实现拖拽,则核心方法为helper.startDrag(holder)。
具体实现如下:
//Adapter
public class NormalAdapter extends RecyclerView.Adapter{
private List mDatas;
private OnStartDragListener mListener;
public NormalAdapter(List data, OnStartDragListener listener) {
this.mDatas = data;
mListener = listener;
}
@Override
public void onBindViewHolder(final VH holder, int position) {
ObjectModel model = mDatas.get(position);
holder.title.setText(model.title);
holder.number.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if(event.getAction() == MotionEvent.ACTION_DOWN){
mListener.startDrag(holder);
}
return false;
}
});
}
@Override
public int getItemCount() {
return mDatas.size();
}
@Override
public VH onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_3, parent, false);
return new VH(v);
}
public static class VH extends RecyclerView.ViewHolder{
public final TextView title;
public final ImageView number;
public VH(View v) {
super(v);
title = (TextView) v.findViewById(R.id.title);
number = (ImageView) v.findViewById(R.id.icon);
}
}
}
//Activity
public class Activity3 extends AppCompatActivity implements OnStartDragListener{
private RecyclerView mRv;
private NormalAdapter mAdapter;
private ItemTouchHelper mHelper;
private List mData;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_3);
mRv = (RecyclerView) findViewById(R.id.rv);
mRv.setLayoutManager(new LinearLayoutManager(this));
mAdapter = new NormalAdapter(mData = initData(), this);
mRv.setAdapter(mAdapter);
mHelper = new ItemTouchHelper(new SimpleItemTouchCallback(mAdapter, mData));
mHelper.attachToRecyclerView(mRv);
}
public ArrayList initData(){
ArrayList models = new ArrayList<>();
String[] titles = getResources().getStringArray(R.array.title_array);
for(int i=0;i
九、嵌套滑动机制
Android 5.0推出了嵌套滑动机制,在之前,一旦子View处理了触摸事件,父View就没有机会再处理这次的触摸事件,而嵌套滑动机制解决了这个问题。为了支持嵌套滑动,子View必须实现NestedScrollingChild接口,父View必须实现NestedScrollingParent接口。而RecyclerView实现了NestedScrollingChild接口,而CoordinatorLayout实现了NestedScrollingParent接口。
例子:
//layout
//Adapter
public abstract class QuickAdapter extends RecyclerView.Adapter{
private List mDatas;
public QuickAdapter(List datas){
this.mDatas = datas;
}
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, mDatas.get(position), position);
}
@Override
public int getItemCount() {
return mDatas.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 v){
super(v);
mConvertView = v;
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 v = mViews.get(id);
if(v == null){
v = mConvertView.findViewById(id);
mViews.put(id, v);
}
return (T)v;
}
public void setText(int id, String value){
TextView view = getView(id);
view.setText(value);
}
}
}
//Activity
public class Activity6 extends AppCompatActivity {
private RecyclerView mRv;
private QuickAdapter mAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_6);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
mRv = (RecyclerView) findViewById(R.id.rv);
mRv.setLayoutManager(new StaggeredGridLayoutManager(3, StaggeredGridLayoutManager.VERTICAL));
mAdapter = new QuickAdapter(initData()) {
@Override
public int getLayoutId(int viewType) {
return R.layout.item_6;
}
@Override
public void convert(VH holder, Integer data, int position) {
ImageView imageView = holder.getView(R.id.image);
Picasso.with(Activity6.this).load(data).into(imageView);
//holder.itemView.setOnClickListener(); 此处添加点击事件
}
@Override
public int getItemViewType(int position) {
return super.getItemViewType(position);
}
};
mAdapter.setHasStableIds(true);
((SimpleItemAnimator)mRv.getItemAnimator()).setSupportsChangeAnimations(false);
mRv.setAdapter(mAdapter);
}
public List initData(){
Integer[] images = {R.drawable.s1, R.drawable.s2, R.drawable.s3, R.drawable.s4, R.drawable.s5,
R.drawable.s6, R.drawable.s7, R.drawable.s8, R.drawable.s9, R.drawable.s10
};
ArrayList list = new ArrayList<>();
for(int i=0;i<2;i++){
for(Integer image:images){
list.add(image);
}
}
return list;
}
}
十、局部刷新
ListView实现局部刷新:
ListView通过adapter.notifyDataSetChanged()实现ListView的更新,这种更新方法的缺点是全局更新,即对每个Item View都进行重绘。但事实上很多时候,我们只是更新了其中一个Item的数据,其他Item其实可以不需要重绘,ListView实现局部更新的方法如下:
public void updateItemView(ListView listview, int position, Data data){
int firstPos = listview.getFirstVisiblePosition();
int lastPos = listview.getLastVisiblePosition();
if(position >= firstPos && position <= lastPos){ //可见才更新,不可见则在getView()时更新
//listview.getChildAt(i)获得的是当前可见的第i个item的view
View view = listview.getChildAt(position - firstPos);
VH vh = (VH)view.getTag();
vh.text.setText(data.text);
}
}
RecyclerView实现局部刷新:
RecyclerView提供了notifyItemInserted()、notifyItemRemoved()、notifyItemChanged()等API更新单个或某个范围的Item视图。
十一、缓存机制
缓存层级
ListView和RecyclerView缓存机制基本一致:
● mActiveViews和mAttachedScrap功能相似,意义在于快速重用屏幕上可见的列表项ItemView,而不需要重新createView和bindView;
● mScrapView和mCachedViews + mReyclerViewPool功能相似,意义在于缓存离开屏幕的ItemView,目的是让即将进入屏幕的ItemView重用。
RecyclerView的优势在于:
● mCacheViews的使用,可以做到屏幕外的列表项ItemView进入屏幕内时也无须bindView快速重用;
● mRecyclerPool可以供多个RecyclerView共同使用,在特定场景下,如viewpaper+多个列表页下有优势。
客观来说,RecyclerView在特定场景下对ListView的缓存机制做了补强和完善。
缓存内容
● RecyclerView缓存RecyclerView.ViewHolder,抽象可理解为:
View + ViewHolder(避免每次createView时调用findViewById) + flag(标识状态);
● ListView缓存View。