Drag and Swipe in RecyclerView. Part 2: Drag and Drop Controllers, Grid, and Custom Animations

Original author: Paul Burke
  • Transfer
  • Tutorial

Drag and Swipe in RecyclerView.  Part 2: Drag and Drop Controllers, Grid, and Custom Animations


In the first part, we looked at ItemTouchHelper and the implementation of ItemTouchHelper.Callback , which adds basic drag & drop and swipe-to-dismiss functions in RecyclerView. In this article, we will continue what was done in the previous one, adding support for the location of elements in the form of a grid, drag controllers list item selection and user swipe animation ( Eng. A swipe).


Mesh elements


Drag and Drop Controllers


When creating a list that supports drag & drop , you usually implement the ability to drag items by touch. This contributes to the comprehensibility and usability of the list in “edit mode”, and also recommended by material guidelines. Adding drag-and-drop controllers to our example is fabulously easy.


Drag and Drop Controllers


First update the layoutitem ( item_main.xml ).


<?xml version="1.0" encoding="utf-8"?><FrameLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:id="@+id/item"android:layout_width="match_parent"android:layout_height="?listPreferredItemHeight"android:clickable="true"android:focusable="true"android:foreground="?selectableItemBackground"><TextViewandroid:id="@+id/text"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center_vertical"android:layout_marginLeft="16dp"android:textAppearance="?android:attr/textAppearanceMedium" /><ImageViewandroid:id="@+id/handle"android:layout_width="?listPreferredItemHeight"android:layout_height="match_parent"android:layout_gravity="center_vertical|right"android:scaleType="center"android:src="@drawable/ic_reorder_grey_500_24dp" /></FrameLayout>

The image used for the drag and drop controller can be found in the Material Design icons and added to the project using the convenient plug- in icon generator in Android Studio.


As briefly mentioned in the last article, you can use ItemTouchHelper.startDrag(ViewHolder)to programmatically start dragging. So, all we need to do is update ViewHolderby adding a drag controller, and set up a simple touch handler that will call startDrag().


We need an interface to send the event through the chain:


publicinterfaceOnStartDragListener{
    /**
     * Called when a view is requesting a start of a drag.
     *
     * @param viewHolder The holder of the view to drag.
     */voidonStartDrag(RecyclerView.ViewHolder viewHolder);
}

Then determine ImageViewfor the drag controller in ItemViewHolder:


publicfinal ImageView handleView;
publicItemViewHolder(View itemView){
    super(itemView);
    // ...
    handleView = (ImageView) itemView.findViewById(R.id.handle);
}

and update RecyclerListAdapter:


privatefinal OnStartDragListener mDragStartListener;
publicRecyclerListAdapter(OnStartDragListener dragStartListener){
    mDragStartListener = dragStartListener;
    // ...
}
@OverridepublicvoidonBindViewHolder(final ItemViewHolder holder, 
        int position){
    // ...
    holder.handleView.setOnTouchListener(new OnTouchListener() {
        @OverridepublicbooleanonTouch(View v, MotionEvent event){
            if (MotionEventCompat.getActionMasked(event) == 
                    MotionEvent.ACTION_DOWN) {
                mDragStartListener.onStartDrag(holder);
            }
            returnfalse;
        }
    });
}

RecyclerListAdaptershould now look something like this .


All that is left to do is add OnStartDragListenerto the snippet:


publicclassRecyclerListFragmentextendsFragmentimplementsOnStartDragListener{
    // ...@OverridepublicvoidonViewCreated(View view, Bundle icicle){
        super.onViewCreated(view, icicle);
        RecyclerListAdapter a = new RecyclerListAdapter(this);
        // ...
    }
    @OverridepublicvoidonStartDrag(RecyclerView.ViewHolder viewHolder){
        mItemTouchHelper.startDrag(viewHolder);
    }
}

RecyclerListFragmentshould now look like this . Now, when you start the application, you can start dragging by touching the controller.


Drag and drop list items


Select item list


Now in our example, there is no visual indication of an item that is being dragged. Obviously, this should not be so, but it is easy to fix. With the help ItemTouchHelperyou can use the standard effects of highlighting an element On Lollipop and later versions of Android, the backlight “blurs” over the element as it interacts with it; in earlier versions, the element simply changes its color to darkened.


To implement this in our example, just add the background (the property background) in the root FrameLayoutelement item_main.xml or install it in the constructor RecyclerListAdapter.ItemViewHolder . It will look something like this:


Element selection


It looks cool, but you might want to control even more. One way to do this is to allow the ViewHolderitem state to be processed. For this, it ItemTouchHelper.Callbackprovides two more methods:


  • onSelectedChanged(ViewHolder, int)called whenever the state of an item changes to drag ( ACTION_STATE_DRAG ) or swipe ( ACTION_STATE_SWIPE ). This is the ideal place to change the state of the viewcomponent to active .
  • clearView(RecyclerView, ViewHolder)called when the drag viewand drop component is completed, as well as when the swipe is completed ( ACTION_STATE_IDLE ). It usually restores the original state of your viewcomponent.

And now let's just put it all together.


First create an interface that will be implemented ViewHolders:


