带你根据源码了解View的事件触发流程,主要讲解为什么子View返回true,ViewGroup就无法接收到事件的过程
轉(zhuǎn)載請標(biāo)明出處!http://blog.csdn.net/sahadev_/article/details/23839039?,當(dāng)然一般也沒人轉(zhuǎn)載。。。
一直想徹底了解View的事件分發(fā)過程,在網(wǎng)上也看了很多大神的博客,但是總有一些東西不是很明白,于是自己就根據(jù)源碼畫了一個流程草圖,思路總算是清晰了。
文章分為View和ViewGroup兩部分介紹,首先來講講View的事件分發(fā):
打開View的源碼,順著onTouch方法向上找到dispatchPointerEvent(MotionEvent event)的這個方法,在View中再沒有其它地方調(diào)用它,估計(jì)事件就是從這里傳過來的吧。
來貼一下這個方法的源碼:
public final boolean dispatchPointerEvent(MotionEvent event) {if (event.isTouchEvent()) {return dispatchTouchEvent(event);} else {return dispatchGenericMotionEvent(event);}}看到在這個方法中調(diào)用了dispatchTouchEvent的這個方法,dispatchTouchEven()暫時沒找到它的相關(guān)說明,權(quán)當(dāng)它返回了true,進(jìn)入dispatchTouchEvent方法: public boolean dispatchTouchEvent(MotionEvent event) {if (mInputEventConsistencyVerifier != null) {mInputEventConsistencyVerifier.onTouchEvent(event, 0);}if (onFilterTouchEventForSecurity(event)) {//noinspection SimplifiableIfStatementListenerInfo li = mListenerInfo;if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED&& li.mOnTouchListener.onTouch(this, event)) {return true;}if (onTouchEvent(event)) {return true;}}if (mInputEventConsistencyVerifier != null) {mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);}return false;}可以看到在這個方法中調(diào)用了onTouchEvent(Event),所以可知onTouchEvent(event)是由dispatchTouchEvent觸發(fā)的。如果有人對onFilterTouchEventForSecurity這方法有疑問,API中是這么解釋的:True if the event should be dispatched, false if the event should be dropped.這句話就不用翻譯了吧,相信大家都懂。
如果onTouchEvent返回了true,那么dispatchTouchEvent的返回值也為true.?對于dispatchTouchEvent的返回值說明是這么解釋的:True if the event was handled by the view, false otherwise. 如果返回true,說明這個事件就被當(dāng)前的這個View消費(fèi)掉了,這里的返回值大家一定要清楚,一會在ViewGroup的說明中需要對這塊很熟悉。
接下來說一說ViewGroup的事件分發(fā)方法:
ViewGroup的事件處理相對View相對有些復(fù)雜,ViewGroup繼承于View,在View中dispatchTouchEvent是由dispatchPointerEvent觸發(fā)的,所以ViewGroup也是由dispatchPointerEvent觸發(fā)的,這里不重要,接著看ViewGroup的dispatchTouchEvent的源代碼:
@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {if (mInputEventConsistencyVerifier != null) {mInputEventConsistencyVerifier.onTouchEvent(ev, 1);}boolean handled = false;if (onFilterTouchEventForSecurity(ev)) {final int action = ev.getAction();final int actionMasked = action & MotionEvent.ACTION_MASK;// Handle an initial down.if (actionMasked == MotionEvent.ACTION_DOWN) {// Throw away all previous state when starting a new touch gesture.// The framework may have dropped the up or cancel event for the previous gesture// due to an app switch, ANR, or some other state change.cancelAndClearTouchTargets(ev);resetTouchState();}// Check for interception.final boolean intercepted;if (actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget != null) {final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;if (!disallowIntercept) {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;}// Check for cancelation.final boolean canceled = resetCancelNextUpFlag(this)|| actionMasked == MotionEvent.ACTION_CANCEL;// Update list of touch targets for pointer down, if needed.final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;TouchTarget newTouchTarget = null;boolean alreadyDispatchedToNewTouchTarget = false;if (!canceled && !intercepted) {if (actionMasked == MotionEvent.ACTION_DOWN|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {final int actionIndex = ev.getActionIndex(); // always 0 for downfinal int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex): TouchTarget.ALL_POINTER_IDS;// Clean up earlier touch targets for this pointer id in case they// have become out of sync.removePointersFromTouchTargets(idBitsToAssign);final int childrenCount = mChildrenCount;if (newTouchTarget == null && childrenCount != 0) {final float x = ev.getX(actionIndex);final float y = ev.getY(actionIndex);// Find a child that can receive the event.// Scan children from front to back.final View[] children = mChildren;final boolean customOrder = isChildrenDrawingOrderEnabled();for (int i = childrenCount - 1; i >= 0; i--) {final int childIndex = customOrder ?getChildDrawingOrder(childrenCount, i) : i;final View child = children[childIndex];if (!canViewReceivePointerEvents(child)|| !isTransformedTouchPointInView(x, y, child, null)) {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();mLastTouchDownIndex = childIndex;mLastTouchDownX = ev.getX();mLastTouchDownY = ev.getY();newTouchTarget = addTouchTarget(child, idBitsToAssign);alreadyDispatchedToNewTouchTarget = true;break;}}}if (newTouchTarget == null && mFirstTouchTarget != null) {// Did not find a child to receive the event.// Assign the pointer to the least recently added target.newTouchTarget = mFirstTouchTarget;while (newTouchTarget.next != null) {newTouchTarget = newTouchTarget.next;}newTouchTarget.pointerIdBits |= idBitsToAssign;}}}// Dispatch to touch targets.if (mFirstTouchTarget == null) {// No touch targets so treat this as an ordinary view.handled = dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS);} else {// Dispatch to touch targets, excluding the new touch target if we already// dispatched to it. Cancel touch targets if necessary.TouchTarget predecessor = null;TouchTarget target = mFirstTouchTarget;while (target != null) {final TouchTarget next = target.next;if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {handled = true;} else {final boolean cancelChild = resetCancelNextUpFlag(target.child)|| intercepted;if (dispatchTransformedTouchEvent(ev, cancelChild,target.child, target.pointerIdBits)) {handled = true;}if (cancelChild) {if (predecessor == null) {mFirstTouchTarget = next;} else {predecessor.next = next;}target.recycle();target = next;continue;}}predecessor = target;target = next;}}// Update list of touch targets for pointer up or cancel, if needed.if (canceled|| actionMasked == MotionEvent.ACTION_UP|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {resetTouchState();} else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {final int actionIndex = ev.getActionIndex();final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);removePointersFromTouchTargets(idBitsToRemove);}}if (!handled && mInputEventConsistencyVerifier != null) {mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);}return handled;}ViewGroup的dispatchTouchEvent的方法有些長,在代碼中可以看到,ViewGroup將View的該方法徹底重寫了,沒有很明顯的看到super.dispatchTouchEvent的字樣,也就是說還沒有著急把事件交給View去處理。
前幾行和View都差不錯,往下看
if (actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget != null) {final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;if (!disallowIntercept) {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;}在第5行中調(diào)用了onInterceptTouchEvent(ev);該方法表示當(dāng)按下事件觸發(fā)的時候是否要攔截本次事件,這個方法的源碼是這樣的: public boolean onInterceptTouchEvent(MotionEvent ev) {return false;}如果不對它進(jìn)行重寫的話,它總是返回false,也就是說intercepted的值恒為false,接著往下看,看到?if (!canceled && !intercepted) {這一行的時候,由于intercepted為false,所以事件可以進(jìn)入if內(nèi)執(zhí)行,接著往下看: for (int i = childrenCount - 1; i >= 0; i--) {final int childIndex = customOrder ?getChildDrawingOrder(childrenCount, i) : i;final View child = children[childIndex];if (!canViewReceivePointerEvents(child)|| !isTransformedTouchPointInView(x, y, child, null)) {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();mLastTouchDownIndex = childIndex;mLastTouchDownX = ev.getX();mLastTouchDownY = ev.getY();newTouchTarget = addTouchTarget(child, idBitsToAssign);alreadyDispatchedToNewTouchTarget = true;break;}}看到這段代碼中dispatchTransformedTouchEvent的這個方法開始對ViewGroup的子View進(jìn)行事件傳遞了: /*** Transforms a motion event into the coordinate space of a particular child view,* filters out irrelevant pointer ids, and overrides its action if necessary.* If child is null, assumes the MotionEvent will be sent to this ViewGroup instead.*/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) {handled = super.dispatchTouchEvent(event);} else {handled = child.dispatchTouchEvent(event);}event.setAction(oldAction);return handled;}// Calculate the number of pointers to deliver.final int oldPointerIdBits = event.getPointerIdBits();final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;// If for some reason we ended up in an inconsistent state where it looks like we// might produce a motion event with no pointers in it, then drop the event.if (newPointerIdBits == 0) {return false;}// If the number of pointers is the same and we don't need to perform any fancy// irreversible transformations, then we can reuse the motion event for this// dispatch as long as we are careful to revert any changes we make.// Otherwise we need to make a copy.final MotionEvent transformedEvent;if (newPointerIdBits == oldPointerIdBits) {if (child == null || child.hasIdentityMatrix()) {if (child == null) {handled = super.dispatchTouchEvent(event);} else {final float offsetX = mScrollX - child.mLeft;final float offsetY = mScrollY - child.mTop;event.offsetLocation(offsetX, offsetY);handled = child.dispatchTouchEvent(event);event.offsetLocation(-offsetX, -offsetY);}return handled;}transformedEvent = MotionEvent.obtain(event);} else {transformedEvent = event.split(newPointerIdBits);}// Perform any necessary transformations and dispatch.if (child == null) {handled = super.dispatchTouchEvent(transformedEvent);} else {final float offsetX = mScrollX - child.mLeft;final float offsetY = mScrollY - child.mTop;transformedEvent.offsetLocation(offsetX, offsetY);if (! child.hasIdentityMatrix()) {transformedEvent.transform(child.getInverseMatrix());}handled = child.dispatchTouchEvent(transformedEvent);}// Done.transformedEvent.recycle();return handled;}該方法的最終返回值是布爾值,意思為是否被處理了,請看這里: // Perform any necessary transformations and dispatch.if (child == null) {handled = super.dispatchTouchEvent(transformedEvent);} else {final float offsetX = mScrollX - child.mLeft;final float offsetY = mScrollY - child.mTop;transformedEvent.offsetLocation(offsetX, offsetY);if (! child.hasIdentityMatrix()) {transformedEvent.transform(child.getInverseMatrix());}handled = child.dispatchTouchEvent(transformedEvent);}這里開始對子View進(jìn)行事件傳遞,調(diào)用子View的dispatchTouchEvent的方法,如果被子View消費(fèi)掉了本次事件,dispatchTransformedTouchEvent就返回true,再回到那個循環(huán): if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {// Child wants to receive touch within its bounds.mLastTouchDownTime = ev.getDownTime();mLastTouchDownIndex = childIndex;mLastTouchDownX = ev.getX();mLastTouchDownY = ev.getY();newTouchTarget = addTouchTarget(child, idBitsToAssign);alreadyDispatchedToNewTouchTarget = true;break;}請注意這個方法:
newTouchTarget = addTouchTarget(child, idBitsToAssign);這個方法中的代碼是這樣的: private TouchTarget addTouchTarget(View child, int pointerIdBits) {TouchTarget target = TouchTarget.obtain(child, pointerIdBits);target.next = mFirstTouchTarget;mFirstTouchTarget = target;return target;}注意將mFirstTouchTarget賦了值,不再是null,先記住這里,待會有用。
好了,子View循環(huán)完成之后,代碼會執(zhí)行到這里:
// Dispatch to touch targets.if (mFirstTouchTarget == null) {// No touch targets so treat this as an ordinary view.handled = dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS);} else {// Dispatch to touch targets, excluding the new touch target if we already// dispatched to it. Cancel touch targets if necessary.TouchTarget predecessor = null;TouchTarget target = mFirstTouchTarget;while (target != null) {final TouchTarget next = target.next;if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {handled = true;} else {final boolean cancelChild = resetCancelNextUpFlag(target.child)|| intercepted;if (dispatchTransformedTouchEvent(ev, cancelChild,target.child, target.pointerIdBits)) {handled = true;}if (cancelChild) {if (predecessor == null) {mFirstTouchTarget = next;} else {predecessor.next = next;}target.recycle();target = next;continue;}}predecessor = target;target = next;}}注意到剛剛mFirstTouchTarget被賦了值,不再是null,不為null,就不會執(zhí)行if中的語句,if中的語句都做了些什么呢? handled = dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS);注意第3個參數(shù)是Null,咱們再返回到dispatchTransformedTouchEvent中(剛才已經(jīng)貼過方法內(nèi)的代碼):該方法內(nèi)有3處super.dispatchTouchEvent(event);因?yàn)閯偛舖FirstTouchTarget的值不為null,所以沒有進(jìn)入if,所以ViewGroup沒法去調(diào)用super.dispatchTouchEvent(event);所以當(dāng)子View把事件消費(fèi)完成之后,ViewGroup就無法接收到onTouchEvent事件,這就是為什么子View返回true,ViewGroup就無法處理的原因。
事件傳遞到這里也就講完了,不知道大家有沒有聽明白,如果有什么疑問可以在下面留言,歡迎轉(zhuǎn)載!
總結(jié)
以上是生活随笔為你收集整理的带你根据源码了解View的事件触发流程,主要讲解为什么子View返回true,ViewGroup就无法接收到事件的过程的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Android官方开发文档Trainin
- 下一篇: Jedis工具类