preface

The last article said the implementation of the side slide button, is very wonderful, especially to judge the state of the touch event and the details of the processing, this chapter about the side slide delete and drag function, here looks more complicated, but the implementation is much simpler.

Address of the previous slide button: juejin.cn/post/700762…

RecyclerView has a class that handles swiping and dragging. This class is ItemTouchHelper.

This is a utility class to add swipe to dismiss and drag & drop support to RecyclerView.

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.
Copy the code

The utility class for sliding delete and drag requires a RecyclerView and Callback to configure which touches are activated and what needs to be done when they are performed.

Since The Android system has code to do this for us, let’s just go ahead and do it.

Source project github address: github.com/angcyo/DslA…

The body of the

First define a drag helper class:

class DragCallbackHelper : ItemTouchHelper.Callback()
Copy the code

The Callback, as I mentioned above, basically tells ItemTouchHelper what the application wants to do through the class’s callbacks. Take a look at these method callbacks in turn.

getMovementFlags

Method prototype:

public abstract int getMovementFlags(@NonNull RecyclerView recyclerView,
        @NonNull ViewHolder viewHolder);
Copy the code

Note:

Should return a composite flag which defines the enabled move directions
in each state (idle, swiping, dragging).
Copy the code

A combination of flags should be returned to indicate which direction moves are available in different states (idle, slide, drag).

At first glance, we need to set different directions for each state, which is quite complicated. In fact, the system has already thought about this for us. We only need to execute one method, which also says in the comment:

Instead of composing this flag manually, 
you can use makeMovementFlags(int, int) or makeFlag(int, int).
Copy the code

Here you just need to execute the makeMovementFlags method and use the return value of this method as the return value of getMovementFlags.

public static int makeMovementFlags(int dragFlags, int swipeFlags) {
    return makeFlag(ACTION_STATE_IDLE, swipeFlags | dragFlags)
            | makeFlag(ACTION_STATE_SWIPE, swipeFlags)
            | makeFlag(ACTION_STATE_DRAG, dragFlags);
}
Copy the code

Here we just need to gather up dragFlags and swipeFlags2 parameters, which indicate the direction of the drag activation and the direction of the swipeFlags2 activation respectively.

For example, if I want to drag in four directions and sideslip left and right, let’s look at the orientation definition. This is defined in TouchHelper:

public static final int UP = 1;
public static final int DOWN = 1 << 1;
public static final int LEFT = 1 << 2;
public static final int RIGHT = 1 << 3;
Copy the code

That’s just for the purposes of the and operation, so the whole direction is:

const val FLAG_ALL = ItemTouchHelper.LEFT or
        ItemTouchHelper.RIGHT or
        ItemTouchHelper.DOWN or
        ItemTouchHelper.UP
Copy the code

The horizontal direction is:

const val FLAG_HORIZONTAL = ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT
Copy the code

Ok, so let’s look at the implementation code in getMovementFlags:

