看这图片我们想到的是使用 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: 仿微信进行拍照、选择图片后进行图片拖拽删除