【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 事件分发】ItemTouchHelper 源码分析 ( OnItemTouchListener 事件监听器源码分析 二 )
- Android 事件分发 系列文章目录
- 一、onTouchEvent 事件消费源码分析
- 1、onTouchEvent 方法
- 2、moveIfNecessary 方法
- 二、ItemTouchHelper 涉及到的本博客相关源码
- 三、博客资源
在上一篇博客 【Android 事件分发】ItemTouchHelper 源码分析 ( OnItemTouchListener 事件监听器源码分析 ) 主要分析了 给 RecyclerView 设置的 OnItemTouchListener 监听器的 onInterceptTouchEvent 触摸事件拦截方法 , 本篇博客中主要分析另外一个 触摸事件消费方法 onTouchEvent ;
在 onTouchEvent 事件消费 中 , 只处理 MotionEvent.ACTION_MOVE 事件 , 不处理其它事件 ;
1、onTouchEvent 方法首先要获取操作的条目组件 ,
ViewHolder viewHolder = mSelected;
其中 mSelected 是在第一次按下时进行的赋值 , 有了 mSelected 值后 , 开始处理滑动事件 ;
如果没有获取到 mSelected , 则直接返回 ;
if (viewHolder == null) {
return;
}
如果当前处于拖动事件 MotionEvent.ACTION_MOVE , 则进行拖动事件处理 ;
拖动事件的核心是 moveIfNecessary 方法 , 该方法是处理滑动事件的核心方法
switch (action) {
case MotionEvent.ACTION_MOVE: {
// 检查该操作是否在拖动
// Find the index of the active pointer and fetch its position
if (activePointerIndex >= 0) {
// 记录修改偏移值
updateDxDy(event, mSelectedFlags, activePointerIndex);
// 处理拖动事件
moveIfNecessary(viewHolder);
mRecyclerView.removeCallbacks(mScrollRunnable);
mScrollRunnable.run();
mRecyclerView.invalidate();
}
break;
}
2、moveIfNecessary 方法
moveIfNecessary 方法中主要进行拖动事件判定 , 一般是拖动条目组件进行重新排序 ;
先获取开发者自定义的 Callback 中的 public float getMoveThreshold(@NonNull RecyclerView.ViewHolder viewHolder)
方法返回值 , 如果开发者没有设置 , 就使用默认值 ;
该值的作用是 设置 拖动幅度 , 组件在宽度 / 高度 上移动超过该比例 , 就认为拖动触发, 执行拖动相关操作 ;
// 该方法就是 开发者 自定义 Callback 中的
// public float getMoveThreshold(@NonNull RecyclerView.ViewHolder viewHolder)
// 方法的作用是设置 拖动幅度
// 组件在宽度 / 高度 上移动超过该比例 , 就认为拖动触发, 执行拖动相关操作
// 拖动多少系数 , 才算完成 拖动操作
final float threshold = mCallback.getMoveThreshold(viewHolder);
final int x = (int) (mSelectedStartX + mDx);
final int y = (int) (mSelectedStartY + mDy);
获取到拖动系数后 , 使用了 threshold 系数 乘以 水平 / 垂直 方向上的条目组件 宽度 / 高度 ;
- 如果拖动比例超过在 水平 / 垂直 方向上的条目组件 宽度 / 高度 乘以 threshold 的值 , 则拖动判定成功 , 执行响应的方法 ;
- 如果拖动比例没有超过该值 , 说明没有触发拖动操作 , 直接返回 ;
// 在该判断中 , 使用了 threshold 系数 乘以 水平 / 垂直 方向上的条目组件宽度 ;
// 如果拖动比例超过在 水平 / 垂直 方向上的条目组件 宽度 / 高度 乘以 threshold 的值
// 则拖动判定成功 , 执行响应的方法
// 如果拖动比例没有超过该值 , 说明没有触发拖动操作 , 直接返回
if (Math.abs(y - viewHolder.itemView.getTop()) = 0) {
checkSelectForSwipe(action, event, activePointerIndex);
}
// 按下第一次后 , mSelected 便进行赋值
// 有了 mSelected 值后 , 开始在滑动中处理
ViewHolder viewHolder = mSelected;
if (viewHolder == null) {
return;
}
switch (action) {
case MotionEvent.ACTION_MOVE: {
// 检查该操作是否在拖动
// Find the index of the active pointer and fetch its position
if (activePointerIndex >= 0) {
// 记录修改偏移值
updateDxDy(event, mSelectedFlags, activePointerIndex);
// 处理拖动事件
moveIfNecessary(viewHolder);
mRecyclerView.removeCallbacks(mScrollRunnable);
mScrollRunnable.run();
mRecyclerView.invalidate();
}
break;
}
case MotionEvent.ACTION_CANCEL:
// 取消操作 , 没有实质的内容
if (mVelocityTracker != null) {
mVelocityTracker.clear();
}
// fall through
case MotionEvent.ACTION_UP:
// 抬起操作
select(null, ACTION_STATE_IDLE);
mActivePointerId = ACTIVE_POINTER_ID_NONE;
break;
case MotionEvent.ACTION_POINTER_UP: {
final int pointerIndex = event.getActionIndex();
final int pointerId = event.getPointerId(pointerIndex);
if (pointerId == mActivePointerId) {
// This was our active pointer going up. Choose a new
// active pointer and adjust accordingly.
final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
mActivePointerId = event.getPointerId(newPointerIndex);
updateDxDy(event, mSelectedFlags, pointerIndex);
}
break;
}
}
}
@Override
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
if (!disallowIntercept) {
return;
}
select(null, ACTION_STATE_IDLE);
}
};
// 该方法作用 :
// 用户按下 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 = top
&& y (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);
}
}
/**
* Checks if we should swap w/ another view holder.
* 拖动事件判定 , 一般是拖动交换条目组件
*/
@SuppressWarnings("WeakerAccess") /* synthetic access */
void moveIfNecessary(ViewHolder viewHolder) {
if (mRecyclerView.isLayoutRequested()) {
return;
}
if (mActionState != ACTION_STATE_DRAG) {
return;
}
// 该方法就是 开发者 自定义 Callback 中的
// public float getMoveThreshold(@NonNull RecyclerView.ViewHolder viewHolder)
// 方法的作用是设置 拖动幅度
// 组件在宽度 / 高度 上移动超过该比例 , 就认为拖动触发, 执行拖动相关操作
// 拖动多少系数 , 才算完成 拖动操作
final float threshold = mCallback.getMoveThreshold(viewHolder);
final int x = (int) (mSelectedStartX + mDx);
final int y = (int) (mSelectedStartY + mDy);
// 在该判断中 , 使用了 threshold 系数 乘以 水平 / 垂直 方向上的条目组件宽度 ;
// 如果拖动比例超过在 水平 / 垂直 方向上的条目组件 宽度 / 高度 乘以 threshold 的值
// 则拖动判定成功 , 执行响应的方法
// 如果拖动比例没有超过该值 , 说明没有触发拖动操作 , 直接返回
if (Math.abs(y - viewHolder.itemView.getTop())
关注
打赏
- 【Android Gradle 插件】Gradle 自定义 Plugin 插件 ③ ( 自定义插件作用 | Android Gradle 插件的扩展 | 自定义 Extension 扩展 )
- 【Android Gradle 插件】Gradle 构建生命周期 ③ ( BuildListener 构建监听器 | TaskExecutionGraphListener 任务执行图监听器 )
- 【Android Gradle 插件】Gradle 构建生命周期 ② ( Gradle 类的添加构建生命周期监听器函数 | Gradle#addListener 函数 )
- 【Android Gradle 插件】Gradle 构建生命周期 ① ( 分析构建脚本 | 执行初始化配置 | 执行 Gradle 任务 | Project#beforeEvaluate 函数 )
- 【Android Gradle 插件】自定义 Gradle 任务 ⑨ ( 控制 Gradle 执行任务顺序 | Task#finalizedBy 函数 | 控制 Gradle 执行任务顺序示例分析 )
- 【Android Gradle 插件】自定义 Gradle 任务 ⑧ ( 控制 Gradle 执行任务顺序 | Task#shouldRunAfter 函数 | 三个函数使用场景对比 )
- 【Android Gradle 插件】自定义 Gradle 任务 ⑦ ( 控制 Gradle 执行任务顺序 | Task#dependsOn 函数 | Task#mustRunAfter 函数 )
- 【数学分析】集合 ① ( 集合概念 | 集合表示 | 常用的数集合 | 集合的表示 )
- 【数学分析】学科简介 ( 初等数学缺陷 | 微分与积分 | 学习数学分析的目的 | 数学分析与高等数学对比 )
- 【Android Gradle 插件】自定义 Gradle 任务 ③ ( Gradle 自定义任务创建方法 Project#task 函数 | Task#doFirst 函数用法 )