preface

RecyclerView is a used to replace the previous ListView and GridView control, when used, although the ListView looks troublesome than before, but in fact as a highly decoupled control, a little bit complex for great flexibility, rich operability, why not? But today I’m going to focus on a helper class called ItemTouchHelper for dragging and sliding lists to delete.

1. What is ItemTouchHelper

ItemTouchHelper is a RecyclerView and Callback class that allows you to swipe and drop objects. Rewrite the onMove and onSwiped methods as needed. Here’s how to use ItemTouchHelper.

2.ItemTouchHelper basic usage method

2.1 Create a drag and drop callback interface

In terms of decoupling, we need an interface to operate on the data between the Adapter and ItemTouchHelper, because after the ItemTouchHelper finishes animating the touches, it needs to perform operations on the Adapter data, such as swipe deletions, Finally, you need to call the Adapter notifyItemRemove() method to remove this data. So we can abstract the part of the data operation into an interface method called by ItemTouchHelper.callback. Create an ItemTouchMoveListener interface:


public interface ItemTouchMoveListener {
    /** * Callback can be implemented in methods when dragging items and implementing refresh effects *@paramFromPosition drags * from what position@paramToPosition to what position *@returnWhether move */ is executed
    boolean onItemMove(int fromPosition,int toPosition);


