Android点击事件属于,Android点击事件简单梳理

View与ViewGroup TouchEvent分析

ViewGroup继承于View,而View实现了两个方法 :

dispatchTouchEvent 主要用于分发事件, 在不考虑父view group拦截的情况下,所有touch事件首先产生down事件,父view从上层获取MotionEvent再调用子类的dispatchTouchEvent将事件传递给子View。

在子view的dispatchTouchEvent方法中,优先判定是否存在touchListerner , 如果有则调用touchlisterner的touch方法。当返回true时,dispatchTouchEvent将返回true,并且不再执行自己的onTouchEvent方法。否则dispatchTouchEvent将返回子viewonTouchEvent的返回值 。

当子view在dispatchTouchEvent的down事件里返回true之后,父view会将当前子view设置成targetView。只有target view才会收到后续的move ,up ,或者cancel事件。

说完子view,再看父类的viewGroup, 父view也是由上层调用自己的dispatchTouchEvent来分发MotionEvent,事件的传递逻辑是先调用dispatchTouchEvent方法,然后调用onInterceptTouchEvent判断是否拦截,这里默认是不拦截,拦截的情况后续再说。再之后

相比view ,view group多了一个onInterceptTouchEvent方法。

该方法主要用于决定是否拦截子view获取MotionEvent。默认实现为false,也就是不拦截。当返回true之后,则直接将当子view的后续点击事件传递给父view.

整个流程里最复杂的部分为viewgroup的dispatchTouchEvent方法。

当点击事件为ACTION_DOWN的时候,首先清除当前view group 处理点击事件的TouchTarget链表,恢复view group到默认状态(在detach的时候也会做)。

接着判断是否需要拦截,如果拦截则直接调用dispatchTransformedTouchEvent方法并传入空view(当孩子为空时默认调用自己的super.dispatchTouchEvent方法)。

当不拦截点击事件为ACTION_POINTER_DOWN ||ACTION_POINTER_DOWN的时候,根据从子view找出符合点击区域的孩子依次调用子view的dispatchTouchEvent方法来判断孩子是否处理,不论是否找到都会调用dispatchTransformedTouchEvent方法。当有子view处理的时候会生成一个TouchTarget并加入到链表中。

对于其他的事件,当TouchTarget链表为空时直接调用dispatchTransformedTouchEvent方法并传入空view丢给自己处理,否则遍历TouchTarget链表找到合适的子view处理。

在cancel或者up事件的时候会清理链表。

@Override

