上一篇中我们学习了使用itemTouchHelper来上下拖拽和侧滑删除view,但是如果需要在item侧滑后响应我们的按钮点击事件是无法生效的,因为ItemTouchHelper并没有进行子view的事件分发,导致无法响应在recyclerView中的子view的点击事件。这就需要我们自己完成对子view的事件分发了,整体实现效果如图,完整项目
在这个recyclerView中我么定义了三种布局的item,里面都有点击删除事件,我们侧滑出来之后,再点击删除可以完成删除操作,就表示我们的子view响应了点击事件。本篇主要讲解思路如何将源码中的ItemTouchHelper这个类支持我们的事件分发
我们需要自己完成ItemTouchHelper中的事件分发,我们把代码copy一份到自己项目中,这个文件唯一依赖的是ItemTouchUIUtilImpl,因此我们将这两个类拷贝出来。我们核心需要添加的就两步:
- 1、找到当前的这个按钮
- 2、开始事件分发
/**
* 分发按键事件给子控件
* 根据坐标找到自控控件
*
* @param event
*/
private void doChildClickEvent(MotionEvent event) {
// mSelected 当前选择的需要上下滑动或者左右删除的元素view
if (mSelected == null) return;
View consumeEventView = mSelected.itemView;
if (consumeEventView instanceof ViewGroup) {
consumeEventView = findConsumeView((ViewGroup) consumeEventView, event.getRawX(),
event.getRawY());
}
/**
* 找到了最终的view,分发click事件
*/
if (consumeEventView != null) {
consumeEventView.performClick();
}
}
/**
* 通过递归来找当前需要响应的view
*/
private View findConsumeView(ViewGroup parent, float x, float y) {
for (int i = 0; i < parent.getChildCount(); i++) {
View child = parent.getChildAt(i);
if (child instanceof ViewGroup && child.getVisibility() == View.VISIBLE) {
View view = findConsumeView((ViewGroup) child, x, y);
if (view != null) {
return view;
}
} else {
if (isInBounds((int) x, (int) y, child)) {
return child;
}
}
}
if (isInBounds((int) x, (int) y, parent)) {
return parent;
}
return null;
}
/**
* 判断是不是在当前的这个xy区域
*/
private boolean isInBounds(int x, int y, View child) {
int[] location = new int[2];
child.getLocationOnScreen(location);
Rect rect = new Rect(location[0], location[1], location[0] + child.getWidth(), location[1] + child.getHeight());
if (rect.contains(x, y) && ViewCompat.hasOnClickListeners(child) &&
child.getVisibility() == View.VISIBLE) {
return true;
}
return false;
}
整体思路比较清晰:在当前选择的需要上下滑动或者左右删除的元素view中,利用递归viewgroup去找到需要响应的子view元素
步骤二:事件分发出去:在onInterceptTouchEvent这个方法中,我们设置两个变量,mClick表示是否点击,initX表示当前手指的x坐标位置;整体思路就是要是点击状态,并且是ACTION_UP则表示我们完成了这次点击,需要调用doChildClickEvent(event),去响应我们的子view的点击事件了。例如:
else if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
if (mClick && action == MotionEvent.ACTION_UP) {
doChildClickEvent(event);
}
case MotionEvent.ACTION_UP:
if (mClick) {
doChildClickEvent(event);
}
mClick = false;
整体的响应点击事件的就这两处;
为了防止手的抖动造成左右滑动时误操作,我们利用initX进行滑动距离判断:
case MotionEvent.ACTION_MOVE: {
// Find the index of the active pointer and fetch its position
if (activePointerIndex >= 0) {
float abs = Math.abs(initX - event.getX(0));
if (abs > 50) {
mClick = false;
}
步骤三:已经侧滑出来的view和其他侧滑view以及整个recyclerView上下滑动时的互斥操作
我们已经侧滑出来的view需要有互斥操作,分为下面两个场景:
- 需要在整体recyclerView上下滑动时关闭掉;
- 已经侧滑出来的view此时要是有其他view侧滑出来则已经侧滑出来的view同样需要关闭掉。
场景一:在源码attachToRecyclerView方法内添加整体recyclerView的滑动监听即可:
/**
* 自定义:添加滑动监听
*/
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (newState == RecyclerView.SCROLL_STATE_DRAGGING && mPreOpened != null) {
closeOpenedPreItem();
}
}
});
场景二:在源码checkSelectForSwipe方法内添加:
/**
* 完成了侧滑操作,并且不是当前的viewHolder,我们将原本的view close掉
*/
if (mPreOpened != null && mPreOpened != vh) {
closeOpenedPreItem();
}
下面的就是需要关闭的动画:
/**
* 已经侧滑出来的view和其他侧滑view以及整个recyclerView上下滑动时的互斥操作的动画
*/
private void closeOpenedPreItem() {
final View view = getItemFrontView(mPreOpened);
if (mPreOpened == null || view == null) return;
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(view, "translationX", view.getTranslationX(), 0f);
objectAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
super.onAnimationStart(animation);
if (mPreOpened != null) mCallback.clearView(mRecyclerView, mPreOpened);
if (mPreOpened != null) mPendingCleanup.remove(mPreOpened.itemView);
endRecoverAnimation(mPreOpened, true);
mPreOpened = mSelected;
}
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
}
});
objectAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
objectAnimator.start();
}
public View getItemFrontView(RecyclerView.ViewHolder viewHolder) {
if (viewHolder == null) return null;
if (viewHolder.itemView instanceof ViewGroup &&
((ViewGroup) viewHolder.itemView).getChildCount() > 1) {
ViewGroup viewGroup = (ViewGroup) viewHolder.itemView;
return viewGroup.getChildAt(viewGroup.getChildCount() - 1);
} else {
return viewHolder.itemView;
}
}
由于这个类源码有2000多行,我们完成后的源码在这里。https://github.com/buder-cp/CustomView/blob/master/buder_DN_view/buderdn11extension/src/main/java/com/test/buderdn11extension/itemTouchHelper/ItemTouchHelper.java
具体使用方法就是将我们上一节中的import替换成我们修改后的这个类即可。完整项目见:https://github.com/buder-cp/CustomView/tree/master/buder_DN_view/buderdn11extension