RecyclerView基本介绍
特点:
- 谷歌在高级版本提出一个新的替代ListView、GridView的控件。
- 高度解耦,但是用起来会比较难用,而且条目点击也需要自己处理。
- 自带了性能优化。ViewHolder。
需要注意的是:RecyclerView没有条目点击事件,需要自己写。
Tips:软件的一个很重要的概念:低耦合高内聚。
基本使用
由于这个控件大家用得比较多,这里只是简单回顾一下使用的步骤。
写条目布局:
写Adapter以及其内部类自定义的ViewHolder:
public class MyRecyclerViewAdapter extends RecyclerView.Adapter {
private List mDatas;
private Context mContext;
public MyRecyclerViewAdapter(Context context, List datas) {
mContext = context;
mDatas = datas;
}
//自定义ViewHolder
class MyViewHolder extends RecyclerView.ViewHolder {
TextView tv_item;
MyViewHolder(View itemView) {
super(itemView);
tv_item = (TextView) itemView.findViewById(R.id.tv_item);
}
}
@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
//创建ViewHolder
View itemView = View.inflate(parent.getContext(), R.layout.item_list, null);
return new MyViewHolder(itemView);
}
@Override
public void onBindViewHolder(MyViewHolder holder, final int position) {
//数据绑定
holder.tv_item.setText(mDatas.get(position));
//设置点击监听
holder.tv_item.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(mContext, mDatas.get(position), Toast.LENGTH_SHORT).show();
}
});
}
@Override
public int getItemCount() {
//数据集大小
return mDatas.size();
}
}
在Activity中的使用,通过设置不同的LayoutManager就可以实现不同的布局效果:
public class MDRecyclerViewActivity extends AppCompatActivity {
private RecyclerView rv_list;
private MyRecyclerViewAdapter mAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_md_recyclerview);
rv_list = (RecyclerView) findViewById(R.id.rv_list);
List datas = new ArrayList<>();
for (int i = 0; i < 100; i++) {
datas.add("第" + i + "个数据");
}
mAdapter = new MyRecyclerViewAdapter(this, datas);
//竖直线性,不反转布局
// rv_list.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));
//表格布局
// rv_list.setLayoutManager(new GridLayoutManager(this, 3));
//瀑布流布局
rv_list.setLayoutManager(new StaggeredGridLayoutManager(3, StaggeredGridLayoutManager.VERTICAL));
rv_list.setAdapter(mAdapter);
}
}
RecyclerView的一个坑以及Inflate源码分析
在Adapter中的onCreateViewHolder,我们需要Inflate布局文件,这里有三种写法:
View itemView = View.inflate(parent.getContext(), R.layout.item_list, null);
View itemView = View.inflate(parent.getContext(), R.layout.item_list, parent);
View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_list, parent, false);
其中:
- 写法一般情况下是没有问题的,但是当我们在onBindViewHolder中拿到布局中TextView的LayoutParams的时候,就有可能返回空。
- 写法二直接Crash,因为ItemView布局已经有一个Parent了(Inflate的时候把ItemView添加到Recycleview了),不能再添加一个Parent(Recycleview再次添加ItemView)。
- 写法三是一、二的两种兼容方案,推荐这种写法。
关于Inflate的简单源码分析
我们先看View.inflate(parent.getContext(), R.layout.item_list, null)方法:
public static View inflate(Context context, @LayoutRes int resource, ViewGroup root) {
LayoutInflater factory = LayoutInflater.from(context);
return factory.inflate(resource, root);
}
实际上这里还是会调用LayoutInflater的inflate方法:
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root{
//attachToRoot为真
return inflate(resource, root, root != null);
}
继续深入分析调用关系:
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
final Resources res = getContext().getResources();
if (DEBUG) {
Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
+ Integer.toHexString(resource) + ")");
}
final XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
布局的渲染是通过解析器解析,然后不断反射来生成View的。我们继续深入try中的inflate方法(这里只给出省略之后的核心代码):
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
try {
if (TAG_MERGE.equals(name)) {
} else {
//如果发现root不为null,那么就会为当前渲染的View创建LayoutParams
if (root != null) {
// Create layout params that match root, if supplied
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
// Set the layout params for temp if we are not
// attaching. (If we are, we use addView, below)
temp.setLayoutParams(params);
}
}
//如果root不为null而且attachToRoot为真,那么把当前渲染的View添加到root中
if (root != null && attachToRoot) {
root.addView(temp, params);
}
}
} catch (XmlPullParserException e) {
} catch (Exception e) {
} finally {
}
}
}
在这里我们就知道原因:
- 写法一之所以在onBindViewHolder中拿不到布局中TextView的LayoutParams,是因为root我们传了null,那么就不会调用generateLayoutParams了。
- 写法二报错是因为,渲染的时候,我们实际传进来的root != null && attachToRoot成立,那么就会调用root.addView(temp, params);把当前的View(ItemView)添加到root(Recycleview)中。在Recycleview运行的时候,内部又会把ItemView添加进来一次,那么就会报错。
- 写法三没毛病,哈哈。
上面这个问题除了RecyclerView,以前的AbsListView(ListView、GridView等)也存在。
拓展:Fragment的onCreateView渲染布局的注意事项
Fragment中,我们也是最好使用下面这种形式:
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(resID, container, false);
}
添加增删接口
在Adapter中添加以及删除的接口:
//条目的增删
public void addItem(String data, int position) {
mDatas.add(position, data);
notifyItemInserted(position);
}
public void removeItem(int position) {
mDatas.remove(position);
notifyItemRemoved(position);
}
注意如果你想使用RecyclerView提供的增删动画,那么就需要使用新增的notify方法。
添加条目点击监听
我们自定义一个点击回调接口:
//条目点击
ItemClickListener mItemClickListener;
public interface ItemClickListener {
void onclick(int position, String data);
}
public void setItemClickListener(ItemClickListener listener) {
mItemClickListener = listener;
}
public abstract class ItemClickListenerPosition implements View.OnClickListener {
private int mPosition;
public ItemClickListenerPosition(int position) {
mPosition = position;
}
public int getPosition() {
return mPosition;
}
}
其中,ItemClickListenerPosition是一个自定义的OnClickListener,目的就是为了把Position和监听绑定在一起,同时也使用了getLayoutPosition方法。防止了点击Position错乱的问题。
(onBindViewHolder() 方法中的位置参数 position 不是实时更新的,例如在我们删除元素后,item 的 position 并没有改变。)
然后在onBindViewHolder里面进行监听:
@Override
public void onBindViewHolder(final MyViewHolder holder, int position) {
//数据绑定
//设置条目监听
holder.itemView.setOnClickListener(new ItemClickListenerPosition(holder.getLayoutPosition()) {
@Override
public void onClick(View v) {
if (mItemClickListener != null) {
mItemClickListener.onclick(getPosition(), mDatas.get(getPosition()));
}
}
});
}
另外,长按的使用也是类似。我们当然也可以直接为每一个条目上面的某一个控件设置监听,实现思路跟这个一样,就不介绍了。
分割线处理
为了实现普通的分割线,我们需要自定义类,继承RecyclerView.ItemDecoration,并且实现getItemOffsets、onDraw两个方法。
其中:
- getItemOffsets是返回条目之间的间隔,例如我们想仿照ListView一样添加分割线,那么就需要设置outRect的下边距。
- onDraw方法就是自己画需要的分割线。
例子:画横纵向ListView分割线
首先,我们需要有一个分割线Drawable:
其中,这里指定的宽高值是指横向或者纵向的时候的分割线宽度。
然后,创建一个类,继承RecyclerView.ItemDecoration,并且实现getItemOffsets、onDraw两个方法:
public class DividerItemDecoration extends RecyclerView.ItemDecoration {
//布局方向
private int mOrientation = LinearLayoutManager.VERTICAL;
private final Drawable mDivider;
public DividerItemDecoration(Context context, int orientation) {
//获取自定义Drawable
mDivider = context.getResources().getDrawable(R.drawable.item_divider);
//设置方向
setOrientation(orientation);
}
//1.调用此方法(首先会先获取条目之间的间隙高度---Rect矩形区域)
// 获得条目的偏移量(所有的条目都回调用一次该方法)
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
if (mOrientation == LinearLayoutManager.VERTICAL) {//垂直
outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
} else {//水平
outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
}
}
//2.调用这个绘制方法, RecyclerView会毁掉该绘制方法,需要你自己去绘制条目的间隔线
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
if (mOrientation == LinearLayoutManager.VERTICAL) {//垂直
drawVertical(c, parent);
} else {//水平
drawHorizontal(c, parent);
}
}
public void setOrientation(int orientation) {
if (orientation != LinearLayoutManager.HORIZONTAL && orientation != LinearLayoutManager.VERTICAL) {
throw new IllegalArgumentException("非水平或者竖直方向的枚举类型");
}
this.mOrientation = orientation;
}
private void drawVertical(Canvas c, RecyclerView parent) {
// 画水平线
int left = parent.getPaddingLeft();
int right = parent.getWidth() - parent.getPaddingRight();
int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
View child = parent.getChildAt(i);
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
int top = child.getBottom() + params.bottomMargin + Math.round(ViewCompat.getTranslationY(child));
int bottom = top + mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
private void drawHorizontal(Canvas c, RecyclerView parent) {
int top = parent.getPaddingTop();
int bottom = parent.getHeight() - parent.getPaddingBottom();
int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
View child = parent.getChildAt(i);
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
int left = child.getRight() + params.rightMargin + Math.round(ViewCompat.getTranslationX(child));
int right = left + mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
}
代码分析:
- 提供构造方法,加载Drawable,设置当前的布局方向。
- getItemOffsets中,根据布局方向设置outRect矩形区域。
- onDraw方法里面进行Drawable的绘制。
当然,我们也可以使用系统自带的分割线:
int[] attrs = new int[]{
android.R.attr.listDivider
};
TypedArray typedArray = context.obtainStyledAttributes(attrs);
mDivider = typedArray.getDrawable(0);
typedArray.recycle();
如果你不喜欢,也可以在style文件里面修改:
进阶例子---网格布局分割线
public class DividerGridViewItemDecoration extends ItemDecoration {
private Drawable mDivider;
private int[] attrs = new int[]{
android.R.attr.listDivider
};
public DividerGridViewItemDecoration(Context context) {
TypedArray typedArray = context.obtainStyledAttributes(attrs);
mDivider = typedArray.getDrawable(0);
typedArray.recycle();
}
@Override
public void onDraw(Canvas c, RecyclerView parent, State state) {
drawVertical(c, parent);
drawHorizontal(c, parent);
}
private void drawHorizontal(Canvas c, RecyclerView parent) {
int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
View child = parent.getChildAt(i);
LayoutParams params = (LayoutParams) child.getLayoutParams();
int left = child.getLeft() - params.leftMargin;
int right = child.getRight() + params.rightMargin;
int top = child.getBottom() + params.bottomMargin;
int bottom = top + mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
private void drawVertical(Canvas c, RecyclerView parent) {
int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
View child = parent.getChildAt(i);
LayoutParams params = (LayoutParams) child.getLayoutParams();
int left = child.getRight() + params.rightMargin;
int right = left + mDivider.getIntrinsicWidth();
int top = child.getTop() - params.topMargin;
int bottom = child.getBottom() + params.bottomMargin;
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
@Override
@Deprecated
public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) {
int right = mDivider.getIntrinsicWidth();
int bottom = mDivider.getIntrinsicHeight();
if (isLastColum(itemPosition, parent)) {
right = 0;
}
if (isLastRow(itemPosition, parent)) {
bottom = 0;
}
outRect.set(0, 0, right, bottom);
}
private boolean isLastRow(int itemPosition, RecyclerView parent) {
int spanCount = getSpanCount(parent);
LayoutManager layoutManager = parent.getLayoutManager();
if (layoutManager instanceof GridLayoutManager) {
int childCount = parent.getAdapter().getItemCount();
int lastRowCount = childCount % spanCount;
if (lastRowCount == 0 || lastRowCount < spanCount) {
return true;
}
}
return false;
}
private boolean isLastColum(int itemPosition, RecyclerView parent) {
LayoutManager layoutManager = parent.getLayoutManager();
if (layoutManager instanceof GridLayoutManager) {
int spanCount = getSpanCount(parent);
if ((itemPosition + 1) % spanCount == 0) {
return true;
}
}
return false;
}
private int getSpanCount(RecyclerView parent) {
LayoutManager layoutManager = parent.getLayoutManager();
if (layoutManager instanceof GridLayoutManager) {
GridLayoutManager lm = (GridLayoutManager) layoutManager;
int spanCount = lm.getSpanCount();
return spanCount;
}
return 0;
}
}
道理都一样,只不过画的时候水平竖直方向都需要画而已。
拓展--源码分析
在RecyclerView里面,我们看看源码是怎么把我们自己的Decoration画上去的,
@Override
public void onDraw(Canvas c) {
super.onDraw(c);
final int count = mItemDecorations.size();
for (int i = 0; i < count; i++) {
mItemDecorations.get(i).onDraw(c, this, mState);
}
}
从RecyclerView的绘制可以看出,在RecyclerView绘制的时候,是通过循环不断回调Decoration的onDraw(自己实现)进行绘制的。
添加头部和尾部
头部和尾部需要我们自己处理,我们可以参考ListView的实现:
addHeaderView(){
if (mAdapter != null) {
if (!(mAdapter instanceof HeaderViewListAdapter)) {
mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, mAdapter);
}
}
setAdapter(){
if (mHeaderViewInfos.size() > 0|| mFooterViewInfos.size() > 0) {
mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, adapter);
} else {
mAdapter = adapter;
}
}
我们通过看ListView的源码,可以知道,ListView在添加头部(尾部)、setAdapter的时候,内部其实是利用装饰者设计模式,利用一个HeaderViewListAdapter来包装我们自己的Adapter,因此我们可以模仿这样的实现,自定义一个RecyclerView:
public class WrapRecyclerView extends RecyclerView {
//头部尾部信息
private ArrayList mHeaderViewInfos = new ArrayList<>();
private ArrayList mFooterViewInfos = new ArrayList<>();
private Adapter mAdapter;
public WrapRecyclerView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void addHeaderView(View v) {
mHeaderViewInfos.add(v);
if (mAdapter != null) {
if (!(mAdapter instanceof HeaderViewRecyclerAdapter)) {
mAdapter = new HeaderViewRecyclerAdapter(mHeaderViewInfos, mFooterViewInfos, mAdapter);
}
}
}
public void addFooterView(View v) {
mFooterViewInfos.add(v);
if (mAdapter != null) {
if (!(mAdapter instanceof HeaderViewRecyclerAdapter)) {
mAdapter = new HeaderViewRecyclerAdapter(mHeaderViewInfos, mFooterViewInfos, mAdapter);
}
}
}
@Override
public void setAdapter(Adapter adapter) {
if (mHeaderViewInfos.size() > 0 || mFooterViewInfos.size() > 0) {
mAdapter = new HeaderViewRecyclerAdapter(mHeaderViewInfos, mFooterViewInfos, adapter);
} else {
mAdapter = adapter;
}
super.setAdapter(mAdapter);
}
}
作为包装类的Adapter如下:
public class HeaderViewRecyclerAdapter extends Adapter {
private static final int VIEW_TYPE_HEADER = 0;
private static final int VIEW_TYPE_FOOTER = 1;
private static final int VIEW_TYPE_ITEM = 2;
private Adapter mAdapter;
ArrayList mHeaderViewInfos;
ArrayList mFooterViewInfos;
public HeaderViewRecyclerAdapter(ArrayList headerViewInfos, ArrayList footerViewInfos, Adapter adapter) {
mAdapter = adapter;
if (headerViewInfos == null) {
mHeaderViewInfos = new ArrayList<>();
} else {
mHeaderViewInfos = headerViewInfos;
}
if (footerViewInfos == null) {
mFooterViewInfos = new ArrayList<>();
} else {
mFooterViewInfos = footerViewInfos;
}
}
@Override
public int getItemCount() {
if (mAdapter != null) {
return getFootersCount() + getHeadersCount() + mAdapter.getItemCount();
} else {
return getFootersCount() + getHeadersCount();
}
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
int numHeaders = getHeadersCount();
if (position < numHeaders) {
return;
}
final int adjPosition = position - numHeaders;
int adapterCount = 0;
if (mAdapter != null) {
adapterCount = mAdapter.getItemCount();
if (adjPosition < adapterCount) {
mAdapter.onBindViewHolder(holder, adjPosition);
return;
}
}
}
@Override
public int getItemViewType(int position) {
int numHeaders = getHeadersCount();
if (position < numHeaders) {
return VIEW_TYPE_HEADER;
}
final int adjPosition = position - numHeaders;
int adapterCount = 0;
if (mAdapter != null) {
adapterCount = mAdapter.getItemCount();
if (adjPosition < adapterCount) {
return mAdapter.getItemViewType(adjPosition);
}
}
return VIEW_TYPE_FOOTER;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == VIEW_TYPE_HEADER) {
return new HeaderViewHolder(mHeaderViewInfos.get(0));
} else if (viewType == VIEW_TYPE_FOOTER) {
return new HeaderViewHolder(mFooterViewInfos.get(0));
}
return mAdapter.onCreateViewHolder(parent, viewType);
}
public int getHeadersCount() {
return mHeaderViewInfos.size();
}
public int getFootersCount() {
return mFooterViewInfos.size();
}
private static class HeaderViewHolder extends ViewHolder {
public HeaderViewHolder(View view) {
super(view);
}
}
}
主要就是根据getItemViewType返回不同类型的布局,然后在对应的方法进行了一次分发。
Item交互动画效果
接下来我们实现条目的拖拽效果以及条目的横向滑动删除效果,主要用到的是ItemTouchHelper这个类。
新建一个ItemTouchHelper对象,并且绑定到RecyclerView。
mItemTouchHelper = new ItemTouchHelper(new ItemTouchHelperCallback(mQqAdapter));
mItemTouchHelper.attachToRecyclerView(rv_list);
接下来介绍在创建ItemTouchHelper的时候需要传入的Callback,这是自定义的一个类,继承了ItemTouchHelper.Callback。
public class ItemTouchHelperCallback extends ItemTouchHelper.Callback {
private ItemMovedListener mItemMovedListener;
public ItemTouchHelperCallback(ItemMovedListener itemMovedListener) {
mItemMovedListener = itemMovedListener;
}
@Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
int swipeFlags = ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
return makeMovementFlags(dragFlags, swipeFlags);
}
@Override
public boolean isLongPressDragEnabled() {
return true;
}
@Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder fromHolder, RecyclerView.ViewHolder toHolder) {
if (fromHolder.getItemViewType() != toHolder.getItemViewType()) {
return false;
}
if (mItemMovedListener != null) {
mItemMovedListener.onItemMoved(fromHolder.getAdapterPosition(), toHolder.getAdapterPosition());
}
return true;
}
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
if (mItemMovedListener != null) {
mItemMovedListener.onItemRemoved(viewHolder.getAdapterPosition());
}
}
@Override
public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) {
viewHolder.itemView.setBackgroundColor(viewHolder.itemView.getContext().getResources().getColor(R.color.grey));
}
super.onSelectedChanged(viewHolder, actionState);
}
@Override
public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
viewHolder.itemView.setBackgroundColor(viewHolder.itemView.getContext().getResources().getColor(R.color.white));
viewHolder.itemView.setAlpha(1);//1~0
viewHolder.itemView.setScaleX(1);//1~0
viewHolder.itemView.setScaleY(1);//1~0
super.clearView(recyclerView, viewHolder);
}
@Override
public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
//dX:水平方向移动的增量(负:往左;正:往右)范围:0~View.getWidth 0~1
if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
//透明度动画
float alpha = 1 - Math.abs(dX) / viewHolder.itemView.getWidth();
viewHolder.itemView.setAlpha(alpha);//1~0
viewHolder.itemView.setScaleX(alpha);//1~0
viewHolder.itemView.setScaleY(alpha);//1~0
}
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
}
}
下面介绍几个需要重写的方法:
- getMovementFlags,返回需要监听的方向,其中包括拖拽以及滑动。这里我监听了上下拖拽以及左右的滑动。
- isLongPressDragEnabled,返回true代表当长按条目的时候开启拖拽效果,当然我们也可以指定触摸一个View开始拖拽。
- onMoveon、Swiped的时候,需要回调Adapter中的notify方法,因此我自定义了一个接口。
- onSelectedChanged,在条目被长按触发滑动或者拖拽效果的时候(不是ACTION_STATE_IDLE状态),方便用户设置一个属性,例如背景色。clearView是动画结束的时候的回调,为了就是清除一些属性,不然的话由于ItemView的复用会导致BUG。
- onChildDraw方法就是在滑动的时候(ACTION_STATE_SWIPE状态),实现一个平移、缩放、渐变等动画。当然,在这个方法里面还可以实现类似QQ的滑动删除效果,具体实现效果网上很多,这里就不再赘述了。
其中,ItemMovedListener的定义如下:
public interface ItemMovedListener {
void onItemMoved(int fromPosition, int toPosition);
void onItemRemoved(int position);
}
ItemMovedListener由Adapter实现:
@Override
public void onItemMoved(int fromPosition, int toPosition) {
Collections.swap(list, fromPosition, toPosition);
notifyItemMoved(fromPosition, toPosition);
}
@Override
public void onItemRemoved(int position) {
list.remove(position);
notifyItemRemoved(position);
}
在进行了拖拽或者滑动的时候,一定要进行相应的数据处理以及notify。
除了长按触发拖拽,ItemTouchHelper有一个onStartDrag(RecyclerView.ViewHolder holder)方法也可以触发拖拽。因此我们又可以抽取一个接口:
public interface StartDragListener {
void onStartDrag(RecyclerView.ViewHolder holder);
}
StartDragListener由Activity实现:
@Override
public void onStartDrag(RecyclerView.ViewHolder holder) {
mItemTouchHelper.startDrag(holder);
}
并且在Adapter构造的时候传进去,在onBindViewHolder的时候就可以进行回调:
@Override
public void onBindViewHolder(final MyViewHolder holder, int location) {
holder.iv_logo.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
if (mStartDragListener != null) {
mStartDragListener.onStartDrag(holder);
}
}
return false;
}
});
}
这里是触摸一个ImageView就可以发生拖拽了。
思考
一个类如果想调用另外一个类的方法,但是那个类又不想直接持有另外一个类的对象的时候(比较大的类、业务分离),就可以通过抽取接口的形式来实现。
如果觉得我的文字对你有所帮助的话,欢迎关注我的公众号:
我的群欢迎大家进来探讨各种技术与非技术的话题,有兴趣的朋友们加我私人微信huannan88,我拉你进群交(♂)流(♀)。