public boolean dispatchTouchEvent(MotionEvent ev) {

if (mInputEventConsistencyVerifier != null) {

mInputEventConsistencyVerifier.onTouchEvent(ev, 1);

}

boolean handled = false;

if (onFilterTouchEventForSecurity(ev)) {

final int action = ev.getAction();

final int actionMasked = action & MotionEvent.ACTION_MASK;

/**

* 第一步:对于ACTION_DOWN进行处理(因为ACTION_DOWN是一系列事件的开端)

*

* 1. 清除以往的Touch状态(state)开始新的手势(gesture)

* cancelAndClearTouchTargets(ev)中有一个非常重要的操作:

* 将mFirstTouchTarget设置为null!!!!

* 2. 随后在resetTouchState()中重置Touch状态标识

*/

if (actionMasked == MotionEvent.ACTION_DOWN) {

// Throw away all previous state when starting a new touch gesture.

cancelAndClearTouchTargets(ev);//取消和清除所有的touch target

resetTouchState();//重置所有touch状况,准备新的touch状况周期

}

/**

* 第二步:检查是否要拦截(Check for interception)

* 在哪些情况下会调用该代码呢?有如下几种情况

* 1 处理ACTION_DOWN事件时

* 2 当ACTION_DOWN事件被子View消费后处理ACTION_MOVE和ACTION_UP时

* 因为此时mFirstTouchTarget!=null。所以此时ViewGroup

* 是有机会拦截ACTION_MOVE和ACTION_UP的,但是我们也可以调用方法:

* requestDisallowInterceptTouchEvent来禁止ViewGroup的事件拦截.

* 如果子View没有消费Touch事件,那么那么当后续的ACTION_MOVE和ACTION_UP

* 到来时是不会调用到本处代码的.

*

* ViewGroup.dispatchTouchEven中,使用变量intercepted来标记ViewGroup是否拦截Touch事件的传递.

* 该变量在后续代码中起着很重要的作用.

*

* 从此处if(actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null)及其内部代码可知:

* 当ViewGroup决定拦截事件后,那么后续的点击事件将会默认交给它处理,不再调用onInterceptTouchEvent()

* 因为在处理ACTION_DOWN时如果Touch事件被子View消费,那么mFirstTouchTarget不为空;

* 反之,如果Touch事件没有被子View消费,那么mFirstTouchTarget为空,即此时Touch由当前

* 的ViewGroup拦截。此时当ACTION_MOVE和ACTION_UP来到时,不再满足:

* if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null)

* 当然也就无法调用其内部的onInterceptTouchEvent()。

* 通俗地说:一旦ViewGroup拦截了ACTION_DOWN事件由自身的onTouchEvent()处理,那么

* 对于后续的ACTION_MOVE和ACTION_UP而言ViewGroup不再调用onInterceptTouchEvent()

*

* 这里有个东西需要注意:FLAG_DISALLOW_INTERCEPT

* 在子View中调用requestDisallowInterceptTouchEvent()后造成disallowIntercept为true

* 即禁止拦截.于是不满足if(!disallowIntercept)所以也就调用不到该if内的onInterceptTouchEvent()

* 自然就没有办法拦截了.

* 但是requestDisallowInterceptTouchEvent()对于ACTION_DOWN是无效的.

* 因为对于ACTION_DOWN会调用 cancelAndClearTouchTargets(ev)和resetTouchState();

* 对FLAG_DISALLOW_INTERCEPT等状态值复原重置(参考上面的代码)

*

* 举两种情况说明:

* 1 当处理ACTION_DOWN时当然会满足

* if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null)

* 对于ACTION_DOWN子View有两种处理结果

* 1.1 消耗了Touch事件,那么mFirstTouchTarget不为null.

* 所以处理后续的ACTION_MOVE和ACTION_UP时依然满足该if判断

* 1.2 没有消耗Touch事件.mFirstTouchTarget=null.不满足该if.

* 所以后续的ACTION_MOVE和ACTION_UP由ViewGroup处理,此时再讨论什么拦截也就没有意义了.

* 同样的道理当子View消费了ACTION_DOWN后当处理ACTION_MOVE的时候ViewGroup拦截了该事件

* 那么当ACTION_UP随之到来时由于mFirstTouchTarget=null所以不会再调用该段代码,自然也就

* 不会调用onInterceptTouchEvent()判断是否拦截了.这点在上面的注释也有提及

* 2 当出现1.1的情况时满足该if判断.

* 如果在子View中调用了requestDisallowInterceptTouchEvent()那么就禁止拦截

* 即disallowIntercept=true.所以不满足if (!disallowIntercept)当然也就调用不到

* onInterceptTouchEvent(ev)了,而是执行else{ intercepted = false;}

* 也就是说ViewGroup无法拦截Touch了.

*/

final boolean intercepted;//使用变量intercepted来标记ViewGroup是否拦截Touch事件的传递.

// 事件为ACTION_DOWN或者mFirstTouchTarget不为null(即已经找到能够接收touch事件的目标组件)时if成立

if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {

//判断disallowIntercept(禁止拦截)标志位

//因为在其他地方可能调用了requestDisallowInterceptTouchEvent()改变该值.

final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;

//当禁止拦截为false时(即disallowIntercept为false)调用onInterceptTouchEvent(ev)方法

if (!disallowIntercept) {

//既然disallowIntercept为false那么就调用onInterceptTouchEvent()方法将结果赋值给intercepted

//这就是常说的事件传递流程:dispatchTouchEvent->onInterceptTouchEvent->onTouchEvent

//注意:这是dispatchTouchEvent中唯一调用onInterceptTouchEvent()的地方

//所以,只有2种情况系统会调用onInterceptTouchEvent:

//1.派发action_donwn;2.ViewGroup里有子控件拦截或消费了action_down

//换句话说,若ViewGroup拦截消费了down,那么它在接收后续的up和move时不再调用onInterceptTouchEvent

intercepted = onInterceptTouchEvent(ev);

ev.setAction(action); // restore action in case it was changed

} else {

//禁止拦截的FLAG为ture说明没有必要去执行是否需要拦截了能够顺利通过,所以设置拦截变量为false

//即,当禁止拦截为true时(即disallowIntercept为true)设置intercepted = false

intercepted = false;

}

} else {

//当事件不是ACTION_DOWN并且mFirstTouchTarget为null(即没有Touch的目标组件)时

//设置 intercepted = true表示ViewGroup执行Touch事件拦截的操作。

//There are no touch targets and this action is not an initial down

//so this view group continues to intercept touches.

intercepted = true;

}

/**

* 第三步:检查cancel(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;

//不是ACTION_CANCEL并且ViewGroup的拦截标志位intercepted为false(不拦截)

if (!canceled && !intercepted) { //这是intercepted体现作用的唯一一行代码

//处理ACTION_DOWN事件.这个环节比较繁琐.

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 (childrenCount != 0) {

// 依据Touch坐标寻找子View来接收Touch事件

// Find a child that can receive the event.

// Scan children from front to back.

final View[] children = mChildren;

final float x = ev.getX(actionIndex);

final float y = ev.getY(actionIndex);

final boolean customOrder = isChildrenDrawingOrderEnabled();

// 遍历子View判断哪个子View接受Touch事件

for (int i = childrenCount - 1; i >= 0; i--) {

final int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i;

final View child = children[childIndex];

if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) {

continue;

}

newTouchTarget = getTouchTarget(child);

if (newTouchTarget != null) {

// 找到接收Touch事件的子View!!!!!!!即为newTouchTarget.

// 既然已经找到了,所以执行break跳出for循环

// 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不满足,当然也不会执行break语句.

* 于是代码会执行到这里来.

*

*

* 调用方法dispatchTransformedTouchEvent()将Touch事件传递给子View做

* 递归处理(也就是遍历该子View的View树)

* 该方法很重要,看一下源码中关于该方法的描述:

* 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.

* 将Touch事件传递给特定的子View.

* 该方法十分重要!!!!!!!!!!!!!!!!!

* 在该方法中为一个递归调用,会递归调用dispatchTouchEvent()方法!!!!!!!!!!

* 在dispatchTouchEvent()中:

* 如果子View为ViewGroup并且Touch没有被拦截那么递归调用dispatchTouchEvent()

* 如果子View为View那么就会调用其onTouchEvent(),这个不再赘述.

*

*

* 该方法返回true则表示子View消费掉该事件,同时进入该if判断.

* 满足if语句后重要的操作有:

* 1 给newTouchTarget赋值

* 2 给alreadyDispatchedToNewTouchTarget赋值为true.

* 看这个比较长的英语名字也可知其含义:已经将Touch派发给新的TouchTarget

* 3 执行break.

* 因为该for循环遍历子View判断哪个子View接受Touch事件,既然已经找到了

* 那么就跳出该for循环.

* 4 注意:

* 如果dispatchTransformedTouchEvent()返回false即子View的onTouchEvent返回false

* (即Touch事件未被消费)那么就不满足该if条件.所以也就无法执行addTouchTarget().

* 在此简单说一下addTouchTarget()中涉及到的ViewGroup的一个内部类TouchTarget——它是一个事件链.

* 该处的mFirstTouchTarget就是一个TouchTarget.它保存了可以消耗Touch事件的View.

* 在该处,如果dispatchTransformedTouchEvent()返回true即子View的onTouchEvent返回true则说明

* 该View消耗了Touch事件,那么将该View加入到事件链中!!!!!!!!!!!!!!!

* 尤其注意:

* 这个操作是在处理ACTION_DOWN的代码块里进行的.即是在:

* if (actionMasked == MotionEvent.ACTION_DOWN||

* (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) ||

* actionMasked == MotionEvent.ACTION_HOVER_MOVE)

* 这个大的if判断中处理的.

* 当处理ACTION_MOVE事件和ACTION_UP事件的时候是不会进入这个if判断的!!!!!

* 而是直接从去判断mFirstTouchTarget!!!!!!!!!!!!!!!!

* 所以如果一个View不处理ACTION_DOWN那么该,那么该View是不会保存在mFirstTouchTarget

* 中的,也就无法继续处理ACTION_MOVE事件和ACTION_UP事件!!!!!!!!!!即若该View不消耗

* ACTION_DOWN事件那么系统是不会讲ACTION_MOVE和ACTION_UP事件传给给该View的

* 5 注意:

* 如果dispatchTransformedTouchEvent()返回true即子View

* 的onTouchEvent返回true(即Touch事件被消费)那么就满足该if条件.

* 从而mFirstTouchTarget不为null!!!!!!!!!!!!!!!!!!!

* 6 小结:

* 对于此处ACTION_DOWN的处理具体体现在dispatchTransformedTouchEvent()

* 该方法返回boolean,如下:

* true---->事件被消费----->mFirstTouchTarget!=null

* false--->事件未被消费--->mFirstTouchTarget==null

* 因为在dispatchTransformedTouchEvent()会调用递归调用dispatchTouchEvent()和onTouchEvent()

* 所以dispatchTransformedTouchEvent()的返回值实际上是由onTouchEvent()决定的.

*

* 简单地说onTouchEvent()是否消费了Touch事件(true or false)的返回值决定了

* dispatchTransformedTouchEvent()的返回值!!!!从而决定了mFirstTouchTarget是否为null!!!!!!

* 从而进一步决定了ViewGroup是否处理Touch事件.这一点在下面的代码中很有体现.

*/

if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {

// Child wants to receive touch within its bounds.

mLastTouchDownTime = ev.getDownTime();

mLastTouchDownIndex = childIndex;

mLastTouchDownX = ev.getX();

mLastTouchDownY = ev.getY();

//调用addTouchTarget()将child添加到mFirstTouchTarget链表的表头

//注意在addTouchTarget()方法内部会对mFirstTouchTarget操作,使其不为null

newTouchTarget = addTouchTarget(child, idBitsToAssign);

alreadyDispatchedToNewTouchTarget = true;

break;

}

}//for循环结束

}

