仿微信朋友圈发表图片拖拽和删除功能
看这图片我们想到的是使用 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: 仿微信进行拍照、选择图片后进行图片拖拽删除
仿微信朋友圈发表图片拖拽和删除功能相关推荐
- 安卓开发仿微信图片拖拽_仿微信朋友圈发表图片拖拽和删除功能
原标题:仿微信朋友圈发表图片拖拽和删除功能 中国联通在香港公布了上市公司2017年中期业绩.2017年上半年,公司主要业绩指标持续向好,收入稳步回升,服务收入达到人民币1,241.1亿元,同比增长3. ...
- 安卓开发仿微信图片拖拽_Android 仿微信朋友圈发表图片拖拽和删除功能
朋友圈实现原理 我们使用 Android Device Monitor 来分析朋友圈发布图片的界面实现原理.如果需要分析其他应用的界面实现也是采用这种方法哦. 打开 Android Device Mo ...
- Android 仿微信朋友圈发表图片拖拽和删除功能
朋友圈实现原理 我们使用 Android Device Monitor 来分析朋友圈发布图片的界面实现原理.如果需要分析其他应用的界面实现也是采用这种方法哦. 打开 Android Device Mo ...
- iOS高仿微信发朋友圈,图片拖拽,删除组件
源码地址:https://github.com/Jethuang/HDragImageView
- Android 仿微信朋友圈添加图片
github地址(欢迎下载Demo) https://github.com/zhouxu88/WXCircleAddPic 老习惯,先上图,着急用的朋友,直接带走Demo,先拿来用吧,毕竟老板催的紧, ...
- android从九宫格全屏预览,仿微信朋友圈展示图片的九宫格图片展示控件,支持点击图片全屏预览大图...
AssNineGridView 仿微信朋友圈展示图片的九宫格图片展示控件,支持点击图片全屏预览大图(可自定义). 写在前面 这是一个九宫格控件,本来是很久之前就写好了,现在才开源出来,也是看了很多优秀 ...
- Android 实现仿微信朋友圈九宫格图片+NineGridView+ImageWatcher(图片查看:1.预览,2.拖动,3.放大,4.左右滑动,5.长按保存到手机)的功能
一.测试 实现: 二.添加依赖包: implementation 'androidx.appcompat:appcompat:1.1.0'implementation 'androidx.recycl ...
- 仿微信朋友圈选择图片
仿微信朋友圈选择图片 该版本实现了如下功能: 1.从相册选择图片,对图片进行了缓存处理,选择图片的时候,图片不会出现OOM 2.加入了拍照功能 3.加入了图库功能,可以让你的图片滚动起来了,如果你想使 ...
- Android仿微信朋友圈发图片和文字
Android仿微信朋友圈发图片和文字的一个开源项目,其在github上的项目主页是:https://github.com/zhangphil/FangWeiXinPengYouQuanFaTuPia ...
- 一个仿微信朋友圈的图片查看器,使用超级简单!
PhotoViewer 项目地址:wanglu1209/PhotoViewer 简介:一个仿微信朋友圈的图片查看器,使用超级简单! 更多:作者 提 Bug 标签: 该图片查看器是模 ...
最新文章
- 基于Kafka实现分布式事件驱动
- LeetCode Multiply Strings(大整数相乘)
- MyEclipse 15 集成SVN
- 如何补救数据中心电缆
- pthread_create函数编译时报错:undefined reference to 'pthread_create'
- TWebBrowser禁止弹出Alert对话框
- 埋点是什么意思_掌握数据生命周期:初识数据埋点
- Android 调用系统相机拍照和录制视频,保存照片和视频
- 读教材并提问-回答自己的提问
- solidity之以太币支付
- Physical Standby Databases Role Transfer
- 在线教育系统源码 知识付费系统源码 网络直播源码
- 九款实用的在线画图工具(那些可以替代Visio的应用)
- QT 字符乱码的原因
- PS如何制作GIF动画
- css中文字操超出固定个数显示省略... 超出隐藏
- MacOS系统安装fish,替代bash
- 余世维 有效沟通3
- 复杂业务系统的架构设计思路
- qml 分隔工具栏ToolSeparator 工具提示ToolTip 旋转轮Tumbler