前言
在日常开发中,RecyclerView起了很重要的作用,当然也少不了一些对RecyclerView的item的操作,比如最常见的是滑动删除和拖拽删除,效果图如:
原理解析
ItemTouchHelper
首先是这个ItemTouchHelper,这个类是RecyclerView自带的一个类,看一下官方解释:
This is a utility class to add swipe to dismiss and drag & drop support to RecyclerView.
这是一个工具类,来协助RecyclerView完成item的滑动、拖拽等效果
It works with a RecyclerView and a Callback class, which configures what type of interactions
are enabled and also receives events when user performs these actions.
它与一个RecyclerView和一个回调类一起工作,用来配置哪些交互类型是enabled的,然后当用户执行这些操作时来接收事件。
比如下面代码:
picDragHelperCallback = PicDragHelperCallback(adapter!!, delArea)
picDragHelperCallback?.setScale(1.2f) //1.3f
picDragHelperCallback?.setAlpha(0.9f)
helper = ItemTouchHelper(picDragHelperCallback!!)
helper?.attachToRecyclerView(picList)
ItemTouchHelper创建实例时便传入一个回调类,然后通过attachToRecyclerView方法绑定到一个RecyclerView上。
ItemTouchHelper.Callback
通过上面简单的了解后,我们来看一下这个Callback方法,其中比如拖拽的功能就是在这个里面实现的,我们看一下它是如何工作的,先看一下官方解释:
This class is the contract between ItemTouchHelper and your application. It lets you control which touch behaviors are enabled per each ViewHolder and also receive callbacks when user performs these actions.
这个类相当于一个连接ItemTouchHelper和自己应用之间的桥梁,它可以控制用户的哪些操作的enabled的,同时也接收用户执行这些操作时的回调。
比如下面一些常见的回调:
To control which actions user can take on each view,
you should override {@link #getMovementFlags(RecyclerView, ViewHolder)}
and return appropriate set of direction flags. ({@link #LEFT}, {@link #RIGHT}, {@link #START}, {@link #END},{@link #UP}, {@link #DOWN}).
You can use {@link #makeMovementFlags(int, int)} to easily construct it. Alternatively, you can use {@link SimpleCallback}.
控制用户对某个view可执行的操作可以复写getMovementFlags来获取该item,然后返回适当的方向flags,通过makeMovementFlags来控制它,比如下面这个仿微信发朋友圈的RecyclerView中,其中加号这个item便不可以移动,其他图片item可以拖拽:
@Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
int dragFlags;
if (viewHolder instanceof PicMgrAdapter.PicAddViewHolder) {
return 0;
}
dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
int swipeFlags = 0;
return makeMovementFlags(dragFlags, swipeFlags);
}
这样操作后,当对于是加号的ViewHolder就不会有任何操作,对于非加号的可以拖拽,当时不可以滑动删除这种。
再比如这个回调:
If user drags an item, ItemTouchHelper will call
{@link Callback#onMove(RecyclerView, ViewHolder, ViewHolder) onMove(recyclerView, dragged, target)}.
Upon receiving this callback, you should move the item from the old position ({@code dragged.getAdapterPosition()}) to new position
({@code target.getAdapterPosition()}) in your adapter and also call {@link RecyclerView.Adapter#notifyItemMoved(int, int)}.
如果用户拖拽item,ItemTouchHelper将会调用onMove方法,通过这个方法,可以将一个item从它的旧位置移动到一个新位置,然后通过notifyItemMoved来完成刷新,其中onMove方法介绍:
public abstract boolean onMove(@NonNull RecyclerView recyclerView,
@NonNull ViewHolder viewHolder, @NonNull ViewHolder target);
Called when ItemTouchHelper wants to move the dragged item from its old position to
* the new position.
If this method returns true, ItemTouchHelper assumes {@code viewHolder} has been moved
* to the adapter position of {@code target} ViewHolder
这个onMove有3个参数,recyclerView不用说了,viewHolder就是用户拖拽的那个item,target就是拖拽完成合适的位置,当返回false的时候,这次拖拽无效,当返回true的时候,ItemTouchHelper会假设viewHolder已经移动到target位置了,然后通过刷新便可以完成移动位置的操作。
比如下面这个例子中实现拖拽移动位置:
@Override
public boolean onMove(RecyclerView recyclerView
, RecyclerView.ViewHolder viewHolder
, RecyclerView.ViewHolder target) {
if (viewHolder.getItemViewType() != target.getItemViewType()) {
return false;
}
if (target instanceof PicMgrAdapter.PicAddViewHolder) {
return false;
}
ArrayList list = mAdapter.getList();
if (list == null || list.size() < 2) {
return false;
}
int from = viewHolder.getAdapterPosition();
int endPosition = target.getAdapterPosition();
Logger.d("recyclerView 发生滑动 move from " + from + "end " +endPosition);
delPos = endPosition;
Collections.swap(list, from, endPosition);
mAdapter.notifyItemMoved(from, endPosition);
return true;
}
当移动的开始位置和结束位置类型不同或者是加号或者list很少时都返回false,否则获取拖拽的from位置和end位置,通过Collections.swap来交换位置,再通过mAdapter.notifyItemMoved来完成数据刷新,最后返回true,这样一个拖拽交换的功能便完成了。
还有一个回调,可以让开发者更多的定制recyclerView的交互,它便是:
/**
* Called by ItemTouchHelper on RecyclerView's onDraw callback.
* <p>
* If you would like to customize how your View's respond to user interactions, this is
* a good place to override.
* <p>
* Default implementation translates the child by the given <code>dX</code>,
* <code>dY</code>.
* ItemTouchHelper also takes care of drawing the child after other children if it is being
* dragged. This is done using child re-ordering mechanism. On platforms prior to L, this
* is
* achieved via {@link android.view.ViewGroup#getChildDrawingOrder(int, int)} and on L
* and after, it changes View's elevation value to be greater than all other children.)
*
* @param c The canvas which RecyclerView is drawing its children
* @param recyclerView The RecyclerView to which ItemTouchHelper is attached to
* @param viewHolder The ViewHolder which is being interacted by the User or it was
* interacted and simply animating to its original position
* @param dX The amount of horizontal displacement caused by user's action
* @param dY The amount of vertical displacement caused by user's action
* @param actionState The type of interaction on the View. Is either {@link
* #ACTION_STATE_DRAG} or {@link #ACTION_STATE_SWIPE}.
* @param isCurrentlyActive True if this view is currently being controlled by the user or
* false it is simply animating back to its original state.
* @see #onChildDrawOver(Canvas, RecyclerView, ViewHolder, float, float, int,
* boolean)
*/
public void onChildDraw(@NonNull Canvas c, @NonNull RecyclerView recyclerView,
@NonNull ViewHolder viewHolder,
float dX, float dY, int actionState, boolean isCurrentlyActive) {
ItemTouchUIUtilImpl.INSTANCE.onDraw(c, recyclerView, viewHolder.itemView, dX, dY,
actionState, isCurrentlyActive);
}
这里明确说了当你需要让view有更多的响应交互,这个方法很值得重写。几个参数的含义:canvas就是绘制item的画布,recyclerView是关联的view,viewHolder是相应的item,这里当然可以被用户操作,dx和dy是通过用户操作导致的item的移动位置,actionState是区分交互类型是drag还是swipe,isCurrentlyActive的含义是当前动画是用户操作还是recyclerView自带的动画。
那么现在我们就做一个仿微信发朋友圈的,当把item拖拽到一定地方时删除该item:
@Override
public void onChildDraw(Canvas c,
RecyclerView recyclerView,
RecyclerView.ViewHolder viewHolder,
float dX,
float dY,
int actionState,
boolean isCurrentlyActive) {
if (delArea == null || isActivatingAniming(viewHolder.itemView)) {
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
return;
}
int delAreaWidth = delArea.getWidth();
int delAreaHeight = delArea.getHeight();
int[] delLocation = new int[2];
delArea.getLocationInWindow(delLocation);
int delAreaX = delLocation[0];
int delAreaY = delLocation[1];
int itemWidth = viewHolder.itemView.getWidth();
int itemHeight = viewHolder.itemView.getHeight();
int[] itemLocation = new int[2];
viewHolder.itemView.getLocationInWindow(itemLocation);
int itemX = itemLocation[0];
int itemY = itemLocation[1];
//Log.d("jiabin","itemWidth:" + itemWidth + " | itemHeight:" + itemHeight + " | itemX:" + itemX + " | itemY:" + itemY);
int scaleItemWidth = (int) (itemWidth * mMoveScale);
int scaleItemHeight = (int) (itemHeight * mMoveScale);
// int scaleItemWidth = itemWidth;
// int scaleItemHeight = itemHeight;
// int itemRight = itemX + scaleItemWidth;
// int itemBottom = itemY + scaleItemHeight;
int centerX = itemX + scaleItemWidth / 2;
int centerY = itemY + scaleItemHeight / 2;
boolean isInside = false;
// if (itemBottom > delAreaY && itemY < delAreaY + delAreaHeight && itemRight > delAreaX && itemX < delAreaX + delAreaWidth) {
// isInside = true;
// } else {
// isInside = false;
// }
if (centerY > delAreaY && centerY < delAreaY + delAreaHeight && centerX > delAreaX && centerX < delAreaX + delAreaWidth) {
isInside = true;
} else {
isInside = false;
}
if (isInside != mIsInside) {
if (tempHolder != null) {
if (isInside) {
mMoveScale = mInsideScale;
// viewHolder.itemView.setScaleX(mInsideScale);
// viewHolder.itemView.setScaleY(mInsideScale);
clearActivatingAnim(viewHolder.itemView);
startActivatingAnim(viewHolder.itemView, mScale, mInsideScale, 150);
viewHolder.itemView.setAlpha(mInsideAlpha);
//viewHolder.itemView.clearAnimation();
//viewHolder.itemView.startAnimation(mInScaleAnim);
} else {
mMoveScale = mScale;
// viewHolder.itemView.setScaleX(mScale);
// viewHolder.itemView.setScaleY(mScale);
clearActivatingAnim(viewHolder.itemView);
startActivatingAnim(viewHolder.itemView, mInsideScale, mScale, 150);
viewHolder.itemView.setAlpha(mAlpha);
//viewHolder.itemView.clearAnimation();
//viewHolder.itemView.startAnimation(mOutScaleAnim);
}
}
if (mDragListener != null) {
mDragListener.onDragAreaChange(isInside, tempHolder == null);
}
}
mIsInside = isInside;
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
}
网友评论