在上一篇中我们分析了从view的dispatchTouchEvent
到onTouchListener
的onTouch
回调到onTouchEvent
到onClickLisener
的onClick
android中view事件传递,在后面遗留了两个问题,那就是在onTouchEvent
中返回false的时候,只触发到action_down
事件,以及在dispatchTouchEvent
中返回false也是只触发到action_down
事件,今天就带着这两个问题分析是如何只会执行到action_down
事件。
开篇还是用上面的例子,但是因为涉及到viewgroup因此在外层放一个父布局用作监听:
public class EventActivity extends AppCompatActivity implements View.OnClickListener, View.OnTouchListener {
private static final String TAG = EventActivity.class.getSimpleName();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
View testView = findViewById(R.id.test_view);
TestViewGroup testViewGroup = findViewById(R.id.test_viewgroup);
testView.setOnClickListener(this);
testView.setOnTouchListener(this);
testViewGroup.setOnClickListener(this);
testViewGroup.setOnTouchListener(this);
}
@Override
public void onClick(View v) {
Log.d(TAG, "onClick-----" + v.getClass().getSimpleName());
}
@Override
public boolean onTouch(View v, MotionEvent event) {
int action = event.getAction();
String actionName = "";
if (action == MotionEvent.ACTION_DOWN) {
actionName = "action_down";
} else if (action == MotionEvent.ACTION_MOVE) {
actionName = "action_move";
} else if (action == MotionEvent.ACTION_UP) {
actionName = "action_up";
}
Log.d(TAG, "onTouch-----" + v.getClass().getSimpleName() + ";" + actionName);
return false;
}
}
外层用了个TestViewGroup
:
public class TestViewGroup extends RelativeLayout {
private static final String TAG = TestViewGroup.class.getSimpleName();
public TestViewGroup(Context context) {
super(context);
}
public TestViewGroup(Context context, AttributeSet attrs) {
super(context, attires
}
public TestViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
int action = event.getAction();
String actionName = "";
if (action == MotionEvent.ACTION_DOWN) {
actionName = "action_down";
} else if (action == MotionEvent.ACTION_MOVE) {
actionName = "action_move";
} else if (action == MotionEvent.ACTION_UP) {
actionName = "action_up";
}
Log.d(TAG, "TestViewGroup dispatchTouchEvent-----" + actionName);
return super.dispatchTouchEvent(event);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
int action = event.getAction();
String actionName = "";
if (action == MotionEvent.ACTION_DOWN) {
actionName = "action_down";
} else if (action == MotionEvent.ACTION_MOVE) {
actionName = "action_move";
} else if (action == MotionEvent.ACTION_UP) {
actionName = "action_up";
}
Log.d(TAG, "TestViewGroup onInterceptTouchEvent-----" + actionName);
return super.onInterceptTouchEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction();
String actionName = "";
if (action == MotionEvent.ACTION_DOWN) {
actionName = "action_down";
} else if (action == MotionEvent.ACTION_MOVE) {
actionName = "action_move";
} else if (action == MotionEvent.ACTION_UP) {
actionName = "action_up";
}
Log.d(TAG, "TestViewGroup onTouchEvent-----" + actionName);
return super.onTouchEvent(event);
}
}
大家都知道viewgroup的事件传递多了个onInterceptTouchEvent
方法,专门用来控制要不要拦截onTouchEvent事件,所以这里也默认将该方法给重写了。下面来看下默认的点击事件日志,先在里面的testview上点击下:
下面如果再点击testview外面区域的话,看看日志又是如何:
此处可以看出来会触发到
testviewgroup
的onTouchEvent
事件以及它的onClick
事件,是因为没找到里面子view触发的点击事件,因此会传给自己的onTouchEvent
和onClick
事件,而且在action_up
中没有触发testviewgroup
的onInterceptTouchEvent
方法,这些一系列问题还是要从源码去分析发生了什么,下面带着大家一步步分析源码,既然从日志上看是先调用了testviewgroup的dispatchTouchEvent
方法,因此咋们从viewgroup的dispatchTouchEvent
入手。
viewgroup之dispatchTouchEvent
@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) {
// 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.
//1.清除targetView
cancelAndClearTouchTargets(ev);
//2.重置touchState
resetTouchState();
}
// Check for interception.
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
//3.mGroupFlags和FLAG_DISALLOW_INTERCEPT进行位与运算,mGroupFlags由于默认是0,因此在跟FLAG_DISALLOW_INTERCEPT进行位与的情况下肯定是等于0的,因此disallowIntercept肯定是false了
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
//4.默认会走onInterceptTouchEvent方法
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;
//5.如果上面不拦截在action_down的时候会走这里
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 = buildTouchDispatchChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
//6.可以看到这里有个反向遍历,其实可以想到这里是后添加的view先遍历出来,因此是这么遍历
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, 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;
}
//7.如果view不可见,获取点击的区域不在子view里面,直接跳出该次循环
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
//8.获取点击的view,这里其实获取到的newTouchTarget是空的
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);
//9.该处很关键,这里是传递到子view的dispatchTouchEvent事件的关键
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();
//10.该处是处理newTouchTarget和mFirstTouchTarget变量的
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.
//12.该处很重要,看到没传给dispatchTransformedTouchEvent的child=null,这个是上一节view事件传递遗留的问题关键点
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;
//11.这里由于上面alreadyDispatchedToNewTouchTarget=true以及target就是mFirstTouchTarget,上面也分析了mFirstTouchTarget和newTouchTarget指的是同一个变量
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的地方调用了cancelAndClearTouchTargets
方法,从字面意思看是取消和清除了TouchTargets,看看该方法:
/**
* Cancels and clears all touch targets.
*/
private void cancelAndClearTouchTargets(MotionEvent event) {
if (mFirstTouchTarget != null) {
boolean syntheticEvent = false;
if (event == null) {
final long now = SystemClock.uptimeMillis();
event = MotionEvent.obtain(now, now,
MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
syntheticEvent = true;
}
for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {
resetCancelNextUpFlag(target.child);
dispatchTransformedTouchEvent(event, true, target.child, target.pointerIdBits);
}
clearTouchTargets();
if (syntheticEvent) {
event.recycle();
}
}
}
上面调用了resetCancelNextUpFlag
方法进行resetCancel标志,已近后面调用了clearTouchTargets
:
/**
* Clears all touch targets.
*/
private void clearTouchTargets() {
TouchTarget target = mFirstTouchTarget;
if (target != null) {
do {
TouchTarget next = target.next;
target.recycle();
target = next;
} while (target != null);
mFirstTouchTarget = null;
}
}
看到了没,上面要做的就是不断的循环然target,最后将mFirstTouchTarget
至为了null,后面要用到该变量。继续回到dispatchTouchEvent
方法的注释2,调用了resetCancelNextUpFlag
方法。在注释3的地方final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0
这么一句,该句很关键,用mGroupFlags
变量和FLAG_DISALLOW_INTERCEPT
常量进行位与,FLAG_DISALLOW_INTERCEPT=0x80000
,所以如果mGroupFlags
是默认值那肯定进行位与之后必须=0,那么disallowIntercept为false,所以默认会走注释4的地方,会调用onInterceptTouchEvent
方法,所以走不走onInterceptTouchEvent
方法,可以通过控制mGroupFlags
变量,在viewgroup源码中有直接设置mGroupFlags
变量的方法:
@Override
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
// We're already in this state, assume our ancestors are too
return;
}
//如果传进来的disallowIntercept=true,此时和FLAG_DISALLOW_INTERCEPT进行位或运算,那上面的方法中mGroupFlags和FLAG_DISALLOW_INTERCEPT进行位与肯定不等于0,disallowIntercept=true,就不会调用`onInterceptTouchEvent`方法
if (disallowIntercept) {
mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
} else {
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
// Pass it up to our parent
if (mParent != null) {
mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
}
}
从上面的注释来看,如果传进来的disallowIntercept=true
,则不会走onInterceptTouchEvent
,也就是不拦截,反之拦截。再回到注释4,咋们可以看看onInterceptTouchEvent
方法内部的实现:
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
&& ev.getAction() == MotionEvent.ACTION_DOWN
&& ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
&& isOnScrollbarThumb(ev.getX(), ev.getY())) {
return true;
}
return false;
}
可以看到默认是返回false,所以注释4的intercepted=false
,接着到了注释5的地方了,接着走到了注释6的地方,通过反序遍历viewgroup中的子view,其实很好理解反序遍历,因为viewgroup中addview或在xml布局文件中后添加的view肯定在viewgroup的最上面,因此最先处理最上面的view的onTouch事件。在注释7我们可以看到调用了if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) {
分别判断是不是可见和是否点击了该view的区域:
/**
* Returns true if a child view can receive pointer events.
* @hide
*/
private static boolean canViewReceivePointerEvents(@NonNull View child) {
//和VISIBILITY_MASK常量位与或者动画不为空
return (child.mViewFlags & VISIBILITY_MASK) == VISIBLE
|| child.getAnimation() != null;
}
/**
* Returns true if a child view contains the specified point when transformed
* into its coordinate space.
* Child must not be null.
* @hide
*/
protected boolean isTransformedTouchPointInView(float x, float y, View child,
PointF outLocalPoint) {
final float[] point = getTempPoint();
point[0] = x;
point[1] = y;
//点击的点存储
transformPointToViewLocal(point, child);
//点击的点是不是在child上
final boolean isInView = child.pointInView(point[0], point[1]);
if (isInView && outLocalPoint != null) {
outLocalPoint.set(point[0], point[1]);
}
return isInView;
}
所以如果不可见或者点击区域不在该view上直接continue该次循环,所以如果没有点击到viewgroup的子view身上是直接跳出该次遍历的,也就不会有后面的子view的dispatchTouchEvent
和onTouchEvent
事件。所以默认点击了子view是不会continue该次循环,紧接着到了注释8,该处调用了getTouchTarget
方法:
/**
* Gets the touch target for specified child view.
* Returns null if not found.
*/
private TouchTarget getTouchTarget(@NonNull View child) {
for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {
if (target.child == child) {
return target;
}
}
return null;
}
此处的mFirstTouchTarget
在一开始的action_down的时候至为null了,所以此处返回的TouchTarget
也是null,至少值action_down的时候是null。紧接着到了注释9的地方,可以看到此处调用了dispatchTransformedTouchEvent
方法:
/**
* Transforms a motion event into the coordinate space of a particular child view,
* filters out irrelevant pointer ids, and overrides its action if necessary.
* If child is null, assumes the MotionEvent will be sent to this ViewGroup instead.
*/
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();
//默认不会走这里,cancel的时候才会走这里
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());
}
//此处很关键的一个点,调用了child的dispatchTouchEvent方法
handled = child.dispatchTouchEvent(transformedEvent);
}
// Done.
transformedEvent.recycle();
return handled;
}
该方法直接看最后调用了child.dispatchTouchEvent(transformedEvent)
,并且将child的dispatchTouchEvent
返回值作为该方法的返回值,所以咱们可以看看该方法的返回值意义何在,继续回到注释9的位置,如果返回true,说明子view的dispatchTouchEvent
返回true,在上一篇分析android中view事件传递,如果view的mOnTouchListener.onTouch
方法返回true或者onTouchEvent
返回true,或者不重写dispatchTouchEvent
方法,直接返回true三种情况。继续看注释10处调用了addTouchTarget
方法:
/**
* Adds a touch target for specified child to the beginning of the list.
* Assumes the target child is not already present.
*/
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
//终于看到mFirstTouchTarget被赋值了,说明只有在dispatchTransformedTouchEvent方法返回true才能被赋值
mFirstTouchTarget = target;
return target;
}
紧接着将alreadyDispatchedToNewTouchTarget
变量至为true,
最后break循环了,此处才是真正找到了touch的子view了,所以没必要再去找了。到现在为止,newTouchTarget
和mFirstTouchTarget
都不为空,并且两个是指同一个targetView。直接看注释11的位置,由于上面分析alreadyDispatchedToNewTouchTarget=true
以及mFirstTouchTarget==newTouchTarget,因此直接进到if了handled = true;
,走完了action_down
后,我们知道mFirstTouchTarget
和newTouchTarget
都不为空,所以在action_move
和action_up
的时候intercepted=false
,还是会走dispatchTransformedTouchEvent
方法,因此会执行子view的dispatchTouchEvent
方法的action_move和action_down
。所以到此默认情况都走完了一遍。
回顾上一篇遗留的问题
还记得上一篇在view的ontouchEvent
中如果返回false是不是直接不走action_move和action_down呢,不熟悉的view的流程看下上一篇的讲解android中view事件传递,从上面viewgroup的dispatchTouchEvent
方法可以知道,如果子view的ontouchEvent直接返回false,那么mFirstTouchTarget
和newTouchTarget
都为空,那么intercepted=true
,所以不会走if (!canceled && !intercepted) {
该处,直接走到了注释12处,调用了handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS);
此处的child传的是null,咋们可以看下dispatchTransformedTouchEvent如果传的是null会是咋样:
看到了没,直接不走child的
dispatchTouchEvent
方法,因此action_move和action_up都不会走child的dispatchTouchEvent
和ontouchEvent
,同理如果child的dispatchTouchEvent
方法直接返回false也是只走action_down事件。
开篇遗留的问题:点击子view之外的区域,在action_up的时候不走viewgroup的onInterceptTouchEvent方法
经过上面的分析,咋们知道如果点击了子view的话,mFirstTouchTarget
变量才不会为空,因此if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
该if不成立,所以在action_up时不会调用viewgroup的onInterceptTouchEvent
方法,包括action_move
的时候也不会走。
总结
- 在action_down的时候,首先去判断viewgroup的
onInterceptTouchEvent
是不是拦截了,如果拦截的话intercepted=true
,就会走handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS);
方法,此处传的child是null,因此直接走super.dispatchTouchEvent方法,不走child的dispatchTouchEvent
方法 - 如果
onInterceptTouchEvent
不拦截,那么在action_down的时候,去获取child.dispatchTouchEvent方法,如果返回true,那么mFirstTouchTarget
和newTouchTarget
都不为空,因此在action_move和action_up的时候会走child的dispatchTouchEvent
和ontouchEvent
方法 - 如果child的
dispatchTouchEvent
方法返回false或者child的ontouchEvent
返回false,mFirstTouchTarget
和newTouchTarget
都为空,因此在action_move
和action_up
的时候不走child的dispatchTouchEvent
和ontouchEvent
方法。 - 如果点击的是viewgroup,那么viewgroup的
onInterceptTouchEvent
的action_move和action_up都不会被执行。