在上篇文章中,我們討論了View
的事件分發機制。這篇文章我們討論佈局控件 ViewGroup
的事件分發機制。ViewGroup
繼承自View
組件。
一、小例子分析
這次由於要討論佈局控件ViewGroup
,所以這次我們重寫Button
、LinearLayout
兩個控件。
1.項目源碼
自定義Button類
public class TestButton extends Button {
public static final String CLICK_EXAMPLE_3 = "ClickExample3";
public TestButton(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
Log.i(CLICK_EXAMPLE_3,"TestButton dispatchTouchEvent--action= "+event.getAction());
return super.dispatchTouchEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.i(CLICK_EXAMPLE_3,"TestButton onTouchEvent--action= "+event.getAction());
return super.onTouchEvent(event);
}
}
自定義LinearLayout類
public class TestLinearLayout extends LinearLayout {
public static final String CLICK_EXAMPLE_3 = "ClickExample3";
public TestLinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.i(CLICK_EXAMPLE_3,"TestLinearLayout onInterceptTouchEvent--action= "+ev.getAction());
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.i(CLICK_EXAMPLE_3,"TestLinearLayout onTouchEvent--action= "+event.getAction());
return super.onTouchEvent(event);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.i(CLICK_EXAMPLE_3,"TestLinearLayout dispatchTouchEvent--action= "+ev.getAction());
return super.dispatchTouchEvent(ev);
}
}
主佈局文件:
<?xml version="1.0" encoding="utf-8"?>
<com.lengyu.free.clickexample3.TestLinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/my_linear"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
>
<com.lengyu.free.clickexample3.TestButton
android:id="@+id/my_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Click Me!"/>
</com.lengyu.free.clickexample3.TestLinearLayout>
主Activity
文件
public class MainActivity extends Activity implements View.OnClickListener,View.OnTouchListener{
public static final String CLICK_EXAMPLE_3 = "ClickExample3";
private TestLinearLayout mLayout;
private TestButton mButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mButton=(TestButton)this.findViewById(R.id.my_btn);
mLayout=(TestLinearLayout)this.findViewById(R.id.my_linear);
mButton.setOnClickListener(this);
mLayout.setOnClickListener(this);
mButton.setOnTouchListener(this);
mLayout.setOnTouchListener(this);
}
@Override
public void onClick(View v) {
Log.i(CLICK_EXAMPLE_3,"OnClickListener--onClick--"+v);
}
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.i(CLICK_EXAMPLE_3,"OnTouchListener--onTouch--action="+event.getAction()+"--"+v);
return false;
}
}
2.事件分析
事件1: 點擊 Button
區域:
09-12 02:21:11.127 2292-2292/? I/ClickExample3: TestLinearLayout dispatchTouchEvent--action= 0
09-12 02:21:11.127 2292-2292/? I/ClickExample3: TestLinearLayout onInterceptTouchEvent--action= 0
09-12 02:21:11.127 2292-2292/? I/ClickExample3: TestButton dispatchTouchEvent--action= 0
09-12 02:21:11.127 2292-2292/? I/ClickExample3: OnTouchListener--onTouch--action=0--com.lengyu.free.clickexample3.TestButton{b2f60670 VFED..C. ......I. 166,345-313,417 #7f0c0051 app:id/my_btn}
09-12 02:21:11.127 2292-2292/? I/ClickExample3: TestButton onTouchEvent--action= 0
09-12 02:21:11.177 2292-2292/? I/ClickExample3: TestLinearLayout dispatchTouchEvent--action= 1
09-12 02:21:11.177 2292-2292/? I/ClickExample3: TestLinearLayout onInterceptTouchEvent--action= 1
09-12 02:21:11.177 2292-2292/? I/ClickExample3: TestButton dispatchTouchEvent--action= 1
09-12 02:21:11.177 2292-2292/? I/ClickExample3: OnTouchListener--onTouch--action=1--com.lengyu.free.clickexample3.TestButton{b2f60670 VFED..C. ...P..I. 166,345-313,417 #7f0c0051 app:id/my_btn}
09-12 02:21:11.177 2292-2292/? I/ClickExample3: TestButton onTouchEvent--action= 1
09-12 02:21:11.177 2292-2292/? I/ClickExample3: OnClickListener--onClick--com.lengyu.free.clickexample3.TestButton{b2f60670 VFED..C. ...P..I. 166,345-313,417 #7f0c0051 app:id/my_btn}
可以發現,點擊button時,觸發的流程是: TestLinearLayout.dispatchTouchEvent
->TestLinearLayout.onInterceptTouchEvent
->TestButton.dispatchTouchEvent
,也就是說點擊TestButton
時,分發的每一個事件都是由TestButton
的父控件TestLinearLayout
分發給子控件。
事件2: 點擊 Button
區域之外的區域:
09-12 02:54:56.138 2292-2292/? I/ClickExample3: TestLinearLayout dispatchTouchEvent--action= 0
09-12 02:54:56.138 2292-2292/? I/ClickExample3: TestLinearLayout onInterceptTouchEvent--action= 0
09-12 02:54:56.138 2292-2292/? I/ClickExample3: OnTouchListener--onTouch--action=0--com.lengyu.free.clickexample3.TestLinearLayout{b2f5fee8 V.E...C. ......I. 0,0-480,762 #7f0c0050 app:id/my_linear}
09-12 02:54:56.138 2292-2292/? I/ClickExample3: TestLinearLayout onTouchEvent--action= 0
09-12 02:54:56.218 2292-2292/? I/ClickExample3: TestLinearLayout dispatchTouchEvent--action= 1
09-12 02:54:56.218 2292-2292/? I/ClickExample3: OnTouchListener--onTouch--action=1--com.lengyu.free.clickexample3.TestLinearLayout{b2f5fee8 V.E...C. ...P..I. 0,0-480,762 #7f0c0050 app:id/my_linear}
09-12 02:54:56.218 2292-2292/? I/ClickExample3: TestLinearLayout onTouchEvent--action= 1
09-12 02:54:56.218 2292-2292/? I/ClickExample3: OnClickListener--onClick--com.lengyu.free.clickexample3.TestLinearLayout{b2f5fee8 V.E...C. ...P..I. 0,0-480,762 #7f0c0050 app:id/my_linear}
可以看到,點擊button
之外的區域時,ACTION_DOWN
事件的分發順序是dispatchTouchEvent
->onInterceptTouchEvent
->onTouch
->onTouchEvent
。ACTION_UP
事件的分發順序是dispatchTouchEvent
->onTouch
->onTouchEvent
-onClick
,在執行ACTION_UP
事件時,onInterceptTouchEvent
並沒有執行。
這時需要我們從ViewGroup
中尋找答案。
二、ViewGroup源碼分析
ViewGroup
繼承自View
,對其中的部分方法進行了複寫。下面我們先來分析一下 dispatchTouchEvent
方法
2.1 dispatchTouchEvent
方法
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.
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;
}
// 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.
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 down
final 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 ArrayList<View> preorderedList = buildOrderedChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
for (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;
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);
}
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.
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;
}
1. 19行-25 行,對ACTION_DOWN
事件進行處理。
由於ACTION_DOWN
事件是一系類事件的開端,所以在首次分發ACTION_DOWN
事件時,調用cancelAndClearTouchTargets(ev);
方法,對ACTION_DOWN
事件之前的手勢進行清理取消,並將mFirstTouchTarget
的值設爲NULL
。然後調用resetTouchState
方法,將觸摸狀態重置。
2. 28 行-42 行,檢測是否需要攔截
在該處代碼中,先聲明瞭一個intercepted
變量,用來標記是否需要攔截。然後在if判斷中,判斷actionMasked == MotionEvent.ACTION_DOWN
, 如果是
|| mFirstTouchTarget != nullACTION_DOWN
或者 mFirstTouchTarget != null
(即已經找到能夠接收touch事件的目標組件)時,條件成立,成立後接下來獲取mGroupFlags
中 的disallowIntercept
(禁止攔截)狀態,如果disallowIntercept
(禁止攔截)的值的狀態爲false
的話,即允許攔截,則調用onInterceptTouchEvent
方法獲取用戶自定義的攔截狀態設置給intercepted
變量,並將action的行爲進行存儲。如果disallowIntercept
(禁止攔截)的值的狀態爲true
的話,即不允許攔截的話,則直接設置intercepted
變量的值爲false
。如果actionMasked == MotionEvent.ACTION_DOWN
不成立,即尚未找到觸摸對象且不是初始化的
|| mFirstTouchTarget != nullACTION_DOWN
事件,則直接進行攔截。
其中disallowIntercept
(禁止攔截) 狀態的設置可用requestDisallowInterceptTouchEvent
方法
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
// We're already in this state, assume our ancestors are too
return;
}
if (disallowIntercept) {
mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
} else {
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
// Pass it up to our parent
if (mParent != null) {
mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
}
}
requestDisallowInterceptTouchEvent
方法設置我true
時,intercepted
的值爲false
。ViewGroup不同於View特有的onInterceptTouchEvent
方法
public boolean onInterceptTouchEvent(MotionEvent ev) {
return false;
}
默認的onInterceptTouchEvent
方法只是返回了一個false
,也即intercepted=false
. 上面例子的調用流程爲dispatchTouchEvent
->onInterceptTouchEvent
->onTouchEvent
,也就是說disallowIntercept
的默認值爲false
.
參考資料:
3. 51 行 到 52行
通過標記和action檢查cancel,然後將結果賦值給局部boolean變量canceled。
4. 53-函數結束,事件分發。
54行首先可以看見獲取一個boolean變量標記split來標記,默認是true,作用是是否把事件分發給多個子View,這個同樣在ViewGroup中提供了public的方法設置,如下:
public void setMotionEventSplittingEnabled(boolean split) {
// TODO Applications really shouldn't change this setting mid-touch event,
// but perhaps this should handle that case and send ACTION_CANCELs to any child views
// with gestures in progress when this is changed.
if (split) {
mGroupFlags |= FLAG_SPLIT_MOTION_EVENTS;
} else {
mGroupFlags &= ~FLAG_SPLIT_MOTION_EVENTS;
}
}
接着會有1個if(!canceled && !intercepted)
判斷語句,當canceled
和intercepted
的值都爲false
時,會進入if
其中。
第80行的if
判斷中對newTouchTarget
和childrenCount
進行判斷,如果newTouchTarget
爲空和childrenCount
個數不爲空的話,則進一步處理。
第85行調用buildOrderedChildList
方法返回ViewGroup
中
字view的先序遍歷順序,其中先序遍歷順序是按view的繪製順序排列的(按照View的Z座標由小到大排列)
第89行進入1個for
循環,從buildOrderedChildList
序列的最後一個 view
向前遍歷,視覺上是從前往後進行掃描,即對於多個包含同一點的view
, 浮在最上面的view最先進行相應
第103行調用getTouchTarget(child)
方法來判斷當前view是否已經在mFirstTouchTarget
變量所指向的TouchTarget
鏈表中,如果在的還返回該對象,如果不在的返回null.
第114行判斷getTouchTarget返回的對象是否爲null,如果不爲null表示當前view已經接收了自已區域內的響應事件直接break出循環。
第122行調用dispatchTransformedTouchEvent
方法對傳遞的事件進行分發,該方法如果第3個參數child
控件的值爲null的話,即ViewGroup
的子view
爲空,調用父控件(View)的dispatchTouchEvent
方法。子view
爲不爲空,調用子控件的dispatchTouchEvent
方法。該方法返回調用dispatchTouchEvent
方法的布爾值。
第122行到141行,如果調用dispatchTransformedTouchEvent
方法時子view
或父view
中dispatchTouchEvent
返回的值爲true
話,(這時子View中的onTouchEvent
方法已經消費掉該事件返回true)則進入if條件語句中,在該if條件從句中mLastTouchDownIndex
記錄最後child
控件的座標, 調用addTouchTarget
方法將當前子View加入到mFirstTouchTarget
變量指向的鏈表中, newTouchTarget
記錄當前TouchTarget
, 將alreadyDispatchedToNewTouchTarget
方法設置爲true
;跳出當前遍歷子view
循環。如果調用dispatchTransformedTouchEvent
方法時子view
或父view
中dispatchTouchEvent
返回的值爲false
,這時newTouchTarget
爲null,alreadyDispatchedToNewTouchTarget
變量的值爲false
,無法調用addTouchTarget(),從而導致mFirstTouchTarget
爲null(沒法對mFirstTouchTarget賦值,因爲上面分析了mFirstTouchTarget
一進來是ACTION_DOWN
就置位爲null
了),這時再執行下面的ACTION_MOVE
事件和ACTION_UP
事件時,執行到28行到42行時,mFirstTouchTarget
爲null導致onInterceptTouchEvent
方法不會執行,intercepted
的值 true
,再執行到58行時,直接跳到163行執行,把ViewGroup當作普通view調用dispatchTransformedTouchEvent
方法處理事件。
149行到第159行之間的爲for循環遍歷View
之外以及條件if (newTouchTarget == null && childrenCount != 0)
之後的代碼,該處代碼判斷如果newTouchTarget 爲null,但mFirstTouchTarget不爲空,即沒有找到子view接收控件,但mFirstTouchTarget
不爲空,就將newTouchTarget
指向mFirstTouchTarget
鏈表中最後一個添加的對象。
163行到166行,如果mFirstTouchTarget=null
,即ViewGroup沒有找到能夠消費子控件的view對象,則把當前ViewGroup當作普通控件調用父view
的dispatchTouchEvent
方法進行處理。該種情況與上面事件2(點擊 Button區域外的區域)情況相同,結合29行到42行代碼解釋了爲什麼ACTIONDOWN
事件調用了onInterceptTouchEvent
方法,ACTIONDOWNUP
沒有調用。同時也解釋了上文章中的事件9,由於子view中處理ACTION_DOWN
事件時,dispatchTouchEvent
方法返回爲false
, 導致dispatchTransformedTouchEvent
返回爲false
,沒有對mFirstTouchTarget
進行賦值爲null,在該處代碼中將ACTION_DOWN
事件分發到父 View
中的dispatchTouchEvent
方法中,多打出父View
中ACTION_DOWN
事件日誌。
第174-176行代碼是對ACTION_DOWN事件和dispatchTransformedTouchEvent
返回爲true
的處理,這時mFirstTouchTarget!=null
已經找到消費該事件的子View
,故直接返回true
,不再調用父VIew中的dispatchTouchEvent
方法。第176-195行代碼,則是對已經找到消費該事件子view,除ACTIONDOWN
事件之外的其它事件進行處理,接着遞歸調用子view的dispatchTouchEvent
方法
2.1 dispatchTransformedTouchEvent
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) {
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;
}
第8行到17行,對 ACTION_CANCEL
事件進行處理,如果第3個參數child
控件的值爲null的話,即ViewGroup
的子view
爲空,調用父控件(View)的dispatchTouchEvent
方法。子view
爲不爲空,調用子控件的dispatchTouchEvent
方法
第35行到70行,對 事件進行處理,如果第3個參數child
控件的值爲null的話,即ViewGroup
的子view
爲空,調用父控件(View)的dispatchTouchEvent
方法。子view
爲不爲空,調用子控件的dispatchTouchEvent
方法。該方 法返回調用dispatchTouchEvent
方法的布爾值。
參考資料:
http://blog.csdn.net/yanbober/article/details/45912661