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 事件分发】ItemTouchHelper 源码分析 ( OnItemTouchListener 事件监听器源码分析 二 )


文章目录

  • Android 事件分发 系列文章目录
  • 一、onTouchEvent 事件消费源码分析
    • 1、onTouchEvent 方法
    • 2、moveIfNecessary 方法
  • 二、ItemTouchHelper 涉及到的本博客相关源码
  • 三、博客资源

一、onTouchEvent 事件消费源码分析


在上一篇博客 【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 positionif (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()) < viewHolder.itemView.getHeight() * threshold&& Math.abs(x - viewHolder.itemView.getLeft())< viewHolder.itemView.getWidth() * threshold) {return;}

继续向后执行 , 说明拖动动作判定成功 , 执行拖动响应方法 , 即开发者自定义的 Callback 中的 onMove 方法 ;

     public boolean onMove(@NonNull RecyclerView recyclerView,@NonNull RecyclerView.ViewHolder viewHolder,@NonNull RecyclerView.ViewHolder target)

先获取拖动的起始位置和目标位置 , 然后传入 onMove 方法 , 触发回调 ;

     // 获取被拖动的目标位置final int toPosition = target.getAdapterPosition();// 获取被拖动的起始位置 final int fromPosition = viewHolder.getAdapterPosition();// 拖动判定成功 , 调用开发者实现的 Callback 的 // public boolean onMove(@NonNull RecyclerView recyclerView,//                  @NonNull RecyclerView.ViewHolder viewHolder,//                  @NonNull RecyclerView.ViewHolder target) 方法 // 一般是拖动交换数据 if (mCallback.onMove(mRecyclerView, viewHolder, target)) {// keep target visiblemCallback.onMoved(mRecyclerView, viewHolder, fromPosition,target, toPosition, x, y);}

二、ItemTouchHelper 涉及到的本博客相关源码


public class ItemTouchHelper extends RecyclerView.ItemDecorationimplements 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() {// 拦截触摸事件 , 处理拦截机制 @Overridepublic 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;}// 处理最终的事件消费 // 只处理手指滑动操作 MotionEvent.ACTION_MOVE @Overridepublic void onTouchEvent(@NonNull RecyclerView recyclerView, @NonNull MotionEvent event) {mGestureDetector.onTouchEvent(event);if (DEBUG) {Log.d(TAG,"on touch: x:" + mInitialTouchX + ",y:" + mInitialTouchY + ", :" + event);}if (mVelocityTracker != null) {mVelocityTracker.addMovement(event);}if (mActivePointerId == ACTIVE_POINTER_ID_NONE) {return;}final int action = event.getActionMasked();final int activePointerIndex = event.findPointerIndex(mActivePointerId);if (activePointerIndex >= 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 positionif (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 throughcase 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;}}}@Overridepublic 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 <= 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 animationsendRecoverAnimation(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 tofinal 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) {@Overridepublic void onAnimationEnd(Animator animation) {super.onAnimationEnd(animation);if (this.mOverridden) {return;}if (swipeDir <= 0) {// this is a drag or failed swipe. recover immediatelymCallback.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 timeif (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 movedmDx = 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()) < viewHolder.itemView.getHeight() * threshold&& Math.abs(x - viewHolder.itemView.getLeft())< viewHolder.itemView.getWidth() * threshold) {return;}// 执行到此处说明拖动判定成功 List<ViewHolder> swapTargets = findSwapTargets(viewHolder);if (swapTargets.size() == 0) {return;}// may swap.ViewHolder target = mCallback.chooseDropTarget(viewHolder, swapTargets, x, y);if (target == null) {mSwapTargets.clear();mDistances.clear();return;}// 获取被拖动的目标位置final int toPosition = target.getAdapterPosition();// 获取被拖动的起始位置 final int fromPosition = viewHolder.getAdapterPosition();// 拖动判定成功 , 调用开发者实现的 Callback 的 // public boolean onMove(@NonNull RecyclerView recyclerView,//                  @NonNull RecyclerView.ViewHolder viewHolder,//                  @NonNull RecyclerView.ViewHolder target) 方法 // 一般是拖动交换数据 if (mCallback.onMove(mRecyclerView, viewHolder, target)) {// keep target visiblemCallback.onMoved(mRecyclerView, viewHolder, fromPosition,target, toPosition, x, y);}}
}

三、博客资源


博客资源 :

  • GitHub 地址 : https://github.com/han1202012/001_RecyclerView

