ViewGroup的Touch事件分发(源码分析)
Android中Touch事件的分發(fā)又分為View和ViewGroup的事件分發(fā),View的touch事件分發(fā)相對比較簡單,可參考
View的Touch事件分發(fā)(一.初步了解) View的Touch事件分發(fā)(一.初步了解)_yinianzhijian99的專欄-CSDN博客
View的Touch事件分發(fā)(二.源碼分析) View的Touch事件分發(fā)(二.源碼分析)_yinianzhijian99的專欄-CSDN博客
現(xiàn)在開始分析ViewGroup的Touch事件分發(fā)
現(xiàn)象:給一個控件設(shè)置OnTouchListener和OnClickListener,點擊控件,會有以下結(jié)果:
1.正常情況下
手指按下
DOWN?ViewGroup.dispatchTouchEvent?->?ViewGroup.onInterceptTouchEvent?->
View.dispatchTouchEvent?->?View.onTouch?->?View.onTouchEvent
手指移動
MOVE?ViewGroup.dispatchTouchEvent?->?ViewGroup.onInterceptTouchEvent?->
View.dispatchTouchEvent?->?View.onTouch?->?View.onTouchEvent
手指抬起
UP?ViewGroup.dispatchTouchEvent?->?ViewGroup.onInterceptTouchEvent?->
View.onTouch?->?View.onTouchEvent?->?View.onclick
2.onClick沒有,理解為沒有消費事件
ViewGroup.dispatchTouchEvent?->?ViewGroup.onInterceptTouchEvent?-
>?View.dispatchTouchEvent?->?View.onTouch?->?View?onTouchEvent?-
>?ViewGroup.onTouchEvent
3.在View的onTouchEvent()方法里面返回true的情況下
手指按下
DOWN?ViewGroup.dispatchTouchEvent?->?ViewGroup.onInterceptTouchEvent?->
View.dispatchTouchEvent?->?View.onTouch?->?View.onTouchEvent
手指移動
MOVE?ViewGroup.dispatchTouchEvent?->?ViewGroup.onInterceptTouchEvent?->
View.dispatchTouchEvent?->?View.onTouch?->?View.onTouchEvent
手指抬起
UP?ViewGroup.dispatchTouchEvent?->?ViewGroup.onInterceptTouchEvent?->
View.onTouch?->?View.onTouchEvent
4.在ViewGroup的onInterceptTouchEvent()方法里面返回true的情況下
ViewGroup.dispatchTouchEvent?->?ViewGroup.onInterceptTouchEvent?->?ViewGroup.onTouchEvent
源碼分析:
ViewGroup的Touch事件是從Activity的dispatchTouchEvent()方法開始的
Activity的dispatchTouchEvent()方法如下:
public boolean dispatchTouchEvent(MotionEvent ev) {if (ev.getAction() == MotionEvent.ACTION_DOWN) {onUserInteraction();}if (getWindow().superDispatchTouchEvent(ev)) {return true;}return onTouchEvent(ev);
}
getWindow()是mWindow,mWindow = new PhoneWindow(this);
進入PhoneWindow的superDispatchTouchEvent()方法:
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {return mDecor.superDispatchTouchEvent(event);
}
DecorView繼承了FrameLayout,最終會調(diào)用ViewGroup的dispatchTouchEvent()方法:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {if (mInputEventConsistencyVerifier != null) {mInputEventConsistencyVerifier.onTouchEvent(ev, 1);}// If the event targets the accessibility focused view and this is it, start// normal event dispatch. Maybe a descendant is what will handle the click.if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {ev.setTargetAccessibilityFocus(false);}//是否被消費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.//對一些變量重置,清除TouchTargets,mFirstTouchTarget = nullcancelAndClearTouchTargets(ev);resetTouchState();}// Check for interception.//檢查是否需要攔截touch事件//mFirstTouchTarget是ViewGroup的一個內(nèi)部類封裝了被觸摸的view//mFirstTouchTarget不為空表示有子View消費touch事件//mFirstTouchTarget為空表示ViewGroup攔截了touch事件或者子view沒有消費touch事件final boolean intercepted;if (actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget != null) {final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;if (!disallowIntercept) {//根據(jù)onInterceptTouchEvent()方法的返回值來決定是否攔截touch事件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.//如果不是ACTION_DOWN并且沒有子view消費掉touch事件,攔截touch事件//如果ACTION_DOWN沒有被子view消費,//那么接下來的ACTION_MOVE,ACTINO_UP就都不會走onInterceptTouchEvent()方法//將intercepted設(shè)置為true直接進行攔截交給自身進行touch事件的處理(dispatchTouchEvent() -> onTouch() -> onTouchEvent())intercepted = true;}// If intercepted, start normal event dispatch. Also if there is already// a view that is handling the gesture, do normal event dispatch.if (intercepted || mFirstTouchTarget != null) {ev.setTargetAccessibilityFocus(false);}// Check for cancelation.//檢查cancel狀態(tài)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 the event is targeting accessiiblity focus we give it to the// view that has accessibility focus and if it does not handle it// we clear the flag and dispatch the event to all children as usual.// We are looking up the accessibility focused host to avoid keeping// state since these events are very rare.View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()? findChildWithAccessibilityFocus() : null;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) {計算touch點的坐標根據(jù)touch點坐標判斷當前觸摸到的是哪個子viewfinal 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 ArrayList<View> preorderedList = buildOrderedChildList();final boolean customOrder = preorderedList == null&& isChildrenDrawingOrderEnabled();final View[] children = mChildren;注意是從后往前進行遍歷 最后面的child是最上層的viewfor (int i = childrenCount - 1; i >= 0; i--) {final int childIndex = customOrder? getChildDrawingOrder(childrenCount, i) : i;final View child = (preorderedList == null)? children[childIndex] : preorderedList.get(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;}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;//已經(jīng)找到了接收touch事件的子view,不用再繼續(xù)遍歷break;}resetCancelNextUpFlag(child);//dispatchTransformedTouchEvent()中調(diào)用了child.dispatchTouchEvent()方法//根據(jù)child.dispatchTouchEvent()方法的返回值判斷子view是否消費了touch事件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 indexfor (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);//為true表示此時已經(jīng)將touch事件分發(fā)給了子view,即子view消費了touch事件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);}if (preorderedList != null) preorderedList.clear();}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.//在ACTION_UP手指抬起或者ACTION_CANCEL取消touch事件分發(fā),恢復(fù)重置一些狀態(tài)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;
}
整個touch事件的傳遞過程為: Activity.dispatchTouchEvent() -> PhoneWindow.superDispatchTouchEvent() -> DecorView.superDispatchTouchEvent() -> ViewGroup.dispatchTouchEvent() -> View.dispatchTouchEvent()
而消費過程則相反: View.onTouchEvent() -> ViewGroup.onTouchEvent() -> DecorView.onTouchEvent() -> Activity.onTouchEvent()
如果子View沒有一個地方返回true,touch事件只會進來一次,只會響應(yīng)DOWN事件,代表不需要消費該事件,如果你想響應(yīng)MOVE或UP必須找個地方ture。
對于ViewGroup來講,如果你想攔截子View的Touch事件,可以覆寫onInterceptTouchEvent,返回true即可?。
如果onInterceptTouchEvent返回的是true,會執(zhí)行該ViewGroup的onTouchEvent方法?,?如果子View沒有消費touch事件,也會調(diào)用該ViewGroup的onTouchEvent?方法。
總結(jié)
以上是生活随笔為你收集整理的ViewGroup的Touch事件分发(源码分析)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: View的Touch事件分发(二.源码分
- 下一篇: 为什么UI线程中创建Handler可以不