一、实现效果图
二、实现的基本思路(不需要重写RecyclerView的touch事件)
- 自定义ViewGroup,每一个list的item项的根布局都使用自定义的ViewGoup。在其中管理item view和menu view显示的位置。
- 在自定义的ViewGroup中处理滑动事件,根据滑动事件处理view的移动(item view 和menu view的移动)
- 使用scroller实现滚动动画,通过设置ViewGroup的scrollX控制显示位置
- 在自定义ViewGroup中监听recycler view的滚动事件,当发生滚动时关闭item menu
三、具体的实现
计算item view和menu view的大小并设置自定义ViewGroup的大小
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (getChildCount() != 2) {
throw new RuntimeException("child count error!You can only add two child view");
}
//第一个view是item view,测量大小
View childView = getChildAt(0);
measureChild(childView, widthMeasureSpec, heightMeasureSpec);
//第二个view是menu view,测量大小
childView = getChildAt(1);
measureChild(childView, widthMeasureSpec, heightMeasureSpec);
//设置ViewGroup的大小
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), childView.getMeasuredHeight());
}
设置item view和menu view的显示位置
protected void onLayout(boolean changed, int l, int t, int r, int b) {
//自定义ViewGroup只能添加item view和menu view两个子view
if (getChildCount() != 2) {
throw new RuntimeException("child count error!You can only add two child view");
}
View childView = getChildAt(0);
//item view填充显示在自定义ViewGroup
childView.layout(0, 0, childView.getMeasuredWidth(), childView.getMeasuredHeight());
childView = getChildAt(1);
//item menu 默认显示在item view的右侧
childView.layout(getRight(), 0, getRight() + childView.getMeasuredWidth(), childView.getMeasuredHeight());
}
滑动事件处理通过TouchHelper完成,主要包括onInterceptTouchEvent和onTouchEvent两个方法
//处理事件的截断处理
public boolean onInterceptTouchEvent(MotionEvent ev) {
int action = ev.getActionMasked();
switch (action) {
case MotionEvent.ACTION_DOWN:
//取消scroller处理,记录down事件的位置用于计算是否滑动
scroller.forceFinished(true);
downX = ev.getX();
lastX = downX;
dragFlag = false;
break;
case MotionEvent.ACTION_MOVE:
float currentX = ev.getX();
lastX = currentX;
float diffX = Math.abs(currentX - downX);
//判断是否超过了滑动触发距离
if (diffX > touchSlop) {
dragFlag = true;
//通知所有事件都发送到当前view的touch方法
view.getParent().requestDisallowInterceptTouchEvent(true);
}
break;
}
return dragFlag;
}
public boolean onTouchEvent(MotionEvent event) {
int action = event.getActionMasked();
switch (action) {
case MotionEvent.ACTION_DOWN:
return false;
case MotionEvent.ACTION_MOVE: {
float currentX = event.getX();
float offsetX = currentX - lastX;
lastX = currentX;
View mainView = view.getChildAt(0);
View menuView = view.getChildAt(1);
//根据touch事件控制view的滑动,边界条件检查
offsetX = positionHelper.checkBoundary(view, menuView, offsetX);
//根据偏移量控制view移动
positionHelper.moveChild(mainView, menuView, (int) offsetX);
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL: {
float currentX = event.getX();
direct = (int) (currentX - downX);
//启动自动滚动到目标位置
smoothScroll(direct);
}
break;
}
return true;
}
通过scroller控制view自动滚动
public void smoothScroll(int direct) {
View menuView = view.getChildAt(1);
//计算到达目标位置的偏移量
int dx = positionHelper.computeDx(view, menuView, direct);
if (dx == 0) {
return;
}
//启动scroller计算滚动位置
scroller.startScroll(positionHelper.getCurrentPosition(menuView), 0, dx, 0);
//触发下一次的偏移位置计算
handler.post(runnable);
}
public void computeScroll() {
//判断是否滚动到目标位置
if (scroller.computeScrollOffset()) {
int x = scroller.getCurrX();
View mainView = view.getChildAt(0);
View menuView = view.getChildAt(1);
int offset = x - positionHelper.getCurrentPosition(menuView);
//根据偏移量移动view
positionHelper.moveChild(mainView, menuView, offset);
handler.post(runnable);
}
}
PositionHelper2实现了移动view的功能
public class PositionHelper2 implements PositionHelper {
//用于边界检查
public int checkBoundary(ViewGroup parent, View menuView, float offsetX) {
int max = menuView.getMeasuredWidth();
int min = 0;
int scroll = parent.getScrollX();
if (scroll - offsetX < min) {
offsetX = -(min - scroll);
} else if (scroll - offsetX > max) {
offsetX = -(max - scroll);
}
return (int) offsetX;
}
//通过设置mScrollX移动子view的位置
public void moveChild(View mainView, View menuView, int offsetX){
ViewGroup parent = (ViewGroup)mainView.getParent();
parent.scrollBy(-offsetX,0);
}
//计算距离目标位置的距离
public int computeDx(ViewGroup parentView, View menuView, int direct){
int max = menuView.getMeasuredWidth();
int scroll = parentView.getScrollX();
int dx = -direct > 0 ? max - scroll: -scroll;
return -dx;
}
public int getCurrentPosition(View view){
ViewGroup parent = (ViewGroup)view.getParent();
return -parent.getScrollX();
}
}
监听RecyclerView的滚动状态变化,RecyclerView滚动时重置scroll x。
private RecyclerView.OnScrollListener onScrollListener = new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
if (newState == SCROLL_STATE_DRAGGING) {
//RecyclerView拖拽时重置scroll x,恢复到原来的位置
touchHelper.smoothScroll(1);
}
}
};
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
//添加监听
if (getParent() instanceof RecyclerView) {
RecyclerView recyclerView = (RecyclerView) getParent();
recyclerView.addOnScrollListener(onScrollListener);
}
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
//删除监听
if (getParent() instanceof RecyclerView) {
RecyclerView recyclerView = (RecyclerView) getParent();
recyclerView.removeOnScrollListener(onScrollListener);
}
}
四、git 地址
https://github.com/mjlong123123/DragItemViewGroup/tree/dev
我的公众号已经开通,公众号会同步发布。
欢迎关注我的公众号