【Android 事件分发】ItemTouchHelper 源码分析 ( OnItemTouchListener 事件监听器源码分析 二 )相关推荐

  1. 【Android 事件分发】ItemTouchHelper 源码分析 ( OnItemTouchListener 事件监听器源码分析 )

    Android 事件分发 系列文章目录 [Android 事件分发]事件分发源码分析 ( 驱动层通过中断传递事件 | WindowManagerService 向 View 层传递事件 ) [Andr ...

  2. 【转】Android事件分发机制完全解析,带你从源码的角度彻底理解(下)

    转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/9153761 记得在前面的文章中,我带大家一起从源码的角度分析了Android中Vi ...

  3. Android事件分发机制完全解析,带你从源码的角度彻底理解(上)

    <div id="container">         <div id="header">     <div class=&qu ...

  4. 【Android 事件分发】MotionEvent.ACTION_DOWN 按下事件分发流程( Activity | ViewGroup | View )

    Android 事件分发 系列文章目录 [Android 事件分发]事件分发源码分析 ( 驱动层通过中断传递事件 | WindowManagerService 向 View 层传递事件 ) [Andr ...

  5. Androi事件分发( 二),解决事件冲突

    通过<Android事件分发(一)>我们了解了Android的事件分发机制,不熟悉的,可以回头再去看一遍. 有了这方面的知识基础,我们来解决实际研发的过程中,老生常谈的事件冲突问题. 解决 ...

  6. 【朝花夕拾】Android自定义View篇之(六)Android事件分发机制(中)从源码分析事件分发机制...

    前言 转载请注明,转自[https://www.cnblogs.com/andy-songwei/p/11039252.html]谢谢! 在上一篇文章[[朝花夕拾]Android自定义View篇之(五 ...

  7. 【朝花夕拾】Android自定义View篇之(六)Android事件分发机制(中)从源码分析事件分发逻辑及经常遇到的一些“诡异”现象

    前言 转载请注明,转自[https://www.cnblogs.com/andy-songwei/p/11039252.html]谢谢! 在上一篇文章[[朝花夕拾]Android自定义View篇之(五 ...

  8. Android Touch事件分发(源码分析)

    Android一文让你轻松搞定Touch事件分发 源码分析 下面,咱们一起通过源码,全面解析事件分发机制,即按顺序讲解: Activity事件分发机制 ViewGroup事件分发机制 View事件分发 ...

  9. View事件分发机制(源码分析篇)

    01.Android中事件分发顺序 1.1 事件分发的对象是谁 事件分发的对象是事件.注意,事件分发是向下传递的,也就是父到子的顺序. 当用户触摸屏幕时(View或ViewGroup派生的控件),将产 ...

最新文章

  1. alexa技能个数_如何在您的技能中使用Alexa演示语言
  2. 微软洪小文:AI将成为人类未来最好的左脑
  3. SpringBoot注入数据的方式
  4. 2432功率计使用说明_为什么说功率计是自行车开挂神器?
  5. 有STC制作一个手持微型示波器
  6. 从DataSet导出Txt
  7. AIOps-一位研发工程师的学习笔记
  8. oracle 数据库,用户管理以及表空间等相关基础操作
  9. tomcat(10)安全性
  10. 在plsql里面怎么去掉空行_PLSQL基本操作手册.doc
  11. vissim免修改时间工具_App闪退怎么办?免越狱如何安装未签名的App?
  12. 基于CUDA的粒子系统的实现
  13. 第六篇 | 解决linux环境中mysql默认端口3306无法连接问题
  14. 汇编语言程序设计思维导图
  15. 锂电池注液工艺视觉定位方案指南
  16. HTML邮件 兼容问题
  17. PYTHON利用REMOVEBG库实现抠图
  18. 在单端输入应用中连接差分放大器
  19. 计算机屏幕闪烁黑屏,台式机电脑。显示屏指示灯一直闪烁,屏幕黑屏。。...-显示器电源灯闪黑屏...
  20. Android 组件化代码中心化问题之.api化方案

热门文章

  1. iptables 过滤条件(Matches)
  2. leetcode 73 矩阵置零 Python
  3. 开源自己写的Library到github,让别人或自己的项目依赖
  4. SQL Server会话KILL不掉,一直处于KILLED /ROLLBACK状态情形浅析
  5. RabbitMQ 官方NET教程(二)【工作队列】
  6. day3_python学习笔记_chapter5_数字
  7. jQuery EasyUI API 中文文档 - 数字框(NumberBox)
  8. Delphi 2010 新增功能之: IOUtils 单元(4): TDirectory.GetDirectories
  9. WF单元测试系列3:测试Activity的行为
  10. BZOJ2339: [HNOI2011]卡农(dp 容斥)