override fun getMovementFlags( recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder ): Int {// Get the viewHolder Item data val dslAdapterItem = _dslAdapter? .getItemData(viewHolder.adapterPosition) return dslAdapterItem? .run {// check if item is configured with dragFlag, Otherwise, use DragCallbackHelp default configuration val dFlag = the if (itemDragFlag > = 0) itemDragFlag else [email protected] // Check whether item swipeFlag is configured. Otherwise, use DragCallbackHelp default configuration val sFlag = the if (itemSwipeFlag > = 0) itemSwipeFlag else [email protected] // Call makeMovementFlags to return flag (if (itemDragEnable) dFlag else FLAG_NONE, if (itemSwipeEnable) sFlag else FLAG_NONE ) } ?: FLAG_NONE }Copy the code

When this is configured, just bind ItemTouchHelper to a RecyclerView:

fun attachToRecyclerView(recyclerView: RecyclerView) { _recyclerView = recyclerView _itemTouchHelper = _itemTouchHelper ? : ItemTouchHelper(this) _itemTouchHelper? .attachToRecyclerView(recyclerView) }Copy the code

At this point, we can drag and drop items, which is done entirely by ItemTouchHelper. Of course, there are a number of callback methods that are performed to customize the Item, as described below.

onSelectedChanged

Method prototype:

public void onSelectedChanged(@Nullable ViewHolder viewHolder, int actionState) {
    if (viewHolder != null) {
        ItemTouchUIUtilImpl.INSTANCE.onSelected(viewHolder.itemView);
    }
}
Copy the code

Note:

Called when the ViewHolder swiped or dragged by the ItemTouchHelper is changed If you override this method, you should call super. Params: ViewHolder -- The new viewHolder that is being swiped or dragged. Might be null if it is cleared. ActionState -- One of ACTION_STATE_IDLE, ACTION_STATE_SWIPE or ACTION_STATE_DRAG.Copy the code

This is the first method that is called back when the drag is performed, indicating that the viewHolder has changed, and when the method needs to be overridden, super must be called.

There are three actionStates in total. For example, drag a ViewHolder and its state changes are:

ACTION_STATE_DRAG – > ACTION_STATE_IDLE,

So we can perform some callbacks in this method, such as DRAG delete, which will display the delete View when the method calls back to DRAG.

After this method is called back, the ViewHolder is shifted during the finger drag, and immediately the ViewHolder’s displacement is called back, the onChildDraw method.

onChildDraw

Method prototype:

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);
}
Copy the code

Note:

Called by ItemTouchHelper on RecyclerView's onDraw callback. If you would like to customize how your View's respond to user interactions, this is a good place to override. Default implementation translates the child by the given dX, dY. 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 android.view.ViewGroup.getChildDrawingOrder(int, int) and on L and after, it changes View's elevation value to be greater than all other children.)Copy the code

Call timing: Call back this method when RecyclerView is drawing.

Function: When dragging or sliding, you want to customize the View’s response based on gestures.

The default implementation is to shift the child View, the ViewHolder, by dX and dY. The ItemTouchHelper is also responsible for dragging and drawing the child object, and this is done by changing the drawing order of the child views of the ViewGroup, which is essentially heightening the Z-axis of the View being dragged, as I’ll explain later.

Here we can do some effects. The most common is to display text at the ViwHolder position when swiping to delete.

