Android 事件分发 系列文章目录
【Android 事件分发】事件分发源码分析 ( 驱动层通过中断传递事件 | WindowManagerService 向 View 层传递事件 )
【Android 事件分发】事件分发源码分析 ( Activity 中各层级的事件传递 | Activity -> PhoneWindow -> DecorView -> ViewGroup )
【Android 事件分发】事件分发源码分析 ( ViewGroup 事件传递机制 一 )
【Android 事件分发】事件分发源码分析 ( ViewGroup 事件传递机制 二 )
【Android 事件分发】事件分发源码分析 ( ViewGroup 事件传递机制 三 )
【Android 事件分发】事件分发源码分析 ( ViewGroup 事件传递机制 四 | View 事件传递机制 )
【Android 事件分发】事件分发源码分析 ( ViewGroup 事件传递机制 五 )
【Android 事件分发】事件分发源码分析 ( ViewGroup 事件传递机制 六 )
【Android 事件分发】事件分发源码分析 ( ViewGroup 事件传递机制 七 )
【Android 事件分发】ItemTouchHelper 简介 ( 拖动/滑动事件 | ItemTouchHelper.Callback 回调 )
【Android 事件分发】ItemTouchHelper 实现侧滑删除 ( 设置滑动方向 | 启用滑动操作 | 滑动距离判定 | 滑动速度判定 | 设置动画时间 | 设置侧滑触发操作 )
【Android 事件分发】ItemTouchHelper 实现拖动排序 ( 设置滑动方向 | 启启用长按拖动功能 | 拖动距离判定 | 设置拖动触发操作 )
【Android 事件分发】ItemTouchHelper 事件分发源码分析 ( 绑定 RecyclerView )
【Android 事件分发】ItemTouchHelper 源码分析 ( OnItemTouchListener 事件监听器源码分析 )
文章目录
- Android 事件分发 系列文章目录
- 一、OnItemTouchListener 事件监听器引入
- 二、OnItemTouchListener 触摸事件拦截方法 onInterceptTouchEvent
-
- 1、onInterceptTouchEvent 方法简介
- 2、处理按下事件
- 3、findAnimation 方法
- 4、findChildView 方法
- 5、动作取消
- 6、动作完成
- 三、ItemTouchHelper 涉及到的本博客相关源码
- 四、博客资源
一、OnItemTouchListener 事件监听器引入
在上一篇博客 【Android 事件分发】ItemTouchHelper 事件分发源码分析 ( 绑定 RecyclerView ) 分析了 ItemTouchHelper 添加时 , 调用了 mItemTouchHelper.attachToRecyclerView(recycler_view)
方法 , 将 ItemTouchHelper 与 RecyclerView 进行关联 , 并在 attachToRecyclerView 方法的最后 , 调用了 setupCallbacks 方法 ;
// 该方法与 destroyCallbacks 方法相对应
private void setupCallbacks() {
// 配置相关
ViewConfiguration vc = ViewConfiguration.get(mRecyclerView.getContext());
mSlop = vc.getScaledTouchSlop();
// 设置 RecyclerView 条目中的装饰 , 可以在条目组件 底部 上层 绘制 Canvas 图形
// ItemTouchHelper 继承 RecyclerView.ItemDecoration
mRecyclerView.addItemDecoration(this);
// 添加了每个条目上的触摸监听器 mOnItemTouchListener
// 该监听器是定义在 ItemTouchHelper 中的成员变量
mRecyclerView.addOnItemTouchListener(mOnItemTouchListener);
mRecyclerView.addOnChildAttachStateChangeListener(this);
startGestureDetection();
}
上一篇博客分析到 mRecyclerView.addItemDecoration(this)
位置 , 本篇博客重点分析 mRecyclerView.addOnItemTouchListener(mOnItemTouchListener)
中的 mOnItemTouchListener , 这是 RecyclerView 的 ItemTouchHelper 的核心 ;
OnItemTouchListener 是 RecyclerView 中定义的作用与条目组件的触摸监听器 , 主要是拦截触摸事件方法 onInterceptTouchEvent
和 消费触摸事件方法 onTouchEvent
;
public class RecyclerView extends ViewGroup implements ScrollingView,
NestedScrollingChild2, NestedScrollingChild3 {
public interface OnItemTouchListener {
boolean onInterceptTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e);
void onTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e);
void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept);
}
}
ItemTouchHelper 事件分发 , 分析手指触摸的 按下 , 移动 , 抬起 事件 ;
在 OnItemTouchListener 触摸监听器中 , onInterceptTouchEvent 方法处理的是事件拦截机制 , onTouchEvent 方法是最终消费事件的方法 ;
在 onInterceptTouchEvent 事件拦截 中 , 只拦截 MotionEvent.ACTION_DOWN / MotionEvent.ACTION_CANCEL / MotionEvent.ACTION_UP 等
种事件 , 不拦截 MotionEvent.ACTION_MOVE 事件 ;
在 onTouchEvent 事件消费 中 , 才处理 MotionEvent.ACTION_MOVE 事件 ;
ItemTouchHelper 没有对子控件进行事件分发 , 其只识别一层组件 , 如果内部有多层组件 , 内层的组件无法分发到事件 ;
二、OnItemTouchListener 触摸事件拦截方法 onInterceptTouchEvent
1、onInterceptTouchEvent 方法简介
在 ItemTouchHelper 中定义的成员变量
代码语言:javascript复制 private final OnItemTouchListener mOnItemTouchListener = new OnItemTouchListener() {
// 拦截触摸事件 , 处理拦截机制
@Override
public boolean onInterceptTouchEvent(@NonNull RecyclerView recyclerView,
@NonNull MotionEvent event) {
}
}
中实现的 RecyclerView.OnItemTouchListener 接口的 onInterceptTouchEvent 方法 , 主要是用于作用与条目上的触摸事件的拦截 ;
注意此处拦截的动作 , 只拦截 DOWN / UP / CANCEL 三种动作 , MOVE 动作不拦截 , 取消操作很少遇到 , 因此 , 拦截机制中 , 只负责拦截手指按下 和 抬起 操作 , 在 ItemTouchHelper 的业务逻辑中 , 不需要处理移动事件 ;
2、处理按下事件
当检测到 MotionEvent.ACTION_DOWN 按下操作时 , 获取按下的 XY 坐标 , 并进行滑动速度检测 ;
代码语言:javascript复制 // 注意此处拦截的动作 , 只拦截 DOWN / UP / CANCEL 三种动作 , MOVE 动作不拦截
// 取消操作很少遇到
// 因此 , 拦截机制中 , 只负责拦截手指按下 和 抬起 操作
// 在 ItemTouchHelper 的业务逻辑中 , 不需要处理移动事件
if (action == MotionEvent.ACTION_DOWN) {
// 按下操作 , 得到初始 XY 坐标位置
mActivePointerId = event.getPointerId(0);
mInitialTouchX = event.getX();
mInitialTouchY = event.getY();
// 滑动速度检测
obtainVelocityTracker();
ViewHolder mSelected = null;
成员是当前正在点击的 ViewHolder 条目 , 如果该 mSelected 成员为空 , 则执行后续操作 ;
// mSelected 是当前正在点击的条目的 ViewHolder
if (mSelected == null) {
// 恢复动画 , 查找手指按下的 View 子组件 , 该子组件时 RecyclerView 中的一个条目
// 用户按下 RecyclerView 中的某个条目
// findAnimation 方法用于找到按下的条目 View , 并设置给 RecoverAnimation 恢复动画
final RecoverAnimation animation = findAnimation(event);
if (animation != null) {
mInitialTouchX -= animation.mX;
mInitialTouchY -= animation.mY;
endRecoverAnimation(animation.mViewHolder, true);
if (mPendingCleanup.remove(animation.mViewHolder.itemView)) {
mCallback.clearView(mRecyclerView, animation.mViewHolder);
}
// 为动画选择 item 项
select(animation.mViewHolder, animation.mActionState);
// 计算当前移动的位置 , 初始位置 与 最后一次事件的位置 偏移值 .
updateDxDy(event, mSelectedFlags, 0);
}
}
先恢复动画 , 查找手指按下的 View 子组件 , 该子组件是 RecyclerView 中的一个条目 , 用户按下 RecyclerView 中的某个条目 , findAnimation 方法用于找到按下的条目 View , 并设置给 RecoverAnimation 恢复动画 ;
3、findAnimation 方法
在 findAnimation 方法中 , 先调用了 findChildView 方法 , 查找手指按下的 View 子组件 , 该子组件是 RecyclerView 中的一个条目 ;
代码语言:javascript复制 // 找到手指按下所在位置的条目的 View 组件
// 查找手指按下的 View 子组件 , 该子组件时 RecyclerView 中的一个条目
View target = findChildView(event);
找到该条目对应的 View 组件后 , 遍历恢复动画 , 动画中有 mViewHolder 成员 , mViewHolder 中有 itemView 成员 , 设置 anim.mViewHolder.itemView 为手指按下的子组件 , 即设置该动画作用于 RecyclerView 的哪个条目上 ;
代码语言:javascript复制 for (int i = mRecoverAnimations.size() - 1; i >= 0; i--) {
final RecoverAnimation anim = mRecoverAnimations.get(i);
if (anim.mViewHolder.itemView == target) {
return anim;
}
}
findAnimation 方法完整代码注释 :
代码语言:javascript复制 // 该方法作用 :
// 用户按下 RecyclerView 中的某个条目
// 该方法用于找到按下的条目 View , 并设置给 RecoverAnimation 恢复动画
@SuppressWarnings("WeakerAccess") /* synthetic access */
RecoverAnimation findAnimation(MotionEvent event) {
if (mRecoverAnimations.isEmpty()) {
return null;
}
// 找到手指按下所在位置的条目的 View 组件
// 查找手指按下的 View 子组件 , 该子组件时 RecyclerView 中的一个条目
View target = findChildView(event);
// 遍历恢复动画
// 动画中有 mViewHolder 成员 , mViewHolder 中有 itemView 成员
// 设置 anim.mViewHolder.itemView 为手指按下的子组件
// 即设置该动画作用于 RecyclerView 的哪个条目上 ;
for (int i = mRecoverAnimations.size() - 1; i >= 0; i--) {
final RecoverAnimation anim = mRecoverAnimations.get(i);
if (anim.mViewHolder.itemView == target) {
return anim;
}
}
return null;
}
4、findChildView 方法
根据按下的 X, Y 坐标 , 查找对应的条目组件 , 先获取触摸的 XY 坐标 ;
代码语言:javascript复制 final float x = event.getX();
final float y = event.getY();
如果 mSelected 成员不为空 , 则直接使用 , 分支中直接返回了 ;
代码语言:javascript复制 if (mSelected != null) {
final View selectedView = mSelected.itemView;
if (hitTest(selectedView, x, y, mSelectedStartX mDx, mSelectedStartY mDy)) {
return selectedView;
}
}
如果 mSelected 为空 , 则开始遍历进行检测 ;
代码语言:javascript复制 for (int i = mRecoverAnimations.size() - 1; i >= 0; i--) {
final RecoverAnimation anim = mRecoverAnimations.get(i);
final View view = anim.mViewHolder.itemView;
// 根据当前按下的坐标 , 找到列表条目对应的 View 组件
if (hitTest(view, x, y, anim.mX, anim.mY)) {
return view;
}
}
findChildView 完整代码注释 :
代码语言:javascript复制 @SuppressWarnings("WeakerAccess") /* synthetic access */
View findChildView(MotionEvent event) {
// first check elevated views, if none, then call RV
// 根据按下的 X, Y 坐标 , 查找对应的条目
final float x = event.getX();
final float y = event.getY();
// 如果 mSelected 成员不为空 , 则直接使用 , 分支中直接返回了
if (mSelected != null) {
final View selectedView = mSelected.itemView;
if (hitTest(selectedView, x, y, mSelectedStartX mDx, mSelectedStartY mDy)) {
return selectedView;
}
}
// 如果 mSelected 为空 , 则开始遍历进行检测
for (int i = mRecoverAnimations.size() - 1; i >= 0; i--) {
final RecoverAnimation anim = mRecoverAnimations.get(i);
final View view = anim.mViewHolder.itemView;
// 根据当前按下的坐标 , 找到列表条目对应的 View 组件
if (hitTest(view, x, y, anim.mX, anim.mY)) {
return view;
}
}
return mRecyclerView.findChildViewUnder(x, y);
}
5、动作取消
当拦截的动作是 MotionEvent.ACTION_CANCEL 或 action == MotionEvent.ACTION_UP 动作时 , 说明用户取消了该动作 , 将选择的组件置空 ;
代码语言:javascript复制 } else if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
mActivePointerId = ACTIVE_POINTER_ID_NONE;
// 抬起 / 取消 时 , 选择项 置空 .
select(null, ACTION_STATE_IDLE);
}
6、动作完成
ACTIVE_POINTER_ID_NONE 表示是否完成了滑动 , 如果滑动完成 , 触发了侧滑事件 , 才会进入该 else if (mActivePointerId != ACTIVE_POINTER_ID_NONE)
分支 , 如果滑动没有完成 , 滑到半路 , 松开手 , 条目组件缩回去了 , 则不会进入该分支 ;
} else if (mActivePointerId != ACTIVE_POINTER_ID_NONE) {
// 该分支表示滑动操作完成的分支
// ACTIVE_POINTER_ID_NONE 表示是否完成了滑动
// 如果滑动完成 , 触发了侧滑事件 , 才会进入该分支
// 如果滑动没有完成 , 滑到半路 , 松开手 , 条目组件缩回去了 , 则不会进入该分支
// in a non scroll orientation, if distance change is above threshold, we
// can select the item
// 滑动完成后 , 记录当前的触摸指针索引
final int index = event.findPointerIndex(mActivePointerId);
if (DEBUG) {
Log.d(TAG, "pointer index " index);
}
if (index >= 0) {
// 检查是否完成了滑动操作
checkSelectForSwipe(action, event, index);
}
}
三、ItemTouchHelper 涉及到的本博客相关源码
代码语言:javascript复制public class ItemTouchHelper extends RecyclerView.ItemDecoration
implements RecyclerView.OnChildAttachStateChangeListener {
/**
* Currently selected view holder
*/
@SuppressWarnings("WeakerAccess") /* synthetic access */
ViewHolder mSelected = null;
/**
* The diff between the last event and initial touch.
* 最后的触摸事件和初始触摸事件之间的坐标差异 , 偏移值 .
*/
float mDx;
float mDy;
private final OnItemTouchListener mOnItemTouchListener = new OnItemTouchListener() {
// 拦截触摸事件 , 处理拦截机制
@Override
public boolean onInterceptTouchEvent(@NonNull RecyclerView recyclerView,
@NonNull MotionEvent event) {
mGestureDetector.onTouchEvent(event);
if (DEBUG) {
Log.d(TAG, "intercept: x:" event.getX() ",y:" event.getY() ", " event);
}
final int action = event.getActionMasked();
// 注意此处拦截的动作 , 只拦截 DOWN / UP / CANCEL 三种动作 , MOVE 动作不拦截
// 取消操作很少遇到
// 因此 , 拦截机制中 , 只负责拦截手指按下 和 抬起 操作
// 在 ItemTouchHelper 的业务逻辑中 , 不需要处理移动事件
if (action == MotionEvent.ACTION_DOWN) {
// 按下操作 , 得到初始 XY 坐标位置
mActivePointerId = event.getPointerId(0);
mInitialTouchX = event.getX();
mInitialTouchY = event.getY();
// 滑动速度检测
obtainVelocityTracker();
// mSelected 是当前正在点击的条目的 ViewHolder
if (mSelected == null) {
// 恢复动画 , 查找手指按下的 View 子组件 , 该子组件时 RecyclerView 中的一个条目
// 用户按下 RecyclerView 中的某个条目
// findAnimation 方法用于找到按下的条目 View , 并设置给 RecoverAnimation 恢复动画
final RecoverAnimation animation = findAnimation(event);
if (animation != null) {
mInitialTouchX -= animation.mX;
mInitialTouchY -= animation.mY;
endRecoverAnimation(animation.mViewHolder, true);
if (mPendingCleanup.remove(animation.mViewHolder.itemView)) {
mCallback.clearView(mRecyclerView, animation.mViewHolder);
}
// 为动画选择 item 项
select(animation.mViewHolder, animation.mActionState);
// 计算当前移动的位置 , 初始位置 与 最后一次事件的位置 偏移值 .
updateDxDy(event, mSelectedFlags, 0);
}
}
} else if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
mActivePointerId = ACTIVE_POINTER_ID_NONE;
// 抬起 / 取消 时 , 选择项 置空 .
select(null, ACTION_STATE_IDLE);
} else if (mActivePointerId != ACTIVE_POINTER_ID_NONE) {
// 该分支表示滑动操作完成的分支
// ACTIVE_POINTER_ID_NONE 表示是否完成了滑动
// 如果滑动完成 , 触发了侧滑事件 , 才会进入该分支
// 如果滑动没有完成 , 滑到半路 , 松开手 , 条目组件缩回去了 , 则不会进入该分支
// in a non scroll orientation, if distance change is above threshold, we
// can select the item
// 滑动完成后 , 记录当前的触摸指针索引
final int index = event.findPointerIndex(mActivePointerId);
if (DEBUG) {
Log.d(TAG, "pointer index " index);
}
if (index >= 0) {
// 检查是否完成了滑动操作
checkSelectForSwipe(action, event, index);
}
}
if (mVelocityTracker != null) {
mVelocityTracker.addMovement(event);
}
return mSelected != null;
}
};
// 该方法作用 :
// 用户按下 RecyclerView 中的某个条目
// 该方法用于找到按下的条目 View , 并设置给 RecoverAnimation 恢复动画
@SuppressWarnings("WeakerAccess") /* synthetic access */
RecoverAnimation findAnimation(MotionEvent event) {
if (mRecoverAnimations.isEmpty()) {
return null;
}
// 找到手指按下所在位置的条目的 View 组件
// 查找手指按下的 View 子组件 , 该子组件时 RecyclerView 中的一个条目
View target = findChildView(event);
// 遍历恢复动画
// 动画中有 mViewHolder 成员 , mViewHolder 中有 itemView 成员
// 设置 anim.mViewHolder.itemView 为手指按下的子组件
// 即设置该动画作用于 RecyclerView 的哪个条目上 ;
for (int i = mRecoverAnimations.size() - 1; i >= 0; i--) {
final RecoverAnimation anim = mRecoverAnimations.get(i);
if (anim.mViewHolder.itemView == target) {
return anim;
}
}
return null;
}
@SuppressWarnings("WeakerAccess") /* synthetic access */
View findChildView(MotionEvent event) {
// first check elevated views, if none, then call RV
// 根据按下的 X, Y 坐标 , 查找对应的条目
final float x = event.getX();
final float y = event.getY();
// 如果 mSelected 成员不为空 , 则直接使用 , 分支中直接返回了
if (mSelected != null) {
final View selectedView = mSelected.itemView;
if (hitTest(selectedView, x, y, mSelectedStartX mDx, mSelectedStartY mDy)) {
return selectedView;
}
}
// 如果 mSelected 为空 , 则开始遍历进行检测
for (int i = mRecoverAnimations.size() - 1; i >= 0; i--) {
final RecoverAnimation anim = mRecoverAnimations.get(i);
final View view = anim.mViewHolder.itemView;
// 根据当前按下的坐标 , 找到列表条目对应的 View 组件
if (hitTest(view, x, y, anim.mX, anim.mY)) {
return view;
}
}
return mRecyclerView.findChildViewUnder(x, y);
}
// 判断删上下左右边距 是否在对应子组件范围内
private static boolean hitTest(View child, float x, float y, float left, float top) {
return x >= left
&& x <= left child.getWidth()
&& y >= top
&& y <= top child.getHeight();
}
/**
* Starts dragging or swiping the given View. Call with null if you want to clear it.
* 开始拖动/滑动给定的 View 组件. 如果想要清除传入 null.
* 为动画选择 item 项
* 该方法中进行一系列的计算
*
* @param selected The ViewHolder to drag or swipe. Can be null if you want to cancel the
* current action, but may not be null if actionState is ACTION_STATE_DRAG.
* @param actionState The type of action
*/
@SuppressWarnings("WeakerAccess") /* synthetic access */
void select(@Nullable ViewHolder selected, int actionState) {
if (selected == mSelected && actionState == mActionState) {
return;
}
mDragScrollStartTimeInMs = Long.MIN_VALUE;
final int prevActionState = mActionState;
// prevent duplicate animations
endRecoverAnimation(selected, true);
mActionState = actionState;
if (actionState == ACTION_STATE_DRAG) {
if (selected == null) {
throw new IllegalArgumentException("Must pass a ViewHolder when dragging");
}
// we remove after animation is complete. this means we only elevate the last drag
// child but that should perform good enough as it is very hard to start dragging a
// new child before the previous one settles.
mOverdrawChild = selected.itemView;
addChildDrawingOrderCallback();
}
int actionStateMask = (1 << (DIRECTION_FLAG_COUNT DIRECTION_FLAG_COUNT * actionState))
- 1;
boolean preventLayout = false;
if (mSelected != null) {
final ViewHolder prevSelected = mSelected;
if (prevSelected.itemView.getParent() != null) {
final int swipeDir = prevActionState == ACTION_STATE_DRAG ? 0
: swipeIfNecessary(prevSelected);
releaseVelocityTracker();
// find where we should animate to
final float targetTranslateX, targetTranslateY;
int animationType;
switch (swipeDir) {
case LEFT:
case RIGHT:
case START:
case END:
targetTranslateY = 0;
targetTranslateX = Math.signum(mDx) * mRecyclerView.getWidth();
break;
case UP:
case DOWN:
targetTranslateX = 0;
targetTranslateY = Math.signum(mDy) * mRecyclerView.getHeight();
break;
default:
targetTranslateX = 0;
targetTranslateY = 0;
}
if (prevActionState == ACTION_STATE_DRAG) {
animationType = ANIMATION_TYPE_DRAG;
} else if (swipeDir > 0) {
animationType = ANIMATION_TYPE_SWIPE_SUCCESS;
} else {
animationType = ANIMATION_TYPE_SWIPE_CANCEL;
}
getSelectedDxDy(mTmpPosition);
final float currentTranslateX = mTmpPosition[0];
final float currentTranslateY = mTmpPosition[1];
final RecoverAnimation rv = new RecoverAnimation(prevSelected, animationType,
prevActionState, currentTranslateX, currentTranslateY,
targetTranslateX, targetTranslateY) {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
if (this.mOverridden) {
return;
}
if (swipeDir <= 0) {
// this is a drag or failed swipe. recover immediately
mCallback.clearView(mRecyclerView, prevSelected);
// full cleanup will happen on onDrawOver
} else {
// wait until remove animation is complete.
mPendingCleanup.add(prevSelected.itemView);
mIsPendingCleanup = true;
if (swipeDir > 0) {
// Animation might be ended by other animators during a layout.
// We defer callback to avoid editing adapter during a layout.
postDispatchSwipe(this, swipeDir);
}
}
// removed from the list after it is drawn for the last time
if (mOverdrawChild == prevSelected.itemView) {
removeChildDrawingOrderCallbackIfNecessary(prevSelected.itemView);
}
}
};
final long duration = mCallback.getAnimationDuration(mRecyclerView, animationType,
targetTranslateX - currentTranslateX, targetTranslateY - currentTranslateY);
rv.setDuration(duration);
mRecoverAnimations.add(rv);
rv.start();
preventLayout = true;
} else {
removeChildDrawingOrderCallbackIfNecessary(prevSelected.itemView);
mCallback.clearView(mRecyclerView, prevSelected);
}
mSelected = null;
}
if (selected != null) {
mSelectedFlags =
(mCallback.getAbsoluteMovementFlags(mRecyclerView, selected) & actionStateMask)
>> (mActionState * DIRECTION_FLAG_COUNT);
mSelectedStartX = selected.itemView.getLeft();
mSelectedStartY = selected.itemView.getTop();
mSelected = selected;
if (actionState == ACTION_STATE_DRAG) {
mSelected.itemView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
}
}
final ViewParent rvParent = mRecyclerView.getParent();
if (rvParent != null) {
rvParent.requestDisallowInterceptTouchEvent(mSelected != null);
}
if (!preventLayout) {
mRecyclerView.getLayoutManager().requestSimpleAnimationsInNextLayout();
}
mCallback.onSelectedChanged(mSelected, mActionState);
mRecyclerView.invalidate();
}
// 计算当前移动的位置
@SuppressWarnings("WeakerAccess") /* synthetic access */
void updateDxDy(MotionEvent ev, int directionFlags, int pointerIndex) {
final float x = ev.getX(pointerIndex);
final float y = ev.getY(pointerIndex);
// Calculate the distance moved
mDx = x - mInitialTouchX;
mDy = y - mInitialTouchY;
if ((directionFlags & LEFT) == 0) {
mDx = Math.max(0, mDx);
}
if ((directionFlags & RIGHT) == 0) {
mDx = Math.min(0, mDx);
}
if ((directionFlags & UP) == 0) {
mDy = Math.max(0, mDy);
}
if ((directionFlags & DOWN) == 0) {
mDy = Math.min(0, mDy);
}
}
}
四、博客资源
博客资源 :
- GitHub 地址 : https://github.com/han1202012/001_RecyclerView