Android(安卓)View的点击事件分发机制
16lz
2021-01-25
三个重要的方法:
- dispatchTouchEvent(MotionEvent ev):用来进行事件的分发
- onInterceptTouchEvent(MotionEvent ev):用来进行事件的拦截,在dispatchTouchEvent()中调用,需要注意的是View没有提供该方法
- onTouchEvent(MotionEvent ev):用来处理点击事件,在dispatchTouchEvent()方法中进行调用
语言描述:
当父组件接收到点击事件后,进行事件分发,自己先拦截到事件,如果自己可以处理此事件,则自己消费事件;若无法消费事件,则交给子组件来处理。
源代码分析:
ViewGroup中的dispatchTouchEvent(MotionEvent ev)入手
public boolean dispatchTouchEvent(MotionEvent ev) { //..省略 // Check for interception. final boolean intercepted; if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) { final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; if (!disallowIntercept) { //拦截事件,ViewGroup的onInterceptTouchEvent直接返回了false,不拦截事件 intercepted = onInterceptTouchEvent(ev); ev.setAction(action); // restore action in case it was changed } else { intercepted = false; } } else { // There are no touch targets and this action is not an initial down // so this view group continues to intercept touches. intercepted = true; } //..省略 final View[] children = mChildren;//遍历所有子组件 for (int i = childrenCount - 1; i >= 0; i--) { final int childIndex = getAndVerifyPreorderedIndex( childrenCount, i, customOrder); final View child = getAndVerifyPreorderedView( preorderedList, children, childIndex); // if there is a view that has accessibility focus we want it // to get the event first and if not handled we will perform a // normal dispatch. we may do a double iteration but this is // safer given the timeframe. if (childWithAccessibilityFocus != null) { if (childWithAccessibilityFocus != child) { continue; } childWithAccessibilityFocus = null; i = childrenCount - 1; }//如果不能接收事件,直接continue if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) { ev.setTargetAccessibilityFocus(false); continue; } newTouchTarget = getTouchTarget(child); if (newTouchTarget != null) { // Child is already receiving touch within its bounds. // Give it the new pointer in addition to the ones it is handling. newTouchTarget.pointerIdBits |= idBitsToAssign; break; } resetCancelNextUpFlag(child);//分发事件到子组件 if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { // Child wants to receive touch within its bounds. mLastTouchDownTime = ev.getDownTime(); if (preorderedList != null) { // childIndex points into presorted list, find original index for (int j = 0; j < childrenCount; j++) { if (children[childIndex] == mChildren[j]) { mLastTouchDownIndex = j; break; } } } else { mLastTouchDownIndex = childIndex; } mLastTouchDownX = ev.getX(); mLastTouchDownY = ev.getY(); newTouchTarget = addTouchTarget(child, idBitsToAssign); alreadyDispatchedToNewTouchTarget = true; break; } // The accessibility focus didn't handle the event, so clear // the flag and do a normal dispatch to all children. ev.setTargetAccessibilityFocus(false); } }
查看dispatchTransformedTouchEvent方法
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits) { final boolean handled; // Canceling motions is a special case. We don't need to perform any transformations // or filtering. The important part is the action, not the contents. final int oldAction = event.getAction(); if (cancel || oldAction == MotionEvent.ACTION_CANCEL) { event.setAction(MotionEvent.ACTION_CANCEL); if (child == null) {//调用了View 的 dispatchTouchEvent(event) handled = super.dispatchTouchEvent(event); } else {//子组件的事件分发 handled = child.dispatchTouchEvent(event); } event.setAction(oldAction); return handled; }//..省略}
这里为了方便理解,统一查看View的dispatchTouchEvent方法,其他组件可自行查看
public boolean dispatchTouchEvent(MotionEvent event) {//..省略 boolean result = false;//..省略 if (onFilterTouchEventForSecurity(event)) { if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) { result = true; } //noinspection SimplifiableIfStatement ListenerInfo li = mListenerInfo; if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { result = true; }//消费事件 if (!result && onTouchEvent(event)) { result = true; } }//..省略 return result; }
View 的 onTouchEvent方法
public boolean onTouchEvent(MotionEvent event) { if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) { switch (action) { case MotionEvent.ACTION_UP: //..省略 if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) { // This is a tap, so remove the longpress check removeLongPressCallback(); // Only perform take click actions if we were in the pressed state if (!focusTaken) { // Use a Runnable and post this rather than calling // performClick directly. This lets other visual state // of the view update before click actions start. if (mPerformClick == null) { mPerformClick = new PerformClick(); } if (!post(mPerformClick)) { //点击事件 performClickInternal(); } } } //..省略 } return true; } //..省略 }
performClickInternal方法中调用了performClick方法:
public boolean performClick() { // We still need to call this method to handle the cases where performClick() was called // externally, instead of through performClickInternal() notifyAutofillManagerOnClick(); final boolean result; final ListenerInfo li = mListenerInfo; if (li != null && li.mOnClickListener != null) { playSoundEffect(SoundEffectConstants.CLICK);//调用了OnClickListener.onClick(View view) 消费了本次事件 li.mOnClickListener.onClick(this); result = true; } else { result = false; } sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); notifyEnterOrExitForAutoFillIfNeeded(true); return result; }
至此消费了事件,返回了true
更多相关文章
- Android(安卓)TextView跑马灯效果
- Android之选项菜单和上下文菜单解析
- Android中如何判断Intent是否存在
- android Fragment相关问题
- Android(安卓)模拟MotionEvent事件 触发输入法
- androidsetClickable不起作用没…
- Android利用soap WSDL与Webservice通信
- 【Android(安卓)Demo】图片之网格视图(GridView)
- Android之AsyncTask源码分析(第五篇:execute方法只能执行一次的原