Android事件分發一之事件傳遞
Android事件分發二之ViewGroup如何處理事件
Android事件分發三之View
Android事件分發四總結
Android滑動沖突一內部攔截外部攔截簡介
Android滑動沖突二內部攔截法詳情
本文接上篇Android滑動沖突一內部攔截外部攔截簡介
一 ViewPager嵌套ListView的滑動沖突,內部攔截法為何ViewPager的onInterceptTouchEvent要做判斷而不是直接回傳true?
我們重溫下Android事件分發二之ViewGroup如何處理事件中ViewGroup事件分發方法的原始碼,注意這個ViewGroup對應到我們例子的ViewPager
class:ViewGroup:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
......//省略
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) {
cancelAndClearTouchTargets(ev);
resetTouchState();
}
//**重點 intercepted就是判斷該ViewGroup是否需要直接處理事件的標記
// Check for interception.
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
// 用內部攔截法時,down事件的時候,我們在子類的requestDisallowInterceptTouchEvent傳入了true,就是希望父類不要攔截我,從而不進入這個判斷,
//但是坑的是,在down的時候,在上面的if判斷中對disallowIntercept進行了重置為false,不受子類requestDisallowInterceptTouchEvent的影響了,也就是說肯定會進入這個判斷
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;
}
}
ViewGroup的事件攔截方法第一步就是給intercpted賦值,當intercepted為true時,就不會通過for回圈去找子View分發事件,我們看看在上述原始碼里面發現在ACTION_DOWN的時候,對disallowIntercept進行了賦值->true,不受子類方法requestDisallowInterceptTouchEvent(請求父類不要攔截我)的影響,
那么本來我們希望的是在action_down的時候希望父類不要攔截我,不進入if (!disallowIntercept) {}這個判斷從而讓intercepted為false,現在進入if (!disallowIntercept) {}了這個判斷-> intercepted = onInterceptTouchEvent(ev),所以我們正確的方法是應該在父類ViewPager的onInterceptTouchEvent加一個判斷,DOWN時回傳false,其它true,
- ACTION_CANCEL什么時候呼叫?
我們在事件分發Android事件分發一之事件傳遞當中講述了DOWN、MOVE、UP、CANCEL四個方法的呼叫時機,其中CANCEL在被上層事件攔截的時候呼叫,看看原始碼分析其原因
ViewGroup:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
//...省略
// 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 {
// move時進入這里
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;
}
}
}
//...省略
以上是ViewGroup事件分發方法的部分原始碼,我們在Android事件分發二之ViewGroup如何處理事件,總結了手指從第一次觸摸DOWN到滑動MOVE時如何命中目標,此刻的場景是命中目標后手指在螢屏上面MOVE,mFirstTouchTarget此時不為空,會呼叫這個 if (dispatchTransformedTouchEvent(ev, cancelChild,target.child, target.pointerIdBits)) {}判斷,并且其中的cancelChild此時為true(因為我們的ListView在MOVE時,根據滑動手勢左右滑動時要求父類攔截我->intercepted為true),那么我們來看看dispatchTransformedTouchEvent方法cancelChild傳true時候的原始碼:
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) {
//呼叫ACTION_CANCEL方法
event.setAction(MotionEvent.ACTION_CANCEL);
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}
//,,省略
}
很容易看出,在cancel傳入為true的時候,進入判斷呼叫ACTION_CANCEL方法,這也就是為何在ACTION_CANCEL方法在被上級攔截時呼叫的原因了,
- ListView被上層攔截了怎么將事件交還給ViewPager?
我們接著剛剛的分析步驟dispatchTransformedTouchEvent方法會進入handled = child.dispatchTouchEvent(event),其child為ListView將事件是否消費交給了ListView處理,(記住此時條件在用戶down并且move后觸發了ViewPager的攔截方法導致intercepted為true,此時命中了消費事件的目標ListView,即mFirstTouchTarget不為空)我們接著ViewGroup的事件方法走,此時走到了 if (cancelChild) {}我們注意進入該判斷后會呼叫 mFirstTouchTarget = next方法,會將mFirstTouchTarget置為空,
此時ViewGroup的dispatchTouchEvent事件分發方法走完了一次,隨著手指的滑動再次進入dispatchTouchEvent(一定要記住MOVE事件會多次呼叫),很容易看出最侄訓進入
呼叫dispatchTransformedTouchEvent方法,并且child傳null,我們通過查看dispatchTransformedTouchEvent的原始碼,得知當child為null時,會呼叫其父級的dispatchTouchEvent方法,這樣就將事件交回了ViewPager
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/248116.html
標籤:其他
