事件分發(fā)
Android可以通過(guò)觸摸屏幕來(lái)實(shí)現(xiàn)與用戶的交互赖歌,Android屏幕上顯示了各種各樣的view,這些view有可能是豎直排列也有可能是疊在一起的呵恢,那么我們觸摸屏幕的時(shí)候到底由哪個(gè)view來(lái)響應(yīng)他嚷,這個(gè)就是我們的view的事件分發(fā)
View的分類
- View,View只具有處理事件的能力
- dispatchTouchEvent
- onTouchEvent
- ViewGroup拍霜,ViewGroup具有分發(fā)事件和處理事件的能力(由于ViewGroup繼承自View)
- dispatchTouchEvent
- onInterceptTouchEvent
- onTouchEvent
- requestDisallowInterceptTouchEvent
事件的種類
對(duì)于單點(diǎn)觸摸來(lái)說(shuō)事件主要有四種
- Down,用戶手指按下的時(shí)候觸發(fā)
- Move薪介,用戶手指在屏幕上移動(dòng)的時(shí)候觸發(fā)
- Up沉御,用戶手指抬起的時(shí)候觸發(fā)
- Cancel,事件被取消的時(shí)候觸發(fā)(事件攔截的時(shí)候會(huì)觸發(fā)Cancel昭灵,點(diǎn)擊的時(shí)候按住屏幕滑動(dòng)等)
事件處理的流程
事件的處理一般都是在View中的dispatchTouchEvent進(jìn)行的吠裆,下面是其中比較重要的一段代碼
public boolean dispatchTouchEvent(MotionEvent event) {
//...上面的方法
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;
}
- li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)
當(dāng)我們?cè)O(shè)置了setOnTouchListener的時(shí)候?qū)i進(jìn)行了初始化,并且li.mOnTouchListener也不為null烂完,那么必定會(huì)執(zhí)行onTouch方法试疙,如果onTouch方法返回true,那么result就為true抠蚣。 - if (!result && onTouchEvent(event)) {
result = true;
}
當(dāng)result返回true的時(shí)候則不會(huì)執(zhí)行onTouchEvent方法祝旷。
當(dāng)result返回false(意味著onTouch方法返回了false),那么就會(huì)去執(zhí)行onTouchEvent嘶窄,最終result也返回了true怀跛,dispatchTouchEvent也返回了true,也就是事件被消費(fèi)了柄冲。
3.接下來(lái)就看onTouchEvent做了哪些操作
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction();
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return clickable;
}
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
//長(zhǎng)按事件是由handler去處理的吻谋,如果不是長(zhǎng)按事件那么就會(huì)執(zhí)行下面的代碼
if (!focusTaken) {
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClickInternal();
}
}
break;
case MotionEvent.ACTION_DOWN:
//...down事件校驗(yàn)是都可以點(diǎn)擊,對(duì)長(zhǎng)按事件做了初始化现横,并且如果在滑動(dòng)控件內(nèi)部漓拾,對(duì)點(diǎn)擊事件做了延時(shí)響應(yīng)處理
break;
case MotionEvent.ACTION_CANCEL:
//...cancel的時(shí)候也將點(diǎn)擊和長(zhǎng)按事件移除了
if (clickable) {
setPressed(false);
}
removeTapCallback();
removeLongPressCallback();
break;
case MotionEvent.ACTION_MOVE:
//....move事件主要對(duì)點(diǎn)擊和長(zhǎng)按做了移除,比如當(dāng)手指移出了view等
break;
}
return true;
}
return false;
}
在up事件中我們可以看到如果不是長(zhǎng)按事件那么就是執(zhí)行點(diǎn)擊事件
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;
//在這里執(zhí)行了點(diǎn)擊事件
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
notifyEnterOrExitForAutoFillIfNeeded(true);
return result;
}
事件分發(fā)
事件分發(fā)主要是由ViewGroup的dispatchTouchEvent來(lái)處理的
同樣的ViewGroup的dispatchTouchEvent的事件也分為Down戒祠,Move骇两,Up,Cancel
dispatchTouchEvent部分的主要邏輯
//是否攔截部分的邏輯
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;
}
//攔截部分的邏輯
Boolean alreadyDispatchedToNewTouchTarget = false;
if (!canceled && !intercepted) {
// If the event is targeting accessibility 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.
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 ArrayList<View> preorderedList = buildTouchDispatchChildList();
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 (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
}
}
}
//自己處理或者分發(fā)的邏輯
// 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;
}
}
Down事件
- 當(dāng)為Down事件的時(shí)候根據(jù)disallowIntercept以及onInterceptTouchEvent來(lái)確定是否攔截事件
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;
}
默認(rèn)情況下 (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0為false
- 如果onInterceptTouchEvent返回true那么intercepted就為true姜盈,那么意味著父控件要進(jìn)行攔截
- 否則不進(jìn)行攔截
- 根據(jù)是否攔截來(lái)判斷是處理還是分發(fā)
if (!canceled && !intercepted)
- 如果被攔截則不執(zhí)行上面的這個(gè)if內(nèi)部的邏輯
- 如果沒(méi)有被攔截低千,則執(zhí)行上面的這個(gè)if內(nèi)部的邏輯
if (!canceled && !intercepted) {
// If the event is targeting accessibility 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.
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 ArrayList<View> preorderedList = buildTouchDispatchChildList();
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 (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
}
}
}
在上面的代碼中我們可以看到對(duì)所有的子view進(jìn)行了遍歷,通過(guò)dispatchTransformedTouchEvent方法尋找有沒(méi)有子view要處理這個(gè)事件馏颂。
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;
}
// 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;
}
調(diào)用dispatchTransformedTouchEvent方法傳遞的參數(shù)是(ev, false, child, idBitsToAssign)示血,cancel為false,并且我們是Down事件饱亮,child不為null矾芙。所以他會(huì)執(zhí)行handled = child.dispatchTouchEvent(transformedEvent);在這里就是將Down事件分發(fā)給子view去處理了。
- 如果child.dispatchTouchEvent返回了false近上,代表他不處理這個(gè)事件剔宪,那么就接著尋找下一個(gè)子View,看看是否處理這個(gè)事件
- 如果child.dispatchTouchEvent返回了true壹无,代表他要處理這個(gè)事件
//這里將child加入到了TouchTarget的鏈表中葱绒,TouchTarget記錄了當(dāng)前有多少個(gè)手指觸摸,
//我們?cè)谶@里分析的是單點(diǎn)觸摸斗锭,那么這個(gè)鏈表應(yīng)該是只有一個(gè)數(shù)據(jù)
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
- 接著就執(zhí)行if (mFirstTouchTarget == null)判斷地淀,這里分為三種情況
- 如果上面對(duì)Down事件進(jìn)行攔截,那么mFirstTouchTarget肯定為null岖是,執(zhí)行handled = dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS);
我們發(fā)現(xiàn)在這里child為null帮毁,canceled也為false实苞,那么就會(huì)調(diào)用
handled = super.dispatchTouchEvent(transformedEvent);去執(zhí)行View的dispatchTouchEvent去處理這個(gè)事件。 - 如果沒(méi)有對(duì)Down事件進(jìn)行攔截烈疚,但是沒(méi)有子View去處理這個(gè)事件黔牵,那么他執(zhí)行的過(guò)程和攔截是一樣的
- 如果沒(méi)有對(duì)Down事件進(jìn)行攔截,并且有子View進(jìn)行處理事件爷肝,這個(gè)時(shí)候mFirstTouchTarget就不為null執(zhí)行else代碼
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;
}
在分發(fā)的時(shí)候找到了子View的時(shí)候?qū)?biāo)記設(shè)置為true猾浦,并且將child加入到了TouchTarget中,那么他就會(huì)執(zhí)行handled = true;表示事件已經(jīng)被處理灯抛。
Move事件
- move事件同樣也會(huì)判斷是否進(jìn)行攔截金赦,因?yàn)閙FirstTouchTarget不為null
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;
}
- 如果被攔截的話就不會(huì)再分發(fā)
- 如果沒(méi)有被攔截,會(huì)遍歷所有的子View進(jìn)行分發(fā)对嚼,看看是不是有子View要處理這個(gè)事件
- 根據(jù)是否攔截以及是否有子View處理事件夹抗,對(duì)事件進(jìn)行處理
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猪半,那么會(huì)執(zhí)行else兔朦,調(diào)用了dispatchTransformedTouchEvent,參數(shù)為(ev, cancelChild,target.child, target.pointerIdBits)),cancelChild為true磨确,target.child不為null
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;
}
// 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);
}
return handled;
}
做了兩步處理沽甥,第一步event.setAction(MotionEvent.ACTION_CANCEL);將事件設(shè)置為cancel,第二步調(diào)用handled = child.dispatchTouchEvent(event);將cancel事件分發(fā)給子View乏奥。
- 事件沒(méi)有被攔截摆舟,但是沒(méi)有子View消費(fèi)這個(gè)事件,這個(gè)時(shí)候mFirstTouchTarget不為null邓了,會(huì)執(zhí)行dispatchTransformedTouchEvent(ev, cancelChild,target.child, target.pointerIdBits)方法恨诱,其中的參數(shù)是cancelChild為false,target.child為null骗炉,那么會(huì)執(zhí)行handled = super.dispatchTouchEvent(transformedEvent);調(diào)用View的dispatchTouchEvent來(lái)處理事件照宝。
- 事件沒(méi)有被攔截,有子View消費(fèi)事件句葵,這個(gè)時(shí)候mFirstTouchTarget不為null,執(zhí)行handled = true;將事件消費(fèi)厕鹃。
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
}
會(huì)有多個(gè)move事件,分發(fā)流程和上面是一樣的乍丈。
滑動(dòng)沖突解決
- 內(nèi)部攔截法
requestDisallowInterceptTouchEvent剂碴,true代表讓父親不要攔截
false 代表讓父親攔截 - 外部攔截法
onInterceptTouchEvent,返回true則會(huì)攔截轻专,返回false則不會(huì)攔截