开发者社区> ksuu> 正文
阿里云
为了无法计算的价值
打开APP
阿里云APP内打开

Android触摸事件(下)——事件的分发

简介: 已经记不清有多久了,貌似自从接触Android开发开始,Android的事件分发机制一直伴随着我们。网上各种大神的各种分析,看完了可能还是会晕晕乎乎的。没办法,谁让咱是菜鸡呢(对!我菜鸡我还有有理了。
+关注继续查看

已经记不清有多久了,貌似自从接触Android开发开始,Android的事件分发机制一直伴随着我们。网上各种大神的各种分析,看完了可能还是会晕晕乎乎的。没办法,谁让咱是菜鸡呢(对!我菜鸡我还有有理了。。)。


img_66c7555967d2f1fb8d2916e4f62b589d.png

前面写过一篇关于Android事件的由来,介绍了Android输入事件从产生到发送到View的过程。今天,就来说说关于事件分发的流程。

1. 事件产生的整个流程(Java层)

img_6b4dfba7601fde494b01c7a785b845f9.png
流程图

从前一篇文章中可以知道,整个流程我是通过debug方式去看方法的调用关系,最终证明是正确的。这里面有个比较有意思的地方:在DecorView和Activity的事件传递,DecorView通过PhoneWindow获取Activity的Window.Callback,并且调用Callback的dispatchTouchEvent方法。而dispatchTouchEvent又通过getWindow方法获得PhoneWindow对象并调用superDispatchTouchEvent方法,通过DecorView调用ViewGroup的dispatchTouchEvent方法进行事件的分发。如果最终事件没有被消费,那么会调用Activity中的onTouchEvent方法。
上面说的过程比较拗口,细细一想就会发现:DecorView通过PhoneWindow来调用Activity的dispatchTouchEvent方法,而Activity又通过PhoneWindow来调用DecorView的superDispatchTouchEvent(这里会调用ViewGroup的dispatchTouchEvent方法)。这里我们就会知道,Activity可能不知道有DecorView这个东西,而DecorView也不知道有Activity这个玩意,但是他俩都有一个好朋友叫PhoneWindow,可以通过PhoneWindow来调用需要的方法,降低耦合度

2. ViewGroup的事件分发

我们都知道ViewGroup事件分发是Parent向Child进行分发,如果Child消费了事件,则返回true告诉Parent;否则,返回false。如果Child都没有消费,那么此时会看Parent是否有消费。

@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)) {
        // 获得Action
        final int action = ev.getAction();
        // Action & 0xff, 正常是的Action包括了1-12,其他的会有ACTION_POINTER_1_DOWN等Action
        // 这里 & 0xff则需要获得1-12的Action
        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) {
            // 是否允许拦截,根据标志位FLAG_DISALLOW_INTERCEPT来判断
            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
            // 为false说明允许拦截,根据onInterceptTouchEvent的返回值来判断是否拦截触摸事件
            if (!disallowIntercept) {
                intercepted = onInterceptTouchEvent(ev);
                // 重新设置MotionEvent的Action防止其被改变
                ev.setAction(action); // restore action in case it was changed
            } else {
                // 不允许拦截的话则设置为false
                intercepted = false;
            }
        } else {
            // There are no touch targets and this action is not an initial down
            // so this view group continues to intercept touches.
            // 如果没有触摸目标并且这个事件不是按下事件,这个ViewGroup需要继续拦截这个事件
            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.
        // FLAG_SPLIT_MOTION_EVENTS在initViewGroup()初始化中设置
        //if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB) {
        //    mGroupFlags |= FLAG_SPLIT_MOTION_EVENTS;
        //}
        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;
            // 如果是按下事件或者是指针按下或者是光标移动的Action,则需要进行分发
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                    || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                // 按下事件总是0
                final int actionIndex = ev.getActionIndex(); // always 0 for down
                // split为true,idBitsToAssign = 1 << ev.getPointerId(actionIndex)
                // 每一个触摸点Pointer都会有一个当次动作序列的唯一Id和Index.MotionEvent中多个手指的操作API大部分都是通过pointerIndex来进行的,
                // 如:获取不同Pointer的触碰位置,getX(int pointerIndex);获取PointerId等等。大部分情况下,pointerId = pointerIndex
                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.
                // 为这个点的id清除之前的触摸目标,防止不同步
                removePointersFromTouchTargets(idBitsToAssign);
                
                final int childrenCount = mChildrenCount;
                // 新的目标为null并且childCount!=0
                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.
                    // 构建触摸分发list,构建的时候有个条件hasChildWithZ,如果没有则返回null,一般返回null
                    final ArrayList<View> preorderedList = buildTouchDispatchChildList();
                    final boolean customOrder = preorderedList == null
                            && isChildrenDrawingOrderEnabled();
                    final View[] children = mChildren;
                    for (int i = childrenCount - 1; i >= 0; i--) {
                        // 获得childIndex
                        final int childIndex = getAndVerifyPreorderedIndex(
                                childrenCount, i, customOrder);
                        // 获得childView
                        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;
                        }
                        // 如果View不能接收PointerEvents(不是VISIBLE或者child.getAnimation() != null)或者该事件的点不在View中
                        if (!canViewReceivePointerEvents(child)
                                || !isTransformedTouchPointInView(x, y, child, null)) {
                            ev.setTargetAccessibilityFocus(false);
                            continue;
                        }
                        
                        // 通过child获得touchTarget,如果有的话说明Child已经接收事件了
                        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.
                            // 设置新的pointerIdBits
                            newTouchTarget.pointerIdBits |= idBitsToAssign;
                            break;
                        }
                        
                        resetCancelNextUpFlag(child);
                        // 这里是分发给child,如果返回true,则说明接收事件
                        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();
                            // 生成一个touchTarget
                            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();
                }
                
                // 没有找到可以接收事件的child,所以把这个指派给最近添加的目标
                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.
        // mFirstTouchTarget不等于null是在addTouchTarget中赋值,这个需要事件分发的时候返回true
        // 这时候可以知道此次事件是被View接收,而上面一部分只是关于DOWN事件的处理,所以一旦有View接收了DOWN事件,
        // 那么接下来的事件都将交给此View处理
        if (mFirstTouchTarget == null) {
            // No touch targets so treat this as an ordinary view.
            // 没有触摸目标,所以把它认为是普通View,调用super.dispatchTouchEvent
            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;
            // 临时变量tagert
            TouchTarget target = mFirstTouchTarget;
            // 链表遍历,target = target.next
            while (target != null) {
                final TouchTarget next = target.next;
                // 这个条件为真的话,是在DOWN事件是有View接收,此时alreadyDispatchedToNewTouchTarget = true
                // 并且mFirstTouchTarget = target = newTouchTarget
                if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                    handled = true;
                } else {
                    // 是否取消,判断flag PFLAG_CANCEL_NEXT_UP_EVENT
                    final boolean cancelChild = resetCancelNextUpFlag(target.child)
                            || intercepted;
                    // 到这边的话此次事件已经不再是DOWN事件,而是MOVE事件,此时分发给target.child,知道最终接收DOWN事件的View
                    // 如果返回true,则设置handled = true
                    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. 通过安全策略检查是否需要过滤此次事件(检查Window是否被覆盖),如果过滤了,则不会处理此次事件;否则进行第2步。
2. 获得事件的Action,并且如果是按下事件的话,则需要清除触摸事件的目标并且重置触摸状态。
3. 检查是否需要拦截此次事件,这时候先判断标志位FLAG_DISALLOW_INTERCEPT是否存在,如果存在则说明不允许进行拦截,否则的话根据onInterceptTouchEvent的返回值来判断是否拦截触摸事件。这里我们可以清楚一点:如果ViewGroup的onInterceptTouchEvent返回true,则不会对事件的进行向下分发。
4. 检查事件是否被取消。
5. 如果事件没有被取消并且没有被拦截,则需要获取事件的id。每一个触摸点Pointer都会有一个当次动作序列的唯一Id。获取不同Pointer的触碰位置可以通过getX(int pointerIndex)方法。大部分情况下,pointerId = pointerIndex
6. 构建分发列表,构建的时候有个条件hasChildWithZ,如果没有则返回null,一般返回null。
7. 对子View进行遍历,如果可以接收事件,那么先去获得触摸目标(getTouchTarget)。如果返回值不为null,则说明子View已经接收事件了,跳出循环;否则,调用dispatchTransformedTouchEvent方法将事件传递给子View。
8. 根据dispatchTransformedTouchEvent返回值来判断是否子View接收了事件。true:接收事件;false:没有接收。
9. 如果没有接收事件,则mFirstTouchTarget = null,所以会调用dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS)这里会调用super.dispatchTouchEvent交给View去处理这次touch事件;如果接收了这次事件,则mFirstTouchTarget !=null 并且alreadyDispatchedToNewTouchTarget = true&&target == newTouchTarget成立,最终handled设置为true。
10. 如果事件不是DOWN事件,那么去寻找是否有TouchTarget,如果有的话,说明有View消费了事件,那么接下来的该事件的其他操作将交由此View处理
说完dispatchTouchEvent方法,这里需要说下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.
    // 事件取消,如果child不为空则向child分发ACTION_CANCEL事件;否则调用super.dispatchTouchEvent
    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.
    // 通过getPointerIdBits获取事件的触碰点的id和传入的进行&运算,如果此时仍和oldPointerIdBits相同,
    // 则说明desiredPointerIdBits包括了oldPointerIdBits,可以理解为是同一个事件
    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.
    // 如果新的事件和旧的事件的getPointerIdBits & desiredPointerIdBits相同,则设置offsetLocation并且直接去进行事件的分发
    // 一般来说单点触摸事件会进行这里
    final MotionEvent transformedEvent;
    if (newPointerIdBits == oldPointerIdBits) {
        if (child == null || child.hasIdentityMatrix()) {
            if (child == null) {
                // 如果没有child的话,则调用View的dispatchTouchEvent方法
                handled = super.dispatchTouchEvent(event);
            } else {
                final float offsetX = mScrollX - child.mLeft;
                final float offsetY = mScrollY - child.mTop;
                event.offsetLocation(offsetX, offsetY);
                // 对child的dispatchTouchEvent方法进行事件分发:
                // 1. 如果child是ViewGroup,则会进行ViewGroup的事件分发过程
                // 2. 如果child是View,则进行View的事件分发过程
                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;
}

这个方法其实总得来说就执行了两个过程:
1. 传入的子View不为null,将事件分发给子View。
2. 传入的子View为null,调用super.dispatchTouchEvent方法去处理。

3. View的dispatchTouchEvent方法

上面说到,如果子View为null,则会调用super.dispatchTouchEvent方法。这里看下View的dispatchTouchEvent方法:

public boolean dispatchTouchEvent(MotionEvent event) {
    ......
    final int actionMasked = event.getActionMasked();
    if (actionMasked == MotionEvent.ACTION_DOWN) {
        // Defensive cleanup for new gesture
        stopNestedScroll();
    }
    // 通过安全策略检查是否需要过滤
    if (onFilterTouchEventForSecurity(event)) {
        // 如果View是enable并且正在处理ScrollBar的鼠标拖拽事件,设置结果为true
        if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
            result = true;
        }
        //noinspection SimplifiableIfStatement
        // 判断ListenerInfo中的OnTouchListener是否为null
        // 不为null,并且ENABLED,则调用mOnTouchListener.onTouch方法
        // 如果mOnTouchListener.onTouch返回true,则结果设为true
        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnTouchListener.onTouch(this, event)) {
            result = true;
        }
        
        // 如果结果为false并且onTouchEvent方法返回值为true,结果设为true
        if (!result && onTouchEvent(event)) {
            result = true;
        }
    }
    ......
    return result;
}

这个方法就简单了很多,因为它作为最终的View(View不包括子View),所能进行的操作要么消费事件要么不消费事件。从方法的实现中也可以看到:

  1. 如果设置了OnTouchListener,那么在执行过程中会先执行OnTouchListener的onTouch方法,接着根据返回值来确定是否需要执行onTouchEvent方法
  2. onTouchEvent是否需要调用是和result的值有关如果result为true,则不调用;反之,则调用

4. 验证

验证过程其是很简单,只要能够进行debug看方法的调用即可。我这里测试的代码如下:


img_87244f9c7d8919f89870813694b84469.png
布局代码

img_cbec7cbb997f4fbb954776ab253fd158.png
测试代码

方法的调用图:


img_093f048f245703a3e825be110716f9d4.png
debug图

可以看到图中是通过ViewGroup一步一步调用到最终的TextView的dispatchTouchEvent方法的,关于为啥会执行这么多次,需要看下页面结构图:
img_fbb113b9f4a805245cc41b9c250e2df7.png
页面结构

从图中可以看到我们最终的TextView被n多个layout包裹,所以会出现ViewGroup中的dispatchTouchEvent方法调用多次。

5. 总结

从源码可以看出:

  • 事件的分发最重要的是对ACTION_DOWN事件的分发,在分发过程中:如果该ViewGroup没有拦截,那么会对其子View进行事件分发。如果子View没有消费事件(返回值为true),那么交由上一级处理。
  • 同一事件的其他操作,例如ACTION_MOVE这个是在处理完ACTION_DOWN事件后进行的,主要是通过查找TouchTarget是否存在来判断是否事件需要传递。
  • View中OnTouchListener的onTouch方法优先级高于onTouchEvent方法,并且onTouchEvent在result为true的时候不会调用。

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
关于Android 日历事件的实现
经常购买火车票,机票的同学就知道,当我们买下一张票的时候,票的行程日期会被写入系统日历中,当火车开动或者飞机启航的前30分钟,手机会有提醒信息,这条信息是由系统日历发出的,提醒用户,别错过时间啦。 像这种系统日历提醒功能,实现起来并不难,毕竟Android 系统已经提供API给我们调用了,不需要重新造轮子,下面我们来实现这个功能。
0 0
RxBus 一个简易、非反射的Android事件通知库
RxBus 一个简易、非反射的Android事件通知库
0 0
Android事件通知工具:RxBus在Eclipse和AS中的实践
Android事件通知工具:RxBus在Eclipse和AS中的实践
0 0
Android Touch事件分发(源码分析)
Android一文让你轻松搞定Touch事件分发 源码分析 Activity事件分发机制 Activity.dispatchTouchEvent()源码 Activity.onTouchEvent()源码 Activity源码总结 ViewGroup事件分发机制 ViewGroup.dispatchTouchEvent()源码 ViewGroup.onInterceptTouchEvent()源码 ViewGroup.onTouchEvent()源码 ViweGroup源码总结 View的事件分发机制 View.dispatchTouchEvent()源码
0 0
Android一文让你轻松搞定Touch事件分发(下)
实例 创建实例 创建MyViewGroup继承ViewGroup 创建MyView继承View 创建TouchActivity继承Activity 创建布局文件 MLog.logEvent() 点击页面,看效果 点击Activity(白色区域) 点击ViewGroup(黄色区域) 点击View(蓝色区域) 结果分析 事件分发和处理 Activity处理和分发 Activity处理 运行结果 Activity分发 ViewGroup拦截处理和分发 ViewGroup拦截处理 运行结果 结果分析 运行结果 ViewGroup分发 View处理和分发 View处理 运行结果 结果分析 View分发
0 0
Android一文让你轻松搞定Touch事件分发(上)
前言 名词了解 什么是事件 事件流 什么是事件分发 思路梳理 ViewGroup View 涉及事件分发的方法 方法的简单用途解析 拥有上述方法的类 事件分发流程
0 0
牛逼!终于有人能把Android事件分发机制讲明白了
在Android开发中,事件分发机制是一块Android比较重要的知识体系,了解并熟悉整套的分发机制有助于更好的分析各种点击滑动失效问题,更好去扩展控件的事件功能和开发自定义控件,同时事件分发机制也是Android面试必问考点之一,如果你能把下面的一些事件分发图当场画出来肯定加分不少。废话不多说,总结一句:事件分发机制很重要。
0 0
Android事件分发机制之ACTION_DOWN
Android的事件分发机制也是老生常谈了,这篇文章并不是笼统的介绍这个机制,而是针对ACTION_DOWN这个事件探讨相关的细节。
0 0
说一说Android事件分发中的requestDisallowInterceptTouchEvent
我们知道在事件分发过程中是存在一个拦截机制的 onInterceptTouchEvent 当它返回true则不向下分发事件,否则向下分发。 但是在这个过程中,还有一个参与者:requestDisallowInterceptTouchEvent,这个函数直接影响事件的拦截。我们今天就来说一说这个这个函数是如何影响事件分发的。
0 0
+关注
文章
问答
文章排行榜
最热
最新
相关电子书
更多
Android组件化实现
立即下载
蚂蚁聚宝Android秒级编译—— Freeline
立即下载
Android插件化:从入门到放弃
立即下载