/**

* 该if条件表示:

* 经过前面的for循环没有找到子View接收Touch事件并且之前的mFirstTouchTarget不为空

*/

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指向了最初的TouchTarget

newTouchTarget.pointerIdBits |= idBitsToAssign;

}//处理ACTION_DOWN结束

}

}

/**

* 经过上面对于ACTION_DOWN的处理后mFirstTouchTarget有两种情况:

* (当然如果不是ACTION_DOWN就不会经过上面较繁琐的流程而是从此处开始执行,比如ACTION_MOVE和ACTION_UP)

*

* 情况1 mFirstTouchTarget为null

* 即没有找到能够消费touch事件的子组件或者是touch事件被拦截了

* 情况2 mFirstTouchTarget不为null

* 即找到了能够消费touch事件的子组件则后续的touch事件都可以传递到子View

* 这两种情况的详细分析见下.

*

* 这两种情况下都会去调用方法:

* dispatchTransformedTouchEvent(MotionEvent event,boolean cancel,View child,int desiredPointerIdBits)

* 我们重点关注该方法的第三个参数View child.

* 详情请参加下面dispatchTransformedTouchEvent()源码分析

* 在该源码中解释了:

* 为什么子view对于Touch事件处理返回true那么其上层的ViewGroup就无法处理Touch事件了!!!!!!!!!

* 为什么子view对于Touch事件处理返回false那么其上层的ViewGroup才可以处理Touch事件!!!!!!!!!!

*

*/

