在上篇文章中,我们讨论了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