效果图如下:
实现过程:
1.先从布局入手。要实现recyclerview全屏的拖拽,布局思路一定要正确。布局关系如下:
布局有一点需要注意:
recyclerview高度必须match_parent,这样才能全屏拖拽,但是为什么又要位于 editText之下呢。
这样是为了防止editText焦点被夺取,无法输入。位于editText之下后recyclerview又怎么滑出自己的布局呢,这时需要一个属性设置clipChildren=false.允许子View超出父View。
2.拖拽功能实现。 利用ItemTouchHelper
(1)自定义一个类集成并实现ItemTouchHelper.Callback(功能核心,代码里有注释)
/**
* created by dalang at 2018/11/26
* 微信拖拽排序删除
*/
public class WXTouchHelper extends ItemTouchHelper.Callback {
private int dragFlags;
private int swipeFlags;
private BGARecyclerViewAdapter adapter;
private List imagesList;//图片的顺序与拖拽顺序保持一致
private boolean up;//手指抬起标记位
private NestedScrollView scrollView;
public WXTouchHelper(BGARecyclerViewAdapter adapter, List imagesList, NestedScrollView scrollView) {
this.adapter = adapter;
this.imagesList = imagesList;
this.scrollView=scrollView;
}
/**
* 设置item是否处理拖拽事件和滑动事件,以及拖拽和滑动操作的方向
*
* @param recyclerView
* @param viewHolder
* @return
*/
@Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
//判断 recyclerView的布局管理器数据
if (recyclerView.getLayoutManager() instanceof StaggeredGridLayoutManager) {//设置能拖拽的方向
dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
swipeFlags = 0;//0则不响应事件
}
return makeMovementFlags(dragFlags, swipeFlags);
}
/**
* 当用户从item原来的位置拖动可以拖动的item到新位置的过程中调用
*
* @param recyclerView
* @param viewHolder
* @param target
* @return
*/
@Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder
viewHolder, RecyclerView.ViewHolder target) {
int fromPosition = viewHolder.getAdapterPosition();//得到item原来的position
int toPosition = target.getAdapterPosition();//得到目标position
//因为没有将 +号的图片 加入imageList,所以不用imageList.size-1 此处限制不能移动到recyclerView最后一位
if (toPosition == imagesList.size() || imagesList.size() == fromPosition) {
return false;
}
if (fromPosition < toPosition) {
for (int i = fromPosition; i < toPosition; i++) {
Collections.swap(imagesList, i, i + 1);
}
} else {
for (int i = fromPosition; i > toPosition; i--) {
Collections.swap(imagesList, i, i - 1);
}
}
adapter.notifyItemMoved(fromPosition, toPosition);
return true;
}
/**
* 设置是否支持长按拖拽
* 此处必须返回false
* 需要在recyclerView长按事件里限制,否则最后+号长按后扔可拖拽
* @return
*/
@Override
public boolean isLongPressDragEnabled() {
return false;
}
/**
* @param viewHolder
* @param direction
*/
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
}
/**
* 当用户与item的交互结束并且item也完成了动画时调用
*
* @param recyclerView
* @param viewHolder
*/
@Override
public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
super.clearView(recyclerView, viewHolder);
adapter.notifyDataSetChanged();
initData();
if (dragListener != null) {
dragListener.clearView();
}
}
/**
* 重置
*/
private void initData() {
if (dragListener != null) {
dragListener.deleteState(false);
dragListener.dragState(false);
}
up = false;
}
/**
* 自定义拖动与滑动交互
*
* @param c
* @param recyclerView
* @param viewHolder
* @param dX
* @param dY
* @param actionState
* @param isCurrentlyActive
*/
@Override
public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder,
float dX, float dY, int actionState, boolean isCurrentlyActive) {
if (null == dragListener) {
return;
}
//recyclerview上面的editText的高度为100
int editTextHeight=ToastUtil.getContext().getResources().getDimensionPixelSize(R.dimen.dimen_100);
//删除按钮高度
int buttonHeight=ToastUtil.getContext().getResources().getDimensionPixelSize(R.dimen.dimen_50);
/**
* item间隔10dp,因为item的xml布局中有个10dp的空白,
* 拖拽时是拖拽整个itemView所有导致底部10dp空白先接触删除按钮所以在判断阈值时应该考虑此10dp,
* 如果是采用addItemDecoration添加分割线就不用考虑这10dp,但是拖拽时会出现分割线遮挡的情况,具体效果可以自己实验一下
*/
int spaceHeight=ToastUtil.getContext().getResources().getDimensionPixelSize(R.dimen.dimen_10);//
/**
* scrollView.getHeight()-editTextHeight 为recyclerview的高度
* 此处不用onChildDraw里的参数recyclerView.getHeight来计算,因为当添加图片至超出屏幕高度
* 即scrollView可以滑动后获取的recyclerview不准确,亲测。
*/
if (dY>=(scrollView.getHeight()-editTextHeight)
- viewHolder.itemView.getBottom()//item底部距离recyclerView顶部高度
-buttonHeight
+scrollView.getScrollY()
+spaceHeight) {//拖到删除处
dragListener.deleteState(true);
if (up) {//在删除处放手,则删除item
//先设置不可见,如果不设置的话,会看到viewHolder返回到原位置时才消失
//,因为remove会在viewHolder动画执行完成后才将viewHolder删除
viewHolder.itemView.setVisibility(View.INVISIBLE);
imagesList.remove(viewHolder.getAdapterPosition());
dragListener.deleteOk();
adapter.notifyItemRemoved(viewHolder.getAdapterPosition());
initData();
return;
}
} else {//没有到删除处
//如果viewHolder不可见,则表示用户放手,重置删除区域状态
if (View.INVISIBLE == viewHolder.itemView.getVisibility()) {
dragListener.dragState(false);
}
dragListener.deleteState(false);
}
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
}
/**
* 当长按选中item的时候(拖拽开始的时候)调用
*
* @param viewHolder
* @param actionState
*/
@Override
public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
if (ItemTouchHelper.ACTION_STATE_DRAG == actionState && dragListener != null) {
dragListener.dragState(true);
}
super.onSelectedChanged(viewHolder, actionState);
}
/**
* 设置手指离开后ViewHolder的动画时间,在用户手指离开后调用
*
* @param recyclerView
* @param animationType
* @param animateDx
* @param animateDy
* @return
*/
@Override
public long getAnimationDuration(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);
/**
* 当用户与item的交互结束并且item也完成了动画时调用
*/
void clearView();
/**
* 当删除完成后调用
*/
void deleteOk();
}
private DragListener dragListener;
public void setDragListener(DragListener dragListener) {
this.dragListener = dragListener;
}
(2)activity调用及处理
拖拽事件开启
//绑定recyclerview
WXTouchHelper myCallBack = new WXTouchHelper(photoPublishAdapter, imgSelected,scrollView);
itemTouchHelper = new ItemTouchHelper(myCallBack);
itemTouchHelper.attachToRecyclerView(recyclerPhoto);
recyclerPhoto.addOnItemTouchListener(new OnRecyclerItemClickListener(recyclerPhoto) {
@Override
public void onItemClick(RecyclerView.ViewHolder viewHolder) {
if (viewHolder.getAdapterPosition() == imgSelected.size()) {
DialogUtil.uploadMultiplePhoto(mActivity, getTakePhoto(), limit);
} else {
if (imgSelected.size() != 0) {
Intent intent = new Intent(mActivity, BigPhotoActivity.class);
intent.putStringArrayListExtra("imgUrls", (ArrayList) imgSelected);
intent.putExtra("position", viewHolder.getAdapterPosition());
mSwipeBackHelper.forward(intent);
}
}
}
//长按事件中开启拖拽 需要判断position不是+号图片
@Override
public void onLongClick(RecyclerView.ViewHolder viewHolder) {
if (viewHolder.getAdapterPosition() != imgSelected.size()) {
BGAKeyboardUtil.closeKeyboard(mActivity);
itemTouchHelper.startDrag(viewHolder);
}
}
});
删除按钮显示
myCallBack.setDragListener(new WXTouchHelper.DragListener() {
@Override
public void deleteState(boolean delete) {
if (delete) {
tvDelete.setAlpha(0.8f);
tvDelete.setText("松手即可删除");
} else {
tvDelete.setAlpha(0.5f);
tvDelete.setText("拖到此处删除");
}
}
@Override
public void dragState(boolean start) {
if (start) {
tvDelete.setVisibility(View.VISIBLE);
} else {
tvDelete.setVisibility(View.GONE);
}
}
@Override
public void clearView() {
//删除图片后需要重新计算recyclerview下面布局的margin
fixBottom();
}
@Override
public void deleteOk() {
//删除后重新计算图片选择数量
limit = 9 - imgSelected.size();
}
});
底部布局处理
/**
* 处理recyclerView下面的布局
*/
private void fixBottom() {
int row = photoPublishAdapter.getItemCount() / 3;
row = (0 == photoPublishAdapter.getItemCount() % 3) ? row : row + 1;//少于3为1行
row = (4 == row) ? 3 : row;//最多为三行
int width = DisplayUtil.getScreenWidth(mActivity);
int itemWidth = (int) (width - getResources().getDimension(R.dimen.dimen_60)) / 3;//item宽高
int itemSpace=(int) getResources().getDimension(R.dimen.dimen_10);//item间隔
int marginTop = (getResources().getDimensionPixelSize(R.dimen.recycle_margin_top)
+ itemWidth * row
+itemSpace*(row-1)
+ getResources().getDimensionPixelSize(R.dimen.bottom_margin_top));
RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) llBottom.getLayoutParams();
params.setMargins(0, marginTop, 0, 0);
llBottom.setLayoutParams(params);
}
3.scrollView包裹editText处理其滚动冲突
/**
* created by dalang at 2018/11/28
*/
@SuppressLint("AppCompatCustomView")
public class EditTextWithScrollView extends EditText {
//滑动距离的最大边界
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+5;
if (mOffsetHeight == 0) {
return false;
}
return (scrollY > 0) || (scrollY < mOffsetHeight - 1);
}
}
4.底部文字点击事件处理
相关代码如下
在 OnRecyclerItemClickListener中添加方法
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);
}
}
}
//将textview固定高度好计算
60dp
//原有 textview style更改
//初始化高度属性
//textview 高度
bottomItemHeight = getResources().getDimensionPixelSize(R.dimen.bottom_textview_height);
//textview之间分割线 高度
lineSpace = (int) getResources().getDimension(R.dimen.dimen_1);
//左边距
leftMargin = (int)getResources().getDimension(R.dimen.dimen_20);
//同步到控件 星星的宽度 图片大小为30dp 左右各留10dp 方便用户点击
starWidth =(int) getResources().getDimension(R.dimen.dimen_50);
//
private void fixBottom() {
之前方法省略
//用于判断 在每次fix底部布局高度后判断 注意要减去顶部edittext的高度
judgeClickMargin = marginTop-getResources().getDimensionPixelSize(R.dimen.edittext_height);
}
//Activity中处理
@Override
public void onOtherClick(MotionEvent e) {
if (e.getY()>judgeClickMargin) {
int between=(int)e.getY()-judgeClickMargin;//判读触摸点与 bottom布局分界处的距离
int oneItem=(bottomItemHeight+lineSpace);//一个textview+一个分割线的高度
LogUtil.e(e.getY()+"---"+judgeClickMargin+"==="+oneItem);
if (between>0 && between<=oneItem) {
//点击在第一个textview上 ---所在位置
ToastUtil.normal("所在位置");
} 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("同步到空间");
}
}
}
其他文章链接地址:
(一)高斯模糊实现毛玻璃效果丶共享元素动画 丶地址选择器
(二)仿京东顶部伸缩渐变丶自定义viewpager指示器丶viewpager3D回廊丶recyclerview瀑布流
(三)RxJava2常用操作符merge、flatmap、zip--结合MVP架构讲解
(四)仿支付宝首页顶部伸缩滑动/中间层下拉刷新
(五)TabLayout+ViewPager悬浮吸顶及刷新数量动画显示
(六)仿QQ首页drawer/侧滑删除/浮动imgaeView/角标拖拽
将持续更新.. 不喜勿喷,仅个人分享,希望能帮助到你
源码地址:Github传送门