if (mFirstTouchTarget == null) {

/**

* 情况1:mFirstTouchTarget为null

*

* 经过上面的分析mFirstTouchTarget为null就是说Touch事件未被消费.

* 即没有找到能够消费touch事件的子组件或Touch事件被拦截了,

* 则调用ViewGroup的dispatchTransformedTouchEvent()方法处理Touch事件则和普通View一样.

* 即子View没有消费Touch事件,那么子View的上层ViewGroup才会调用其onTouchEvent()处理Touch事件.

* 在源码中的注释为:No touch targets so treat this as an ordinary view.

* 也就是说此时ViewGroup像一个普通的View那样调用dispatchTouchEvent(),且在dispatchTouchEvent()

* 中会去调用onTouchEvent()方法.

* 具体的说就是在调用dispatchTransformedTouchEvent()时第三个参数为null.

* 第三个参数View child为null会做什么样的处理呢?

* 请参见下面dispatchTransformedTouchEvent()的源码分析

*/

handled = dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS);

} else {

/**

* 情况2:mFirstTouchTarget不为null

* 即找到了可以消费Touch事件的子View且后续Touch事件可以传递到该子View

* 在源码中的注释为:

* 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) {

//如果前面利用ACTION_DOWN事件寻找符合接收条件的子组件的同时消费掉了ACTION_DOWN事件

//那么这里为handled赋值为true

handled = true;

} else {

final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted;

//对于非ACTION_DOWN事件继续传递给目标子组件进行处理

//依然是递归调用dispatchTransformedTouchEvent()

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;

}

}

/**

* 处理ACTION_UP和ACTION_CANCEL

* 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;

}

//=====================以上为dispatchTouchEvent()源码分析======================

//===============以下为dispatchTransformedTouchEvent()源码分析=================

/**

* 在dispatchTouchEvent()中会调用dispatchTransformedTouchEvent()将事件分发给子View处理

*

* 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.

*

* 在此请着重注意第三个参数:View child

* 在dispatchTouchEvent()中多次调用dispatchTransformedTouchEvent(),但是有时候第三个参数为null,有时又不是.

* 那么这个参数是否为null有什么区别呢?

* 在如下dispatchTransformedTouchEvent()源码中可见多次对于child是否为null的判断,并且均做出如下类似的操作:

* if (child == null) {

* handled = super.dispatchTouchEvent(event);

* } else {

* handled = child.dispatchTouchEvent(event);

* }

* 这个代码是什么意思呢?

*

* 当child == null时会将Touch事件传递给该ViewGroup自身的dispatchTouchEvent()处理.

* 即super.dispatchTouchEvent(event)

* 正如源码中的注释描述的一样:

* If child is null, assumes the MotionEvent will be sent to this ViewGroup instead.

*

* 当child != null时会调用该子view(当然该view可能是一个View也可能是一个ViewGroup)的dispatchTouchEvent(event)处理.

* 即child.dispatchTouchEvent(event);

*

* 那么该child是否为null又是由什么决定的呢?

* 在dispatchTouchEvent()中已经知道了:

* 如果Touch事件被消耗掉那么child不为null

* 如果Touch事件未被消耗掉那么child为null

*

* 这就解释了:

* 为什么子view对于Touch事件处理返回true那么其上层的ViewGroup就无法处理Touch事件了!!!!!!!!!

* 为什么子view对于Touch事件处理返回false那么其上层的ViewGroup才可以处理Touch事件!!!!!!!!!!

*/

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) {

//调用自身的dispatchTouchEvent()!!!!!!

handled = super.dispatchTouchEvent(event);

} else {

final float offsetX = mScrollX - child.mLeft;

final float offsetY = mScrollY - child.mTop;

event.offsetLocation(offsetX, offsetY);

//调用子View的dispatchTouchEvent()!!!!!!

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);

}

transformedEvent.recycle();

return handled;

}

//===============以上为dispatchTransformedTouchEvent()源码分析=================

}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值