override fun onChildDraw( canvas: Canvas, recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, dX: Float, dY: Float, actionState: Int, isCurrentlyActive: Boolean ) { super.onChildDraw(canvas, recyclerView, viewHolder, dX, dY, actionState, IsCurrentlyActive) // If (enableSwipeTip && isCurrentlyActive && actionState == when swipeTip is displayed ACTION_STATE_SWIPE) {itemTouchHelper.action_state_swipe) {// View val itemView = viewholder. itemView Float = if (dX > 0) {// Swipe right to delete itemView.left.tofloat ()} else {// Swipe left to delete (itemView.right -) _drawtext._paint.measureText (swipetiptext.tostring ()))} //y indicates that the y coordinate of swipeTipText needs to be drawn. Float = ItemView.top + ItemView.measuredheight / 2 - _drawtext._paint.textheight () / 2 // Save canvas Canvas. Translate (x, y) // drawText canvas. DrawText (swipetiptext.tostring (),0f,0f,_paint) canvas. Restore ()}}Copy the code

Take a look at the results:

Here is a canvas operation, why frequent save, translate and restore? The reason is that the canvas is RecyclerView canvas, that is, the width and height of the canvas is RecyclerView width and height. So I need to move the canvas to the desired position and then draw it, of course I won’t move it, I can also draw Text with coordinates:

canvas.drawText(swipeTipText.toString(),x,y,_paint)
Copy the code

This will do the same.

So the key to this function is to draw some effects from the canvas by dragging or sliding, where the position of the current itemView is the key and the position of the drawing is the key.

Now that you can drag or slide, and draw your own View while sliding, there must be a callback after dragging or sliding. Let’s start with the onSwiped method.

onSwiped

Method prototype:

public abstract void onSwiped(@NonNull ViewHolder viewHolder, int direction);
Copy the code

Note:

Called when a ViewHolder is swiped by the user.

If you are returning relative directions (START , END) 
from the getMovementFlags(RecyclerView, RecyclerView.ViewHolder) method, 
this method will also use relative directions. Otherwise,
it will use absolute directions.

If you don't support swiping, this method will never be called.
Copy the code

Call timing: When a slide occurs, note that it is a slide, not a drag.

This class does not delete the View but does not delete the data on the display layer. If the View does not slide and the data is not deleted, the View is removed from the display layer.

So it needs to be processed after the Swipe callback, that is, delete the item data and refresh the interface at the same time.

Take a look at the code:

override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { Log.i(TAG, "onSwiped: Direction = $direction") _swipeHappened = true .apply { getItemData(viewHolder.adapterPosition)? .apply {// Delete data while calling notify removeItem(this) //item delete callback onItemSwipeDeleted? .invoke(this) } } }Copy the code

This can be quickly redrawn after deletion, with the following effect:

Okay, so with swipe delete out of the way, let’s talk a little bit more about drag, and for drag reorder, that’s what ItemTouchHelper does for us as well, and there’s an onMove function involved.

onMove

Method prototype:

public abstract boolean onMove(@NonNull RecyclerView recyclerView,
        @NonNull ViewHolder viewHolder, @NonNull ViewHolder target);
Copy the code

Note:

Called when ItemTouchHelper wants to move the dragged item from its old position to the new position.
Copy the code

When you drag and drop a ViewHolder, ItemTouchHelper will automatically handle it for you. The method will call back when it feels like it can reorder and swap positions, and will not call back when it doesn’t. Here’s what happens:

Note that for every interaction, moMove will be called back. For example, in this GIF, I had 4 interactions and reorder, so it will be called back 4 times, printed as follows:

The 2021-09-16 09:11:50. 546, 7881-7881 / com. Angcyo. Dsladapter. Demo I/zyh: onMove: FromPos = 5 toPos = 6 09:11:52 2021-09-16. 396. 7881-7881 / com angcyo. Dsladapter. The demo I/zyh: onMove: FromPos = 6 toPos 09:11:53 = 7 2021-09-16. 520. 7881-7881 / com angcyo. Dsladapter. The demo I/zyh: onMove: FromPos = 7 toPos = 8 09:11:54. 2021-09-16, 995, 7881-7881 / com. Angcyo. Dsladapter. The demo I/zyh: onMove: 8 toPos fromPos = = 5Copy the code

As you can also see from this print, onMove is triggered every time you swap.

Of course, the above code is logically correct, and write the data exchange, here to write good logic must understand the parameters and return value of the callback function.

Parameters:

RecyclerView: Indicates the recyclerView being dragged.

ViewHolder: viewHolder being dragged.

Target: ViewHolder where the target is being replaced.

The return value:

If the function returns true, ItemTouchHelper will tell you that the viewHolder has moved from the original position to the target position, so if you want to return true, you must have done something.

For example, I do nothing and return true:

override fun onMove( recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder ): Boolean { val fromPosition = viewHolder.adapterPosition val toPosition = target.adapterPosition Log.i(TAG, "onMove: FromPos = $fromPosition toPos = $toPosition") // If viewHolder has moved to target, return true}Copy the code

The effect is as follows:

Print the following:

The 2021-09-16 09:35:10. 174, 6968-6968 / com. Angcyo. Dsladapter. Demo I/zyh: onMove: FromPos = 5 toPos = 6 09:35:10 2021-09-16. 183. 6968-6968 / com angcyo. Dsladapter. The demo I/zyh: onMove: FromPos = 5 toPos = 6 09:35:10 2021-09-16. 191. 6968-6968 / com angcyo. Dsladapter. The demo I/zyh: onMove: FromPos = 5 toPos = 6 09:35:10 2021-09-16. 199. 6968-6968 / com angcyo. Dsladapter. The demo I/zyh: onMove: FromPos = 5 toPos = 6 09:35:10 2021-09-16. 208. 6968-6968 / com angcyo. Dsladapter. The demo I/zyh: onMove: FromPos = 5 toPos = 6 09:35:10 2021-09-16. 216. 6968-6968 / com angcyo. Dsladapter. The demo I/zyh: onMove: FromPos = 5 toPos = 6 09:35:10 2021-09-16. 225. 6968-6968 / com angcyo. Dsladapter. The demo I/zyh: onMove: FromPos = 5 toPos = 6 09:35:10 2021-09-16. 232. 6968-6968 / com angcyo. Dsladapter. The demo I/zyh: onMove: FromPos = 5 toPos = 6 09:35:10 2021-09-16. 241. 6968-6968 / com angcyo. Dsladapter. The demo I/zyh: onMove: FromPos = 5 toPos = 6 09:35:10 2021-09-16. 249. 6968-6968 / com angcyo. Dsladapter. The demo I/zyh: onMove: FromPos = 5 toPos = 6 09:35:10 2021-09-16. 258. 6968-6968 / com angcyo. Dsladapter. The demo I/zyh: onMove: FromPos = 5 toPos = 6 09:35:10 2021-09-16. 266. 6968-6968 / com angcyo. Dsladapter. The demo I/zyh: onMove: FromPos = 5 toPos = 6 09:35:10 2021-09-16. 274. 6968-6968 / com angcyo. Dsladapter. The demo I/zyh: onMove: FromPos = 5 toPos = 6 09:35:10 2021-09-16. 283. 6968-6968 / com angcyo. Dsladapter. The demo I/zyh: onMove: FromPos = 5 toPos = 6 09:35:10 2021-09-16. 291. 6968-6968 / com angcyo. Dsladapter. The demo I/zyh: onMove: FromPos = 5 toPos = 6 09:35:10 2021-09-16. 299. 6968-6968 / com angcyo. Dsladapter. The demo I/zyh: onMove: FromPos = 5 toPos = 6 09:35:10 2021-09-16. 308. 6968-6968 / com angcyo. Dsladapter. The demo I/zyh: onMove: FromPos = 5 toPos = 6 09:35:10 2021-09-16. 316. 6968-6968 / com angcyo. Dsladapter. The demo I/zyh: onMove: FromPos = 5 toPos = 6 09:35:10 2021-09-16. 332. 6968-6968 / com angcyo. Dsladapter. The demo I/zyh: onMove: FromPos = 5 toPos 09:35:10 = 8 2021-09-16. 341. 6968-6968 / com angcyo. Dsladapter. The demo I/zyh: onMove: FromPos = 5 toPos 09:35:10 = 8 2021-09-16. 349. 6968-6968 / com angcyo. Dsladapter. The demo I/zyh: onMove: FromPos = 5 toPos 09:35:10 = 8 2021-09-16. 358. 6968-6968 / com angcyo. Dsladapter. The demo I/zyh: onMove: FromPos = 5 toPos 09:35:10 = 8 2021-09-16. 366. 6968-6968 / com angcyo. Dsladapter. The demo I/zyh: onMove: FromPos = 5 toPos 09:35:10 = 8 2021-09-16. 374. 6968-6968 / com angcyo. Dsladapter. The demo I/zyh: onMove: FromPos = 5 toPos 09:35:10 = 8 2021-09-16. 382. 6968-6968 / com angcyo. Dsladapter. The demo I/zyh: onMove: FromPos = 5 toPos 09:35:10 = 8 2021-09-16. 391. 6968-6968 / com angcyo. Dsladapter. The demo I/zyh: onMove: FromPos = 5 toPos 09:35:10 = 8 2021-09-16. 399. 6968-6968 / com angcyo. Dsladapter. The demo I/zyh: onMove: FromPos = 5 toPos 09:35:10 = 8 2021-09-16. 408. 6968-6968 / com angcyo. Dsladapter. The demo I/zyh: onMove: FromPos = 5 toPos 09:35:10 = 8 2021-09-16. 416. 6968-6968 / com angcyo. Dsladapter. The demo I/zyh: onMove: FromPos = 5 toPos 09:35:10 = 8 2021-09-16. 424. 6968-6968 / com angcyo. Dsladapter. The demo I/zyh: onMove: FromPos = 5 toPos 09:35:10 = 8 2021-09-16. 432. 6968-6968 / com angcyo. Dsladapter. The demo I/zyh: onMove: FromPos = 5 toPos 09:35:10 = 8 2021-09-16. 441. 6968-6968 / com angcyo. Dsladapter. The demo I/zyh: onMove: FromPos = 5 toPos 09:35:10 = 8 2021-09-16. 483. 6968-6968 / com angcyo. Dsladapter. The demo I/zyh: onMove: FromPos = 5 toPos 09:35:10 = 8 2021-09-16. 491. 6968-6968 / com angcyo. Dsladapter. The demo I/zyh: onMove: FromPos = 5 toPos 09:35:10 = 8 2021-09-16. 499. 6968-6968 / com angcyo. Dsladapter. The demo I/zyh: onMove: fromPos = 5 toPos = 8Copy the code

When onMove is called back, the correct logic is to replace the data of the two positions and call notify to refresh the data of the two positions:

Here is the normal handling:

// Override fun onMove(recyclerView: recyclerView, viewHolder: recyclerView.ViewHolder, target: recyclerView. RecyclerView.ViewHolder ): Boolean { val fromPosition = viewHolder.adapterPosition val toPosition = target.adapterPosition Log.i(TAG, "onMove: FromPos = $fromPosition toPos = $toPosition") // If [viewHolder] has moved to [target], return _dslAdapter? .run {// The code can be ignored here, Is the source library has the logic val validFilterDataList = getValidFilterDataList () val fromItem = validFilterDataList. GetOrNull (fromPosition) Val toItem = validFilterDataList. GetOrNull (toPosition) if (fromItem = = null | | toItem = = null) {/ / abnormal operation returns false false} Else {// This code can be ignored, the source library has this logic, Val fromList = getItemListPairByItem(fromItem) val toPair = getItemListPairByItem(toItem) val fromList  MutableList<DslAdapterItem>? = fromPair.first val toList: MutableList<DslAdapterItem>? = toPair.first if (fromList.isNullOrEmpty() && toList.isNullOrEmpty()) { false } else { Collections.swap(validFilterDataList, fromPosition, If (fromList == toList) {collections.swap (fromList, fromPair. Second, fromList == toList) {collections.swap (fromList, fromPair. } else {val temp = fromList!! [fromPair.second] fromList[fromPair.second] = toList!! [topair.second] toList[topair.second] = temp} _updateAdapterItems() NotifyItemMove (fromPosition, toPosition) _dragHappened = true onItemMoveChanged? .invoke(fromList!! , toList!! , fromPair.second, toPair.second) true } } } ? : false }Copy the code

To sum up: when onMove is called, drag occurs. To handle this correctly, you need to exchange the values of the two positions first, and then call notifyItemMove to refresh the interface. Finally, it returns true, otherwise it returns false.

There is also a callback for drag, which is whether the current viewHolder can be swapped with the target viewHolder. This is controlled by a callback called canDropOver.

canDropOver

Method prototype:

public boolean canDropOver(@NonNull RecyclerView recyclerView, @NonNull ViewHolder current,
        @NonNull ViewHolder target) {
    return true;
}
Copy the code

Note:

Return true if the current ViewHolder can be dropped over the the target ViewHolder.
Copy the code

Very simply, returning true means that the current ViewHolder can be replaced by the target ViewHolder.

For example:

override fun canDropOver( recyclerView: RecyclerView, current: RecyclerView.ViewHolder, target: RecyclerView. ViewHolder) : Boolean {/ / when dragged ViewHolder is 5, cannot replaced by drag and drop the return current. AbsoluteAdapterPosition! = 5 return super.canDropOver(recyclerView, current, target) }Copy the code

Drag pos equals 5 views and cannot be exchanged with other views:

conclusion

ItemTouchHelper is an ItemTouchHelper class that can be used as a callback to delete items from the ItemTouchHelper class. It can be used as a callback to delete items from the ItemTouchHelper class.

The source part of which you can view the above said open source library DragCallbackHelper class, will not copy a lot of code, mainly said the process:

Lateral spreads to delete

For sideslip deletion, the above three methods are mainly called back, and the specific logic is determined according to the requirements.

Drag and drop replacement

For drag-and-drop, the method callbacks above, pay attention to the order and actions in onMove.

ItemTouchHelper is a callback for ItemTouchHelper. It is a callback for ItemTouchHelper. It is a callback for ItemTouchHelper.