    /** * Callback * when the entry is removed@paramPosition Position to be removed *@return* /
    boolean onItemRemove(int position);
}
Copy the code

Let our Adapter implement this interface:


public class QQAdapter extends RecyclerView.Adapter<QQAdapter.MyViewHolder> implements ItemTouchMoveListener {


    private List<QQMessage> list;

    public QQAdapter(List<QQMessage> list) {
        this.list = list;
    }

    @Override
    public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.listitem, parent, false);

        return new MyViewHolder(view);
    }

    @Override
    public void onBindViewHolder(final MyViewHolder holder, int position) {

        QQMessage qqMessage = list.get(position);
        holder.ivLogo.setImageResource(qqMessage.getLogo());
        holder.tvName.setText(qqMessage.getName());
        holder.tvLastMsg.setText(qqMessage.getLastMsg());
        holder.tvTime.setText(qqMessage.getTime());

    }

    @Override
    public int getItemCount(a) {
        return list.size();
    }

    @Override
    public boolean onItemMove(int fromPosition, int toPosition) {
        //1. Data exchange 2. Refresh
        Collections.swap(list, fromPosition, toPosition);
        notifyItemMoved(fromPosition, toPosition);
        return true;
    }

    @Override
    public boolean onItemRemove(int position) {
        list.remove(position);
        notifyItemRemoved(position);
        return true;
    }

    static class MyViewHolder extends RecyclerView.ViewHolder {
        @BindView(R.id.iv_logo)
        CircleImageView ivLogo;
        @BindView(R.id.tv_name)
        TextView tvName;
        @BindView(R.id.tv_time)
        TextView tvTime;
        @BindView(R.id.tv_lastMsg)
        TextView tvLastMsg;
        @BindView(R.id.iv_detele)
        ImageView ivDetele;
        @BindView(R.id.tv_detele)
        TextView tvDetele;

        public MyViewHolder(View itemView) {
            super(itemView);
            ButterKnife.bind(this, itemView); }}}Copy the code

Call the interface’s methods directly inside ItemTouchHelper.callback.

2.2 Create a new class that inherits from ItemTouchHelper.callback

public class MyItemTouchHelperCallback extends ItemTouchHelper.Callback {

    private ItemTouchMoveListener moveListener;

    public MyItemTouchHelperCallback(ItemTouchMoveListener moveListener) {
        this.moveListener = moveListener;
    }

    /** * Callback Callback is called first when listening to the current action@param recyclerView
     * @param viewHolder
     * @return* /
    @Override
    public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
        // Direction: up, down, left, right
        / / constant
        // ItemTouchHelper.UP 0x0001
        // ItemTouchHelper.DOWN 0x0010
        // ItemTouchHelper.LEFT
        // ItemTouchHelper.RIGHT

        // Which drag direction I want to listen for
        int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
        // What direction do I want to listen to swipe
        int swipeFlags = ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;


        int flags = makeMovementFlags(dragFlags, swipeFlags);
        return flags;
    }

    /** * Whether to enable long drag effect **@return* /
    @Override
    public boolean isLongPressDragEnabled(a) {
        return true;
    }

    // Callback method when moving up or down
    @Override
    public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder srcHolder, RecyclerView.ViewHolder targetHolder) {
        // Call adapter.notifyItemMoved(from,to) continuously during drag;
        if(srcHolder.getItemViewType() ! = targetHolder.getItemViewType()) {return false;
        }
        NotifyItemMoved (from,to); // Call adapter.notifyItemMoved(from,to);
        boolean result = moveListener.onItemMove(srcHolder.getAdapterPosition(), targetHolder.getAdapterPosition());
        return result;
    }

    // The method of retracting the side slip
    @Override
    public void onSwiped(RecyclerView.ViewHolder holder, int direction) {
        1. Delete data 2. Call Adapter. notifyItemRemove(position);
        moveListener.onItemRemove(holder.getAdapterPosition());

    }

    // Set the slide item background
    @Override
    public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
        // Check the selected status
        if(actionState ! = ItemTouchHelper.ACTION_STATE_IDLE) { viewHolder.itemView.setBackgroundColor(viewHolder.itemView.getContext().getResources().getColor(R.color.colorC)); }super.onSelectedChanged(viewHolder, actionState);

    }

    // Clears the slide item background
    @Override
    public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
        / / recovery
        viewHolder.itemView.setBackgroundColor(Color.WHITE);

        // Prevent reuse problems that cause items not to be displayed
        viewHolder.itemView.setAlpha(1);/ / 1-0
        // Set the slider size
// viewHolder.itemView.setScaleX(1);
// viewHolder.itemView.setScaleY(1);
        super.clearView(recyclerView, viewHolder);
    }

    // Sets the opacity of the sliding item's background
    @Override
    public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
        //dX: horizontal increment (negative: left; 0- view.getwidth () 0- view.getwidth ()
        float alpha = 1 - Math.abs(dX) / viewHolder.itemView.getWidth();
        if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {

            // Transparency animation
            viewHolder.itemView.setAlpha(alpha);/ / 1-0
            // Set the slider size
// viewHolder.itemView.setScaleX(alpha);
// viewHolder.itemView.setScaleY(alpha);
        }
// // to prevent reuse problems resulting in entries do not display method two
// if(alpha==0){
// viewHolder.itemView.setAlpha(1); / / 1-0
// // Set the slide out size
//// viewHolder.itemView.setScaleX(1);
//// viewHolder.itemView.setScaleY(1);
/ /}
        // This super method handles setTranslationX automatically
        super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive); }}Copy the code
2.3 Add ItemTouchHelper for RecycleView

   // Enter the help class
        ItemTouchHelper.Callback callback = new MyItemTouchHelperCallback(adapter);
        itemTouchHelper = new ItemTouchHelper(callback);
        itemTouchHelper.attachToRecyclerView(recyclerView);
Copy the code

After the above steps, we have implemented the drag-and-drop and sideslip functions on the Item. Let’s look at the effect:

3. Customize sideslip animation

The onChildDraw method provided by ItemTouchHelper.Callback makes it easy to create the desired animation.

This effect is quite common. When a user slides an Item left, the prompt “Swipe left to delete” is displayed at first. When the user slides to a certain distance, the icon of deletion is displayed. First, to slide left to display a deleted box, you can place one of these “boxes” in the LinearLayout and arrange it horizontally side by side with the Item. Here is the layout file:


<? xml version="1.0" encoding="utf-8"? > <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="#fff"
    android:gravity="center_vertical"
    android:orientation="horizontal">

    <com.haocai.itemtouchhelper.view.CircleImageView
        android:id="@+id/iv_logo"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:layout_marginBottom="8dp"
        android:layout_marginLeft="5dp"
        android:layout_marginTop="8dp"
        android:src="@drawable/logo1" />

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_marginLeft="10dip"
        android:orientation="vertical"
        android:paddingRight="5dp">

        <RelativeLayout
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:gravity="center_vertical">

            <TextView
                android:id="@+id/tv_name"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentLeft="true"
                android:text="Wang"
                android:textColor="# 000"
                android:textSize="16sp" />

            <TextView
                android:id="@+id/tv_time"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentRight="true"
                android:layout_centerVertical="true"
                android:text="O"
                android:textColor="#a6a6a6"
                android:textSize="12sp" />
        </RelativeLayout>

        <RelativeLayout
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="5dip"
            android:gravity="center_vertical"
            android:orientation="horizontal">

            <TextView
                android:id="@+id/tv_lastMsg"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentLeft="true"
                android:layout_centerVertical="true"
                android:text="Eat together"
                android:textColor="# 808080"
                android:textSize="12sp" />

            <ImageView
                android:id="@+id/iv_pop"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentRight="true" />
        </RelativeLayout>
    </LinearLayout>

    <FrameLayout
        android:layout_width="100dp"
        android:layout_height="match_parent"
        android:background="#f33213">

        <ImageView
            android:id="@+id/iv_detele"
            android:layout_width="40dp"
            android:layout_height="40dp"
            android:layout_gravity="center"
            android:src="@drawable/delete"
            android:visibility="invisible" />

        <TextView
            android:id="@+id/tv_detele"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:text="Swipe left to delete"
            android:textColor="#ffffff"
            android:textSize="18sp" />
    </FrameLayout>
</LinearLayout>
Copy the code

After the layout file is modified, we try to slide it and the delete block does not appear. This is because the default slide is setTranslationX(int), which is the entire View, so no matter how we slide it, the delete block does not appear. So, we’re going to change one of the slides, like scrollTo(int,int), which is a slide on the contents of the View, so as we go left, the item goes left, and the box on the right naturally appears.

Next, we consider how the “remove eye” icon can grow from small to large. This implementation is also relatively simple. Just change the layoutparams.width of the ImageView according to the sliding distance, but be careful to limit the size, otherwise it will cause image distortion. When the sliding distance is equal to half of RecyclerView width, release the hand at this time will make Item delete, then we can reach the value of the sliding distance when the “eyes” become the largest, at this time can achieve a good interactive effect, prompting the user to delete the Item without continuing to slide.

Finally, we need to consider: after deleting the Item or sliding back to the original position without deleting it, we need to reset the changes made; otherwise, the change of ViewHolder in other positions will be the same as that of the current ViewHolder due to RecyclerView reuse, that is, display errors will be caused. We can reset the changes within the clearView() method to solve the display problems caused by reuse.

Finally, we see SimpleItemTouchHelperCallback code:


public class MyItemTouchHelperCallback3 extends ItemTouchHelper.Callback {

    // Limit the size of the ImageView
    private double ICON_MAX_SIZE = 40;
    // The initial width of the ImageView
    private int fixedWidth = 120;

    private ItemTouchMoveListener moveListener;

    public MyItemTouchHelperCallback3(ItemTouchMoveListener moveListener) {
        this.moveListener = moveListener;
    }

//    /**
// * Sets the slider type flag
/ / *
// * @param recyclerView
// * @param viewHolder
// * @return
// * Returns an integer identifier to determine which movement of Item is allowed
/ / * /
// @Override
// public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
// //START RIGHT LEFT END LEFT RIGHT LEFT LEFT RIGHT LEFT RIGHT UP UP
// // If a value is passed to 0, the operation is not triggered
// return makeMovementFlags(ItemTouchHelper.UP|ItemTouchHelper.DOWN,ItemTouchHelper.END );
/ /}

    /** * Callback Callback is called first when listening to the current action@param recyclerView
     * @param viewHolder
     * @return* /
    @Override
    public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
        // Direction: up, down, left, right
        / / constant
        // ItemTouchHelper.UP 0x0001
        // ItemTouchHelper.DOWN 0x0010
        // ItemTouchHelper.LEFT
        // ItemTouchHelper.RIGHT

        // Which drag direction I want to listen for
        int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
        // What direction do I want to listen to swipe
        int swipeFlags = ItemTouchHelper.LEFT ;


        int flags = makeMovementFlags(dragFlags, swipeFlags);
        return flags;
    }


    /** * Whether to enable long drag effect **@return* /
    @Override
    public boolean isLongPressDragEnabled(a) {
        return true;
    }
    /** * whether the Item supports sliding **@return* true Supports sliding * false does not support sliding */
    @Override
    public boolean isItemViewSwipeEnabled(a) {
        return true;
    }
    // Callback method when moving up or down
    @Override
    public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder srcHolder, RecyclerView.ViewHolder targetHolder) {
        // Call adapter.notifyItemMoved(from,to) continuously during drag;
        if(srcHolder.getItemViewType() ! = targetHolder.getItemViewType()) {return false;
        }
        NotifyItemMoved (from,to); // Call adapter.notifyItemMoved(from,to);
        boolean result = moveListener.onItemMove(srcHolder.getAdapterPosition(), targetHolder.getAdapterPosition());
        return result;
    }

    // The method of retracting the side slip
    @Override
    public void onSwiped(RecyclerView.ViewHolder holder, int direction) {
        1. Delete data 2. Call Adapter. notifyItemRemove(position);
        moveListener.onItemRemove(holder.getAdapterPosition());

    }

    // Set the slide item background
    @Override
    public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
        // Check the selected status
        if(actionState ! = ItemTouchHelper.ACTION_STATE_IDLE) { viewHolder.itemView.setBackgroundColor(viewHolder.itemView.getContext().getResources().getColor(R.color.colorC)); }super.onSelectedChanged(viewHolder, actionState);

    }

    // Clears the slide item background
    @Override
    public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
        / / recovery
        viewHolder.itemView.setBackgroundColor(Color.WHITE);

        // Prevent reuse problems that cause items not to be displayed
        viewHolder.itemView.setAlpha(1);/ / 1-0
        // Set the slider size
// viewHolder.itemView.setScaleX(1);
// viewHolder.itemView.setScaleY(1);

        QQAdapter.MyViewHolder myViewHolder = (QQAdapter.MyViewHolder)viewHolder;
        // Reset changes to prevent display problems due to reuse
        viewHolder.itemView.setScrollX(0);
        myViewHolder.tvDetele.setText("Swipe left to delete");
        FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) myViewHolder.ivDetele.getLayoutParams();
        params.width = 150;
        params.height = 150;
        myViewHolder.ivDetele.setLayoutParams(params);
        myViewHolder.ivDetele.setVisibility(View.INVISIBLE);

        super.clearView(recyclerView, viewHolder);
    }

    // Sets the opacity of the sliding item's background
    @Override
    public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
        QQAdapter.MyViewHolder myViewHolder = (QQAdapter.MyViewHolder)viewHolder;
        // Only for sideslip
        if (actionState ==ItemTouchHelper.ACTION_STATE_SWIPE){
            Log.d("http"."4444");
            // If dX is less than or equal to the width of the removed block, then we slide the block out
            if (Math.abs(dX) <= getSlideLimitation(viewHolder)){
                viewHolder.itemView.scrollTo(-(int) dX,0);
            }
            // If the dX has not reached the distance to remove it, slowly increase the size of the "eye", the maximum increase is ICON_MAX_SIZE
            else if (Math.abs(dX) <= recyclerView.getWidth() / 2) {double distance = (recyclerView.getWidth() / 2 -getSlideLimitation(viewHolder));
                double factor = ICON_MAX_SIZE / distance;
                double diff =  (Math.abs(dX) - getSlideLimitation(viewHolder)) * factor;
                if (diff >= ICON_MAX_SIZE)
                    diff = ICON_MAX_SIZE;
                myViewHolder.tvDetele.setText("");   // Remove the text
                myViewHolder.ivDetele.setVisibility(View.VISIBLE);  // Display the eyes
                FrameLayout.LayoutParams params = (FrameLayout.LayoutParams)    myViewHolder.ivDetele.getLayoutParams();
                params.width = (int) (fixedWidth + diff);
                params.height = (int) (fixedWidth + diff); myViewHolder.ivDetele.setLayoutParams(params); }}else {
            // No changes are made in the drag-and-drop state
            super.onChildDraw(c,recyclerView,viewHolder,dX,dY,actionState,isCurrentlyActive); }}/** * gets the width of the deleted block */
    public int getSlideLimitation(RecyclerView.ViewHolder viewHolder){
        ViewGroup viewGroup = (ViewGroup) viewHolder.itemView;
        return viewGroup.getChildAt(2).getLayoutParams().width; }}Copy the code