android viewgroup 事件,android中viewgroup的事件传递分析

在上一篇中我们分析了从view的dispatchTouchEvent到onTouchListener的onTouch回调到onTouchEvent到onClickLisener的onClickandroid中view事件传递,在后面遗留了两个问题,那就是在onTouchEvent中返回false的时候,只触发到action_down事件,以及在dispatchTouchEvent中返回false也是只触发到action_down事件,今天就带着这两个问题分析是如何只会执行到action_down事件。

开篇还是用上面的例子,但是因为涉及到viewgroup因此在外层放一个父布局用作监听:

c4cc0ca65578

image.png

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上点击下:

c4cc0ca65578

image.png

c4cc0ca65578

整理的一张默认流程图.png

下面如果再点击testview外面区域的话,看看日志又是如何:

c4cc0ca65578

image.png

此处可以看出来会触发到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 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会是咋样:

c4cc0ca65578

image.png

看到了没,直接不走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都不会被执行。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值