仿微信朋友圈发表图片拖拽和删除功能

          

看这图片我们想到的是使用 RecyclerView 自带的ItemTouchHelper对拖拽进行处理,那先来一些知识的储备

一:RecyclerView 的ItemTouchHelper

官方解释:

This is a utility class to add swipe to dismiss and drag & drop support to RecyclerView
一个让 RecyclerView 支持滑动删除和拖拽的实用工具类

主要方法

//关联对应的 RecyclerView
public void attachToRecyclerView(RecyclerView recyclerView)

//viewHolder开始拖动
public void startDrag(RecyclerView.ViewHolder viewHolder)

//viewHolder开始滑动
public void startSwipe(RecyclerView.ViewHolder viewHolder)

使用

  • 自定义一个类继承并实现ItemTouchHelper.Callback接口,以下方法必须实现:
//设置item是否处理拖拽事件和滑动事件,以及拖拽和滑动操作的方向
@Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
}

//当用户从item原来的位置拖动可以拖动的item到新位置的过程中调用
@Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
}

//滑动到消失后的调用
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
}
  • 实现化ItemTouchHelper并关联RecyclerView
itemTouchHelper = new ItemTouchHelper(myCallBack);
itemTouchHelper.attachToRecyclerView(recyclerView);

二:ItemTouchHelper.Callback

ItemTouchHelper在拖拽和滑动删除的过程中会回调ItemTouchHelper.Callback的相关方法

主要方法

//设置item是否处理拖拽事件和滑动事件,以及拖拽和滑动操作的方向
public int getMovementFlags (RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder)

/**
*当用户从item原来的位置拖动可以拖动的item到新位置的过程中调用
*@recyclerView 
*@viewHolder 拖动的 item
*@target 目标 item
**/
public boolean onMove (RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target)

/**
* RecyclerView调用onDraw时调用,如果想自定义item对用户互动的响应,可以重写该方法
* @dx item 滑动的距离
**/
public void onChildDraw (Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive)

//设置是否可以长按拖拽
public boolean isLongPressDragEnabled ()

//设置手指离开后ViewHolder的动画时间,在用户手指离开后调用
public long getAnimationDuration(RecyclerView recyclerView, int animationType, float animateDx, float animateDy)

//当长按选中item的时候(拖拽开始的时候)调用
public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState)

//当用户与item的交互结束并且item也完成了动画时调用
public void clearView (RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder)

三:实现过程

1:从布局入手

对应的xml布局如下:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/white"
    android:focusableInTouchMode="true"
    android:orientation="vertical"
    tools:context=".module.feedback.add.FeedBackAddActivity">


    <androidx.core.widget.NestedScrollView
        android:id="@+id/scrollView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:fillViewport="true">


        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:clipChildren="false"
            android:clipToPadding="false">

            <com.snxun.zzypt.module.feedback.layout.EditTextWithScrollView
                android:id="@+id/feedback_content_edit"
                android:layout_width="match_parent"
                android:layout_height="130dp"
                android:autofillHints="@string/feedback_msg"
                android:background="@null"
                android:gravity="start"
                android:hint="@string/feedback_msg"
                android:inputType="textMultiLine"
                android:maxLength="200"
                android:paddingStart="20dp"
                android:paddingTop="14dp"
                android:paddingEnd="20dp"
                android:paddingBottom="14dp"
                android:textColor="@color/color_d9000000"
                android:textColorHint="@color/color_40000000"
                android:textSize="16sp" />

            <LinearLayout
                android:id="@+id/bottom_ll"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginStart="20dp"
                android:layout_marginTop="280dp"
                android:layout_marginEnd="20dp"
                android:orientation="vertical"
                android:paddingBottom="15dp">

                <View
                    android:layout_width="match_parent"
                    android:layout_height="1dp"
                    android:background="@color/color_17000000" />

                <TextView
                    android:id="@+id/feedback_history_tv"
                    android:layout_width="match_parent"
                    android:layout_height="50dp"
                    android:drawablePadding="5dp"
                    android:gravity="center_vertical"
                    android:text="@string/feedback_history"
                    android:textColor="@color/color_d9000000"
                    android:textSize="14sp"
                    app:drawableEndCompat="@drawable/ic_right"
                    app:drawableStartCompat="@drawable/ic_history" />

                <View
                    android:layout_width="match_parent"
                    android:layout_height="1dp"
                    android:background="@color/color_17000000" />
            </LinearLayout>

            <androidx.recyclerview.widget.RecyclerView
                android:id="@+id/pic_rv"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_below="@+id/feedback_content_edit"
                android:layout_marginStart="20dp"
                android:layout_marginEnd="20dp"
                android:nestedScrollingEnabled="false"
                app:layoutManager="androidx.recyclerview.widget.StaggeredGridLayoutManager"
                app:spanCount="3" />

        </RelativeLayout>
    </androidx.core.widget.NestedScrollView>

    <LinearLayout
        android:id="@+id/delete_area_view"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        android:background="@color/color_FFF5222D"
        android:gravity="center"
        android:visibility="invisible">

        <TextView
            android:id="@+id/delete_area_tv"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:drawablePadding="5dp"
            android:gravity="center"
            android:text="@string/delete_pic"
            android:textColor="@color/white"
            app:drawableStartCompat="@drawable/ic_delete_white" />
    </LinearLayout>

</RelativeLayout>

EditTextWithScrollView这个类:

我们设置输入框的高度是固定的,当输入的内容高于设置的高度时可进行滚动查看,用于解决嵌套Scrollview的时候由于多行而产生的滑动冲突问题,

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;

import androidx.appcompat.widget.AppCompatEditText;

/**
 * 用于解决嵌套Scrollview的时候由于多行而产生的滑动冲突问题
 *
 * @author Wuczh
 * @date 2021/11/30
 */
public class EditTextWithScrollView extends AppCompatEditText {

    //滑动距离的最大边界
    private int mOffsetHeight;

    //是否到顶或者到底的标志
    private boolean mBottomFlag = false;
    private boolean mCanVerticalScroll;

    public EditTextWithScrollView(Context context) {
        super(context);
        init();
    }

    public EditTextWithScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public EditTextWithScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        mCanVerticalScroll = canVerticalScroll();
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_DOWN)
            //如果是新的按下事件,则对mBottomFlag重新初始化
            mBottomFlag = false;
        //如果已经不要这次事件,则传出取消的信号,这里的作用不大
        if (mBottomFlag)
            event.setAction(MotionEvent.ACTION_CANCEL);

        return super.dispatchTouchEvent(event);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        boolean result = super.onTouchEvent(event);
        if (mCanVerticalScroll) {
            //如果是需要拦截,则再拦截,这个方法会在onScrollChanged方法之后再调用一次
            if (!mBottomFlag)
                getParent().requestDisallowInterceptTouchEvent(true);
        } else {
            getParent().requestDisallowInterceptTouchEvent(false);
        }
        return result;
    }

    @Override
    protected void onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert) {
        super.onScrollChanged(horiz, vert, oldHoriz, oldVert);
        if (vert == mOffsetHeight || vert == 0) {
            //这里触发父布局或祖父布局的滑动事件
            getParent().requestDisallowInterceptTouchEvent(false);
            mBottomFlag = true;
        }
    }

    /**
     * EditText竖直方向是否可以滚动
     *
     * @return true:可以滚动   false:不可以滚动
     */
    private boolean canVerticalScroll() {
        //滚动的距离
        int scrollY = getScrollY();
        //控件内容的总高度
        int scrollRange = getLayout().getHeight();
        //控件实际显示的高度
        int scrollExtent = getHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom();
        //控件内容总高度与实际显示高度的差值
        mOffsetHeight = scrollRange - scrollExtent;

        if (mOffsetHeight == 0) {

            return false;
        }

        return (scrollY > 0) || (scrollY < mOffsetHeight - 1);
    }
}

 三个属性的使用

android:clipChildren="false"
 android:clipToPadding="false"
 android:fillViewport="true"

Android开发实战(二十一):浅谈android:clipChildren属性 - 云+社区 - 腾讯云

android:clipToPadding的使用_wangjiang-CSDN博客_android cliptopadding

ScrollView中添加一个android:fillViewport="true"_Jsoh的博客-CSDN博客_android fillviewport

设置RecyclerView的高度为android:layout_height="wrap_content",要想item可以在整个布局拖动,我们设置父布局RelativeLayout为

 <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:clipChildren="false"
            android:clipToPadding="false">

这样设置能实现全局拖动的效果吗?我们来看下下面的效果,发现item只可在父布局的范围内拖动,这时候需要我们将NestedScrollView加上属性fillViewport="true",这样RelativeLayout就可填充NestedScrollView,也就是全屏

 <androidx.core.widget.NestedScrollView
        android:id="@+id/scrollView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:fillViewport="true">

下方显示的布局的设置:

以一般的判断我们会将底下布局,直接在代码里设置相对与RecyclerView底部显示,那看下如果这样设置会出现什么情况:

 从图中可以看到,在移动item的时候,下面布局的显示位置会发生错乱,所以我们采用的是具体的显示位置在代码中进行位置计算后设置的。具体的计算我们在后面进行说明。

2:设置ItemTouchHelperCallback继承ItemTouchHelper.Callback

具体的实现在代码中都有很详细的备注

import android.graphics.Canvas;

import androidx.annotation.NonNull;
import androidx.core.widget.NestedScrollView;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.StaggeredGridLayoutManager;

import com.lodz.android.corekt.album.PicInfo;
import com.lodz.android.corekt.anko.AnkoAnimKt;
import com.lodz.android.corekt.anko.AnkoArrayKt;
import com.lodz.android.corekt.anko.AnkoDimensionsKt;
import com.lodz.android.corekt.anko.AnkoVibratorKt;
import com.snxun.zzypt.App;
import com.snxun.zzypt.module.feedback.add.adapter.PhotoPublishAdapter;

import java.util.ArrayList;
import java.util.Collections;

/**
 * 拖拽排序删除
 *
 * @author Wuczh
 * @date 2021/11/18
 */
public class ItemTouchHelperCallback extends ItemTouchHelper.Callback {

    /**
     * 是否需要拖拽震动提醒
     */
    private boolean isNeedDragVibrate = true;
    /**
     * 适配器
     */
    private PhotoPublishAdapter mAdapter;

    /**
     * 手指抬起标记位
     */
    private boolean up;
    /**
     * 可滑动伸缩空间
     */
    private NestedScrollView mScrollView;
    /**
     * 数据列表
     */
    private ArrayList<PicInfo> mDataList;

    private int dragFlags;
    private int swipeFlags;

    /**
     * @param isNeedDragVibrate 是否需要拖拽震动提醒
     * @param imagesList        图片数据
     * @param scrollView        可滑动伸缩空间
     * @param adapter           适配器
     */
    public ItemTouchHelperCallback(boolean isNeedDragVibrate, ArrayList<PicInfo> imagesList, NestedScrollView scrollView, PhotoPublishAdapter adapter) {
        this.isNeedDragVibrate = isNeedDragVibrate;
        mAdapter = adapter;
        mScrollView = scrollView;
        mDataList = imagesList;
    }

    /**
     * 设置item是否处理拖拽事件和滑动事件,以及拖拽和滑动操作的方向
     */
    @Override
    public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
        //判断 recyclerView的布局管理器数据,设置 item 只能处理拖拽事件,并能够向左、右、上、下拖拽
        if (recyclerView.getLayoutManager() instanceof StaggeredGridLayoutManager) {//设置能拖拽的方向
            dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
            swipeFlags = 0;//0则不响应滑动事件
        }
        return makeMovementFlags(dragFlags, swipeFlags);
    }

    /**
     * 拖拽,交换位置(当用户从item原来的位置拖动可以拖动的item到新位置的过程中调用)
     *
     * @param viewHolder 拖动的 item
     * @param target     目标 item
     */
    @Override
    public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder
            viewHolder, @NonNull RecyclerView.ViewHolder target) {
        if (AnkoArrayKt.isNullOrEmpty(mDataList)) {
            return false;
        }
        // 得到拖动ViewHolder的Position
        int fromPosition = viewHolder.getBindingAdapterPosition();
        // 得到目标ViewHolder的Position
        int toPosition = target.getBindingAdapterPosition();

        //因为没有将 +号的图片 加入imageList,所以不用imageList.size-1 此处限制不能移动到recyclerView最后一位
        if (toPosition == mDataList.size() || mDataList.size() == fromPosition) {
            return false;
        }

        if (fromPosition < toPosition) {//顺序小到大
            for (int i = fromPosition; i < toPosition; i++) {
                Collections.swap(mDataList, i, i + 1);
            }
        } else {//顺序大到小
            for (int i = fromPosition; i > toPosition; i--) {
                Collections.swap(mDataList, i, i - 1);
            }
        }
        mAdapter.notifyItemMoved(fromPosition, toPosition);
        return true;
    }

    /**
     * 滑动
     */
    @Override
    public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
    }

    /**
     * 当长按选中item时(拖拽开始时)调用
     * ItemTouchHelper.ACTION_STATE_IDLE  闲置状态
     * ItemTouchHelper.ACTION_STATE_DRAG  拖拽中状态
     */
    @Override
    public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
        if (viewHolder == null) {
            return;
        }
        //设置拖拽震动提醒
        if (actionState != ItemTouchHelper.ACTION_STATE_IDLE && isNeedDragVibrate) {
            AnkoVibratorKt.createVibrator(App.getInstance(), 100);
        }
        //设置拖拽动画
        if (actionState == ItemTouchHelper.ACTION_STATE_DRAG) {//开始拖拽
            AnkoAnimKt.startScaleSelf(viewHolder.itemView, 1.0f, 1.05f, 1.0f, 1.05f, 50, true);
        }
        //设置拖拽状态为true
        if (ItemTouchHelper.ACTION_STATE_DRAG == actionState && dragListener != null) {
            dragListener.dragState(true);
        }
        super.onSelectedChanged(viewHolder, actionState);
    }

    /**
     * 当手指松开时(拖拽完成时)调用
     * 在clearView()方法里去notifyDataSetChanged,不然 item的position是没有交换的
     */
    @Override
    public void clearView(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
        AnkoAnimKt.startScaleSelf(viewHolder.itemView, 1.05f, 1.0f, 1.05f, 1.0f, 50, true);
        super.clearView(recyclerView, viewHolder);
        initData();
        if (dragListener != null) {
            dragListener.clearView();
        }
    }

    /**
     * 自定义拖动与滑动交互
     *
     * @param c
     * @param recyclerView
     * @param viewHolder
     * @param dX                X轴移动的距离
     * @param dY                Y轴移动的距离
     * @param actionState       当前Item的状态
     * @param isCurrentlyActive 如果当前被用户操作为true,反之为false
     */
    @Override
    public void onChildDraw(Canvas c, @NonNull RecyclerView
            recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, float dX, float dY,
                            int actionState, boolean isCurrentlyActive) {
        if (null == dragListener) {
            return;
        }
        int editTextHeight = AnkoDimensionsKt.dp2px(App.getInstance(), 130);//输入框的高度
        int deleteViewHeight = AnkoDimensionsKt.dp2px(App.getInstance(), 50);//删除区间的高度
        /**
         *滑动的距离到达删除区域时的判断
         */
        if (dY >= (mScrollView.getHeight() - editTextHeight)
                - viewHolder.itemView.getBottom()//item底部距离recyclerView顶部高度
                - deleteViewHeight
                + mScrollView.getScrollY()) {//拖到删除处
            dragListener.deleteState(true);
            if (up) {//在删除处放手,则删除item
                mDataList.remove(viewHolder.getBindingAdapterPosition());
                dragListener.deleteOk();
                mAdapter.notifyDataSetChanged();
                initData();
                return;
            }
        } else {//没有到删除处
            dragListener.deleteState(false);
        }
        super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
    }

    /**
     * 设置是否支持长按拖拽
     * 此处必须返回false
     * 需要在recyclerView长按事件里限制,否则最后+号长按后扔可拖拽
     */
    @Override
    public boolean isLongPressDragEnabled() {
        return false;
    }

    /**
     * 设置是否支持支持滑动
     *
     * @return true  支持滑动操作
     * false 不支持滑动操作
     */
    @Override
    public boolean isItemViewSwipeEnabled() {
        return false;
    }

    /**
     * 设置手指离开后ViewHolder的动画时间,在用户手指离开后调用
     *
     * @param recyclerView
     * @param animationType
     * @param animateDx
     * @param animateDy
     * @return
     */
    @Override
    public long getAnimationDuration(@NonNull RecyclerView recyclerView, int animationType,
                                     float animateDx, float animateDy) {
        //手指放开
        up = true;
        return super.getAnimationDuration(recyclerView, animationType, animateDx, animateDy);
    }

    public interface DragListener {
        /**
         * 用户是否将 item拖动到删除处,根据状态改变颜色
         *
         * @param delete
         */
        void deleteState(boolean delete);

        /**
         * 是否于拖拽状态
         *
         * @param start
         */
        void dragState(boolean start);

        /**
         * 当删除完成后调用
         */
        void deleteOk();

        /**
         * 当用户与item的交互结束并且item也完成了动画时调用
         */
        void clearView();
    }

    private DragListener dragListener;

    public void setDragListener(DragListener dragListener) {
        this.dragListener = dragListener;
    }

    /**
     * 重置状态(拖拽状态设置为false 删除状态为false)
     */
    private void initData() {
        if (dragListener != null) {
            dragListener.deleteState(false);
            dragListener.dragState(false);
        }
        up = false;
    }
}
  • 重点是实现拖拽到底部放手删除功能

item从拖动到放手的主要处理流程图如下;

那么我们应该在哪个地方去判断item是否到达删除区域呢?在前面介绍的方法,有这么个方法:onChildDraw,这个方法就会在item拖拽的过程不断回调并且返回item的偏移量。有了偏移量之后我们就很容易去判断item是否到达删除区域了。


偏移量满足以下条件时,就到达删除区域:
item偏移量>=RecyclerView的高-item底部到RecyclerView顶边的距离-.EditText的高+ScrollView.getScrollY()的滑动距离

从上面的图片看起来,似乎没有体现ScrollView.getScrollY()的高度。可以观察下视频里的滑动效果,视频里是没有加上ScrollView.getScrollY()的高度的判断,发现在整个屏幕没有往上滑动的时候,item进行删除都是会到达删除区域的时候,但当我们把界面手动上划的时候发现item还没到达删除区域就会进入删除状态,这其实是dY的这个距离是包含ScrollView.getScrollY()的高度。

视频效果滑动效果-CSDN直播

  • 怎样判断用户在拖动后放手呢?

用boolean up来标记,当up为true时手指抬起,false为初始状态。在getAnimationDuration()中设置其为true。记得需要在clearView()中恢复初始值false;
还需要在ItemTouchHelper.Callback中暴露个接口DragListener给外部,用来提示通知外部什么时候显示删除区域,以及item进入删除区域时的文字提示。

  /**
     * 设置手指离开后ViewHolder的动画时间,在用户手指离开后调用
     *
     * @param recyclerView
     * @param animationType
     * @param animateDx
     * @param animateDy
     * @return
     */
    @Override
    public long getAnimationDuration(@NonNull RecyclerView recyclerView, int animationType,
                                     float animateDx, float animateDy) {
        //手指放开
        up = true;
        return super.getAnimationDuration(recyclerView, animationType, animateDx, animateDy);
    }



 /**
     * 当手指松开时(拖拽完成时)调用,重置状态
     */
    @Override
    public void clearView(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
        AnkoAnimKt.startScaleSelf(viewHolder.itemView, 1.05f, 1.0f, 1.05f, 1.0f, 50, true);
        super.clearView(recyclerView, viewHolder);
        initData();
        if (dragListener != null) {
            dragListener.clearView();
        }
    }

 /**
     * 重置状态(拖拽状态设置为false 删除状态为false)
     */
    private void initData() {
        if (dragListener != null) {
            dragListener.deleteState(false);
            dragListener.dragState(false);
        }
        up = false;
    }



  /**
     * 自定义拖动与滑动交互
     *
     * @param c
     * @param recyclerView
     * @param viewHolder
     * @param dX                X轴移动的距离
     * @param dY                Y轴移动的距离
     * @param actionState       当前Item的状态
     * @param isCurrentlyActive 如果当前被用户操作为true,反之为false
     */
    @Override
    public void onChildDraw(Canvas c, @NonNull RecyclerView
            recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, float dX, float dY,
                            int actionState, boolean isCurrentlyActive) {
        if (null == dragListener) {
            return;
        }
        int editTextHeight = AnkoDimensionsKt.dp2px(App.getInstance(), 130);//输入框的高度
        int deleteViewHeight = AnkoDimensionsKt.dp2px(App.getInstance(), 50);//删除区间的高度
        /**
         * scrollView.getHeight()-editTextHeight 为recyclerview的高度
         * 此处不用onChildDraw里的参数recyclerView.getHeight来计算,因为当添加图片至超出屏幕高度
         * 即scrollView可以滑动后获取的recyclerview不准确,亲测。
         */
        if (dY >= (mScrollView.getHeight() - editTextHeight)
                - viewHolder.itemView.getBottom()//item底部距离recyclerView顶部高度
                - deleteViewHeight
                + mScrollView.getScrollY()) {//拖到删除处
            dragListener.deleteState(true);
            if (up) {//在删除处放手,则删除item
                mDataList.remove(viewHolder.getBindingAdapterPosition());
                dragListener.deleteOk();
                mAdapter.notifyDataSetChanged();
                initData();
                return;
            }
        } else {//没有到删除处
            dragListener.deleteState(false);
        }
        super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
    }

3. 关联 RecyclerView

  ItemTouchHelperCallback myCallBack = new ItemTouchHelperCallback(false, mDataList, mBinding.scrollView, mAdapter);
  mItemTouchHelper = new ItemTouchHelper(myCallBack);//实现化ItemTouchHelper(拖拽和滑动删除的过程中会回调ItemTouchHelper.Callback的相关方法)
  mItemTouchHelper.attachToRecyclerView(mBinding.picRv);//关联对应的 RecyclerView
  //滑动状态回调处理
        myCallBack.setDragListener(new ItemTouchHelperCallback.DragListener() {
            @Override
            public void deleteState(boolean delete) {
                //根据是否是删除状态,显示对应的删除区域的文字和背景色
                if (delete) {
                    mBinding.deleteAreaView.setAlpha(0.9f);
                    mBinding.deleteAreaTv.setText(getString(R.string.loosen_delete_pic));
                } else {
                    mBinding.deleteAreaView.setAlpha(0.6f);
                    mBinding.deleteAreaTv.setText(R.string.drag_delete_pic);
                }
            }

            @Override
            public void dragState(boolean start) {
                //根据是否是开始滑动状态,来设置是否显示删除区域
                if (start) {
                    mBinding.deleteAreaView.setVisibility(View.VISIBLE);
                } else {
                    mBinding.deleteAreaView.setVisibility(View.GONE);
                }
            }

            @Override
            public void deleteOk() {
                //删除后重新计算图片选择数量

            }

            @Override
            public void clearView() {
                //item删除后需要重新计算底部区域的显示位置,否则会造成底部区域显示混乱
                fixBottom();
            }
        });

4:注意需设置加号不能进行拖拽

由以上分析可知,ItemTouchHelper.Callback的isLongPressDragEnabled()可以设置是否支持长按拖拽,默认是true,即支持长按拖拽。现在我们要自定义指定哪些item可以拖拽,哪些不可以,因此我们需要重写isLongPressDragEnabled()

    /**
     * 设置是否支持长按拖拽
     * 此处必须返回false
     * 需要在recyclerView长按事件里限制,否则最后+号长按后扔可拖拽
     */
    @Override
    public boolean isLongPressDragEnabled() {
        return false;
    }

item的长按事件中进行是否拖动判断,这边的长按有两种方式,一种是在Adapter适配器中对item的长按事件进行监听回调,还有一种方式是自定义RecyclerView 的点击监听事件,可以参考以下内容:RecyclerView的高效触摸监听 - 简书,至于哪种方式看大家自己选择,这边用前者进行讲解。

        主要就是在自己设置的长按回调里进行是否拖拽的操作!

 
        @Override
        public void onItemLongClick(RecyclerView.ViewHolder viewHolder) {
          //我这边是因为当position==数据数量时就是加号图片,这里要看实际的值进行设置
            if (viewHolder.getBindingAdapterPosition() != mDataList.size()) {
                    mItemTouchHelper.startDrag(viewHolder);//viewHolder开始拖动
                }
        }
   

5:在代码中计算底部区域的显示位置

    /**
     * 处理recyclerView下面的布局
     */
    private void fixBottom() {
        int row = mAdapter.getItemCount() / 3;
        row = (0 == mAdapter.getItemCount() % 3) ? row : row + 1;//少于3为1行
        int screenWidth = AnkoScreenKt.getScreenWidth(getContext());
        int itemHeight = (screenWidth - AnkoDimensionsKt.dp2px(getContext(), 40)) / 3;
        int editHeight = mBinding.feedbackContentEdit.getHeight();
        editHeight = editHeight == 0 ? AnkoDimensionsKt.dp2px(getContext(), 130) : mBinding.feedbackContentEdit.getHeight();
        int layoutMargin = AnkoDimensionsKt.dp2px(getContext(), 20);//距离上部应用的间隔
        int marginTop = itemHeight * row + editHeight + layoutMargin;//+ itemSpace * (row - 1)
        RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) mBinding.bottomLl.getLayoutParams();

        //用户判断 在每次fix底部布局高度后用来判断底部按钮的点击位置 注意要减去顶部edittext的高度
        judgeClickMargin = marginTop - editHeight;

        params.setMargins(0, marginTop, 0, 0);
        mBinding.bottomLl.setLayoutParams(params);
    }

记得在页面初始化和对应item发生变化的方法里要调用此方法进行布局设置。

6:底部位置的点击(这点可以不考虑)

由于设置RecyclerView是高度设置是android:layout_height="wrap_content",这样RecyclerView就不会拦截底部布局的点击事件,所以点击操作只需要用id在代码中进行点击事件的判断即可,就不需要下面的这些操作了

由于RecyclerView是高度设置是android:layout_height="match_parent",在整个界面的内容没有超过屏幕时,点击布局其实监听进入的是RecyclerView的OnItemTouchListener监听,所以要进行两层点击判断

  • 继承RecyclerView.OnItemTouchListener对里面的方法进行处理
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;

import androidx.annotation.NonNull;
import androidx.core.view.GestureDetectorCompat;
import androidx.recyclerview.widget.RecyclerView;

/**
 * RecyclerViewde 的点击  进行拖拽的布局会照成recyclerview下方的布局无法点击,需要在RecyclerView点击里重新设置
 * <p>
 * 因为我们自带的适配器已近设置了item的点击和长按点击的功能,这边就把这2个点击屏蔽
 *
 * @author Wuczh
 * @date 2021/12/1
 */
public abstract class OnRecyclerItemClickListener implements RecyclerView.OnItemTouchListener {
    private GestureDetectorCompat mGestureDetectorCompat;
    private RecyclerView mRecyclerView;

    public OnRecyclerItemClickListener(RecyclerView recyclerView) {
        mRecyclerView = recyclerView;
        mGestureDetectorCompat = new GestureDetectorCompat(mRecyclerView.getContext(),
                new ItemTouchHelperGestureListener());
    }

    @Override
    public boolean onInterceptTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) {
        mGestureDetectorCompat.onTouchEvent(e);
        return false;
    }

    @Override
    public void onTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) {
        mGestureDetectorCompat.onTouchEvent(e);
    }

    @Override
    public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {

    }

    //    public abstract void onItemClick(RecyclerView.ViewHolder viewHolder);
    //
    //    public abstract void onLongClick(RecyclerView.ViewHolder viewHolder);

    public abstract void onOtherClick(MotionEvent e);

    private class ItemTouchHelperGestureListener extends GestureDetector.SimpleOnGestureListener {
        @Override
        public boolean onSingleTapUp(MotionEvent e) {
            View childViewUnder = mRecyclerView.findChildViewUnder(e.getX(), e.getY());
            if (childViewUnder != null) {
                RecyclerView.ViewHolder childViewHolder = mRecyclerView.getChildViewHolder(childViewUnder);
                //                onItemClick(childViewHolder);
            } else {
                onOtherClick(e);
            }
            return true;
        }

        @Override
        public void onLongPress(MotionEvent e) {
            View childViewUnder = mRecyclerView.findChildViewUnder(e.getX(), e.getY());
            if (childViewUnder != null) {
                RecyclerView.ViewHolder childViewHolder = mRecyclerView.getChildViewHolder(childViewUnder);
                //                onLongClick(childViewHolder);
            }
        }

    }
}

在回调进行布局位置判断,设置对应点击

        mBinding.picRv.addOnItemTouchListener(new OnRecyclerItemClickListener(mBinding.picRv) {
            @Override
            public void onOtherClick(MotionEvent e) {
                    int bottomItemHeight = AnkoDimensionsKt.dp2px(getContext(), 50);//历史意见反馈的高度
                if (e.getY()>judgeClickMargin) {
                    int between=(int)e.getY()-judgeClickMargin;//判读触摸点与 bottom布局分界处的距离
                    int oneItem=(bottomItemHeight);//一个textview+一个分割线的高度
                    if (between>0 && between<=oneItem) {
                        //点击在第一个textview上 ---所在位置
                        FeedBackHistoryActivity.start(getContext());
                    }
//                    else if (between>oneItem && between<=2*oneItem) {
//
//                        //点击在第二个textview上 ---谁可以看
//                        ToastUtil.normal("谁可以看");
//                    } else if (between>2*oneItem && between<=3*oneItem) {
//
//                        //点击在第三个textview上 ---提醒谁看
//                        ToastUtil.normal("提醒谁看");
//                    } else if (between>3*oneItem && between<=4*oneItem && e.getX()>=leftMargin && e.getX()<=(starWidth+leftMargin)) {
//                        //点击星星 同步到空间
//                        ToastUtil.normal("同步到空间");
//                    }

                }
            }
        });
  • 当界面超过屏幕时,采用布局的id设置点击事件
  //意见反馈
        mBinding.feedbackHistoryTv.setOnClickListener(v -> FeedBackHistoryActivity.start(getContext()));

以上就是拖拽删除的主要操作

附上demo的guthub下载路径:GitHub - WCaiZhu/PictureDrag: 仿微信进行拍照、选择图片后进行图片拖拽删除

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值