看这图片我们想到的是使用 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.NestedScrollViewandroid:id="@+id/scrollView"android:layout_width="match_parent"android:layout_height="match_parent"android:fillViewport="true"><RelativeLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"android:clipChildren="false"android:clipToPadding="false"><com.snxun.zzypt.module.feedback.layout.EditTextWithScrollViewandroid: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" /><LinearLayoutandroid: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"><Viewandroid:layout_width="match_parent"android:layout_height="1dp"android:background="@color/color_17000000" /><TextViewandroid: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" /><Viewandroid:layout_width="match_parent"android:layout_height="1dp"android:background="@color/color_17000000" /></LinearLayout><androidx.recyclerview.widget.RecyclerViewandroid: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><LinearLayoutandroid: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"><TextViewandroid: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() {}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);mCanVerticalScroll = canVerticalScroll();}@Overridepublic boolean dispatchTouchEvent(MotionEvent event) {if (event.getAction() == MotionEvent.ACTION_DOWN)//如果是新的按下事件,则对mBottomFlag重新初始化mBottomFlag = false;//如果已经不要这次事件,则传出取消的信号,这里的作用不大if (mBottomFlag)event.setAction(MotionEvent.ACTION_CANCEL);return super.dispatchTouchEvent(event);}@Overridepublic boolean onTouchEvent(MotionEvent event) {boolean result = super.onTouchEvent(event);if (mCanVerticalScroll) {//如果是需要拦截,则再拦截,这个方法会在onScrollChanged方法之后再调用一次if (!mBottomFlag)getParent().requestDisallowInterceptTouchEvent(true);} else {getParent().requestDisallowInterceptTouchEvent(false);}return result;}@Overrideprotected 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为

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

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

 <androidx.core.widget.NestedScrollViewandroid: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是否处理拖拽事件和滑动事件,以及拖拽和滑动操作的方向*/@Overridepublic 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*/@Overridepublic boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolderviewHolder, @NonNull RecyclerView.ViewHolder target) {if (AnkoArrayKt.isNullOrEmpty(mDataList)) {return false;}// 得到拖动ViewHolder的Positionint fromPosition = viewHolder.getBindingAdapterPosition();// 得到目标ViewHolder的Positionint 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;}/*** 滑动*/@Overridepublic void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {}/*** 当长按选中item时(拖拽开始时)调用* ItemTouchHelper.ACTION_STATE_IDLE  闲置状态* ItemTouchHelper.ACTION_STATE_DRAG  拖拽中状态*/@Overridepublic 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);}//设置拖拽状态为trueif (ItemTouchHelper.ACTION_STATE_DRAG == actionState && dragListener != null) {dragListener.dragState(true);}super.onSelectedChanged(viewHolder, actionState);}/*** 当手指松开时(拖拽完成时)调用* 在clearView()方法里去notifyDataSetChanged,不然 item的position是没有交换的*/@Overridepublic 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*/@Overridepublic void onChildDraw(Canvas c, @NonNull RecyclerViewrecyclerView, @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) {//在删除处放手,则删除itemmDataList.remove(viewHolder.getBindingAdapterPosition());dragListener.deleteOk();mAdapter.notifyDataSetChanged();initData();return;}} else {//没有到删除处dragListener.deleteState(false);}super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);}/*** 设置是否支持长按拖拽* 此处必须返回false* 需要在recyclerView长按事件里限制,否则最后+号长按后扔可拖拽*/@Overridepublic boolean isLongPressDragEnabled() {return false;}/*** 设置是否支持支持滑动** @return true  支持滑动操作* false 不支持滑动操作*/@Overridepublic boolean isItemViewSwipeEnabled() {return false;}/*** 设置手指离开后ViewHolder的动画时间,在用户手指离开后调用** @param recyclerView* @param animationType* @param animateDx* @param animateDy* @return*/@Overridepublic 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*/@Overridepublic long getAnimationDuration(@NonNull RecyclerView recyclerView, int animationType,float animateDx, float animateDy) {//手指放开up = true;return super.getAnimationDuration(recyclerView, animationType, animateDx, animateDy);}/*** 当手指松开时(拖拽完成时)调用,重置状态*/@Overridepublic 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*/@Overridepublic void onChildDraw(Canvas c, @NonNull RecyclerViewrecyclerView, @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) {//在删除处放手,则删除itemmDataList.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() {@Overridepublic 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);}}@Overridepublic void dragState(boolean start) {//根据是否是开始滑动状态,来设置是否显示删除区域if (start) {mBinding.deleteAreaView.setVisibility(View.VISIBLE);} else {mBinding.deleteAreaView.setVisibility(View.GONE);}}@Overridepublic void deleteOk() {//删除后重新计算图片选择数量}@Overridepublic void clearView() {//item删除后需要重新计算底部区域的显示位置,否则会造成底部区域显示混乱fixBottom();}});

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

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

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

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

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

 @Overridepublic 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());}@Overridepublic boolean onInterceptTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) {mGestureDetectorCompat.onTouchEvent(e);return false;}@Overridepublic void onTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) {mGestureDetectorCompat.onTouchEvent(e);}@Overridepublic 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 {@Overridepublic 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;}@Overridepublic 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) {@Overridepublic 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: 仿微信进行拍照、选择图片后进行图片拖拽删除

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

  1. 安卓开发仿微信图片拖拽_仿微信朋友圈发表图片拖拽和删除功能

    原标题:仿微信朋友圈发表图片拖拽和删除功能 中国联通在香港公布了上市公司2017年中期业绩.2017年上半年,公司主要业绩指标持续向好,收入稳步回升,服务收入达到人民币1,241.1亿元,同比增长3. ...

  2. 安卓开发仿微信图片拖拽_Android 仿微信朋友圈发表图片拖拽和删除功能

    朋友圈实现原理 我们使用 Android Device Monitor 来分析朋友圈发布图片的界面实现原理.如果需要分析其他应用的界面实现也是采用这种方法哦. 打开 Android Device Mo ...

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

    朋友圈实现原理 我们使用 Android Device Monitor 来分析朋友圈发布图片的界面实现原理.如果需要分析其他应用的界面实现也是采用这种方法哦. 打开 Android Device Mo ...

  4. iOS高仿微信发朋友圈,图片拖拽,删除组件

    源码地址:https://github.com/Jethuang/HDragImageView

  5. Android 仿微信朋友圈添加图片

    github地址(欢迎下载Demo) https://github.com/zhouxu88/WXCircleAddPic 老习惯,先上图,着急用的朋友,直接带走Demo,先拿来用吧,毕竟老板催的紧, ...

  6. android从九宫格全屏预览,仿微信朋友圈展示图片的九宫格图片展示控件,支持点击图片全屏预览大图...

    AssNineGridView 仿微信朋友圈展示图片的九宫格图片展示控件,支持点击图片全屏预览大图(可自定义). 写在前面 这是一个九宫格控件,本来是很久之前就写好了,现在才开源出来,也是看了很多优秀 ...

  7. Android 实现仿微信朋友圈九宫格图片+NineGridView+ImageWatcher(图片查看:1.预览,2.拖动,3.放大,4.左右滑动,5.长按保存到手机)的功能

    一.测试 实现: 二.添加依赖包: implementation 'androidx.appcompat:appcompat:1.1.0'implementation 'androidx.recycl ...

  8. 仿微信朋友圈选择图片

    仿微信朋友圈选择图片 该版本实现了如下功能: 1.从相册选择图片,对图片进行了缓存处理,选择图片的时候,图片不会出现OOM 2.加入了拍照功能 3.加入了图库功能,可以让你的图片滚动起来了,如果你想使 ...

  9. Android仿微信朋友圈发图片和文字

    Android仿微信朋友圈发图片和文字的一个开源项目,其在github上的项目主页是:https://github.com/zhangphil/FangWeiXinPengYouQuanFaTuPia ...

  10. 一个仿微信朋友圈的图片查看器,使用超级简单!

    PhotoViewer 项目地址:wanglu1209/PhotoViewer  简介:一个仿微信朋友圈的图片查看器,使用超级简单! 更多:作者   提 Bug 标签:        该图片查看器是模 ...

最新文章

  1. 基于Kafka实现分布式事件驱动
  2. LeetCode Multiply Strings(大整数相乘)
  3. MyEclipse 15 集成SVN
  4. 如何补救数据中心电缆
  5. pthread_create函数编译时报错:undefined reference to 'pthread_create'
  6. TWebBrowser禁止弹出Alert对话框
  7. 埋点是什么意思_掌握数据生命周期:初识数据埋点
  8. Android 调用系统相机拍照和录制视频,保存照片和视频
  9. 读教材并提问-回答自己的提问
  10. solidity之以太币支付
  11. Physical Standby Databases Role Transfer
  12. 在线教育系统源码 知识付费系统源码 网络直播源码
  13. 九款实用的在线画图工具(那些可以替代Visio的应用)
  14. QT 字符乱码的原因
  15. PS如何制作GIF动画
  16. css中文字操超出固定个数显示省略... 超出隐藏
  17. MacOS系统安装fish,替代bash
  18. 余世维 有效沟通3
  19. 复杂业务系统的架构设计思路
  20. qml 分隔工具栏ToolSeparator 工具提示ToolTip 旋转轮Tumbler

热门文章

  1. 六、软考·系统架构师——UML建模工具
  2. linux 监听 ipv6,zabbix 监控 ipv6
  3. 数据结构之二叉树深度计算
  4. 在页面中使用Flowplayer播放器
  5. mingw socket编程
  6. 数学公式编辑器的探索与实现
  7. Tableau宣布退出中国市场,背后的原因细思恐极...
  8. 初识Java 之软件下载与安装配置
  9. 江苏省×××局数据复制软件招标
  10. vue开发app端使用H5+下载文件流