/**
 * Notifies a View Holder of relevant callbacks from 
 * {@link ItemTouchHelper.Callback}.
 */publicinterfaceItemTouchHelperViewHolder{
    /**
     * Called when the {@link ItemTouchHelper} first registers an 
     * item as being moved or swiped.
     * Implementations should update the item view to indicate 
     * it's active state.
     */voidonItemSelected();
    /**
     * Called when the {@link ItemTouchHelper} has completed the 
     * move or swipe, and the active item state should be cleared.
     */voidonItemClear();
}

Then SimpleItemTouchHelperCallbackimplement the appropriate methods:


@OverridepublicvoidonSelectedChanged(RecyclerView.ViewHolder viewHolder, 
        int actionState){
   // We only want the active itemif (actionState != ItemTouchHelper.ACTION_STATE_IDLE) {
        if (viewHolder instanceof ItemTouchHelperViewHolder) {
            ItemTouchHelperViewHolder itemViewHolder = 
                    (ItemTouchHelperViewHolder) viewHolder;
            itemViewHolder.onItemSelected();
        }
    }
    super.onSelectedChanged(viewHolder, actionState);
}
@OverridepublicvoidclearView(RecyclerView recyclerView, 
        RecyclerView.ViewHolder viewHolder){
    super.clearView(recyclerView, viewHolder);
    if (viewHolder instanceof ItemTouchHelperViewHolder) {
        ItemTouchHelperViewHolder itemViewHolder = 
                (ItemTouchHelperViewHolder) viewHolder;
        itemViewHolder.onItemClear();
    }
}

Now it remains only to RecyclerListAdapter.ItemViewHolderimplement ItemTouchHelperViewHolder:


publicclassItemViewHolderextendsRecyclerView.ViewHolderimplementsItemTouchHelperViewHolder{
    // ...@OverridepublicvoidonItemSelected(){
        itemView.setBackgroundColor(Color.LTGRAY);
    }
    @OverridepublicvoidonItemClear(){
        itemView.setBackgroundColor(0);
    }
}

In this example, we simply add a gray background while the item is active, and then delete it. If your ItemTouchHelperadapter is closely related, you can easily drop this setting and switch the state of the viewcomponent directly to ItemTouchHelper.Callback.


Nets


If you now try to use it GridLayoutManager, you will see that it is not working properly. The cause and solution of the problem are simple: we must inform ours ItemTouchHelperthat we want to support dragging elements left and right. Earlier SimpleItemTouchHelperCallbackwe already indicated:


@OverridepublicintgetMovementFlags(RecyclerView recyclerView, 
        RecyclerView.ViewHolder viewHolder){
    int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
    int swipeFlags = ItemTouchHelper.START | ItemTouchHelper.END;
    return makeMovementFlags(dragFlags, swipeFlags);
}

The only change needed to support grids is to add the appropriate flags:


int dragFlags = ItemTouchHelper.UP   | ItemTouchHelper.DOWN | 
                ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;

However, swipe-to-dismiss is not a very natural behavior for elements in the form of a grid, so it is swipeFlagsmost reasonable to zero:


@OverridepublicintgetMovementFlags(RecyclerView recyclerView, 
        RecyclerView.ViewHolder viewHolder){
    int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN | 
                    ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
    int swipeFlags = 0;
    return makeMovementFlags(dragFlags, swipeFlags);
}

To see the working example GridLayoutManager, see RecyclerGridFragment . Here is what it looks like at startup:


Elements in the form of a grid


Custom swipe animations


ItemTouchHelper.Callbackprovides a really convenient way to fully control the animation while dragging or swiping. Since ItemTouchHelperthis is RecyclerView.ItemDecoration , we can intervene in the viewcomponent drawing process in a similar way. In the next part, we will examine this question in more detail, but for now let's look at a simple example of overriding the default swipe animation to show linear extinction.


@OverridepublicvoidonChildDraw(Canvas c, RecyclerView recyclerView, 
        ViewHolder viewHolder, float dX, float dY, 
        int actionState, boolean isCurrentlyActive){
    if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
        float width = (float) viewHolder.itemView.getWidth();
        float alpha = 1.0f - Math.abs(dX) / width;
        viewHolder.itemView.setAlpha(alpha);
        viewHolder.itemView.setTranslationX(dX);    
    } else {
        super.onChildDraw(c, recyclerView, viewHolder, dX, dY, 
                actionState, isCurrentlyActive);
    }
}

Parameters dXand dYis the current shift relative to the selected viewcomponent, where:


  • -1.0f - this is a full swipe from right to left (from ItemTouchHelper.ENDto ItemTouchHelper.START)
  • 1.0f - this is a full swipe from left to right (from ItemTouchHelper.STARTto ItemTouchHelper.END)

It is important to call superfor anyone actionStatethat you do not handle in order for the default animation to start.


In the next part, we will look at an example in which we will control the drawing of the element at the moment of dragging.


Conclusion


In fact, setting up an ItemTouchHelper is quite fun. In order not to increase the volume of this article, I have divided it into several.


Source


View all the code for this series of articles in the Android-ItemTouchHelper-Demo GitHub repository . This article covers commits from ef8f149 to d164fba .


← Drag and Swipe in RecyclerView. Part 1: ItemTouchHelper


Also popular now: