触摸事件分发源码分析(十)

1,触摸事件

手机屏幕就是人机交互的入口。

触摸事件接口(KeyEvent):

ACTION_UP

ACTION_DOWN

ACTION_CANCEL

ACTION_MOVE

2,触摸事件分发处理

分发处理流程如下,


ViewRootImpl的内部类ViewPostImeInputStage的processPointerEvent方法会调用View的dispatchPointerEvent方法,

boolean handled = mView.dispatchPointerEvent(event);

mView变量是在setview方法中赋值的,对于应用窗口来说, 

mView变量指向PhoneWindow的内部类DecorView对象,但是调用的是父类View的dispatchPointerEvent方法,

public final boolean dispatchPointerEvent(MotionEvent event) {
        if (event.isTouchEvent()) {
            return dispatchTouchEvent(event);
        } else {
            return dispatchGenericMotionEvent(event);
        }
}

DecorView的dispatchTouchEvent方法如下,

public boolean dispatchTouchEvent(MotionEvent ev) {
            final Callback cb = getCallback();
            return cb != null && !isDestroyed() && mFeatureId < 0 ? cb.dispatchTouchEvent(ev)
                    : super.dispatchTouchEvent(ev);
        }

activity和Dialog都是Callback接口的具体实现,主要看activity的dispatchTouchEvent方法,

public boolean dispatchTouchEvent(MotionEvent ev) {
        •••
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
}

首先调用PhoneWindow的superDispatchTouchEvent方法,如果未处理才继续调用onTouchEvent方法, superDispatchTouchEvent方法如下,

public boolean superDispatchTouchEvent(MotionEvent event) {
        return mDecor.superDispatchTouchEvent(event);
}

直接调用DecorView的superDispatchTouchEvent 

public boolean superDispatchTouchEvent(MotionEvent event) {
            return super.dispatchTouchEvent(event);
        }

这一步骤才终于调用到View的树结构中了,

首先看看ViewGroup的dispatchTouchEvent方法,

public boolean dispatchTouchEvent(MotionEvent ev) {
•••
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 (!canceled && !intercepted) {
•••
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;
                            }
•••
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;
                            }
•••

2.1 onInterceptTouchEvent

变量mGroupFlags的值决定是否允许截获触屏消息,如果允许才会调用onInterceptTouchEvent方法进行处理.

一般在默认情况下, ViewGroup都是允许截获触屏消息,可以调用requestDisallowInterceptTouchEvent方法禁止ViewGroup截获触屏消息。该方法如下,

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

2.2 触摸事件分发

逐个遍历ViewGroup的子view,对于一个view调用isTransformedTouchPointInView方法判断触摸坐标是否在该view之内,如果不是,则遍历下一个,如果是,则调用dispatchTransformedTouchEvent方法处理, isTransformedTouchPointInView方法如下,

protected boolean isTransformedTouchPointInView(float x, float y, View child,
            PointF outLocalPoint) {
        final float[] point = getTempPoint();
        point[0] = x;
        point[1] = y;
        transformPointToViewLocal(point, child);
        final boolean isInView = child.pointInView(point[0], point[1]);
        if (isInView && outLocalPoint != null) {
            outLocalPoint.set(point[0], point[1]);
        }
        return isInView;
    }

View的pointInView方法如下,

final boolean pointInView(float localX, float localY) {  // 判断是否在矩形框之内
        return localX >= 0 && localX < (mRight - mLeft)
                && localY >= 0 && localY < (mBottom - mTop);
    }

dispatchTransformedTouchEvent的逻辑也很简单,

 如果该view没有子view,就调用父类,即view类的dispatchTouchEvent,

如果该view有子类,则调用子类的dispatchTouchEvent方法,注意,在该处可能存在递归调用问题,也就是ViewGroup包含ViewGroup的情况。

2.3 dispatchTouchEvent

View的dispatchTouchEvent方法如下,

public boolean dispatchTouchEvent(MotionEvent event) {
       ••• 
        if (onFilterTouchEventForSecurity(event)) {
            //noinspection SimplifiableIfStatement
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }

            if (!result && onTouchEvent(event)) {
                result = true;
            }
        }
•••
        return result;
    }

首先调用view的触摸监听方法onTouch,如果没有监听对象或者未处理,才调用onTouchEvent方法,

public boolean onTouchEvent(MotionEvent event) {
        android.util.SeempLog.record(3);
        final float x = event.getX();
        final float y = event.getY();
        final int viewFlags = mViewFlags;
        final int action = event.getAction();

    •••
            switch (action) {
                case MotionEvent.ACTION_UP:
                   •••
                            // Only perform take click actions if we were in the pressed state
                            if (!focusTaken) {
                                // Use a Runnable and post this rather than calling
                                // performClick directly. This lets other visual state
                                // of the view update before click actions start.
                                if (mPerformClick == null) {
                                    mPerformClick = new PerformClick();
                                }
                                if (!post(mPerformClick)) {
                                    performClick();
                                }
                            }
                        }
                       •••
                case MotionEvent.ACTION_DOWN:
                   •••
                    } else {
                        // Not inside a scrolling container, so show the feedback right away
                        setPressed(true, x, y);
                        checkForLongClick(0);
                    }
                    break;

                case MotionEvent.ACTION_CANCEL:
                    setPressed(false);
                    removeTapCallback();
                    removeLongPressCallback();
                    mInContextButtonPress = false;
                    mHasPerformedLongPress = false;
                    mIgnoreNextUpEvent = false;
                    break;

                case MotionEvent.ACTION_MOVE:
                    drawableHotspotChanged(x, y);

                    // Be lenient about moving outside of buttons
                    if (!pointInView(x, y, mTouchSlop)) {
                        // Outside button
                        removeTapCallback();
                        if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                            // Remove any future long press/tap checks
                            removeLongPressCallback();

                            setPressed(false);
                        }
                    }
                    break;
            }
            return true;
        }
        return false;
    }

View中分别对四种不同的触摸事件分别处理, checkForLongClick和performClick方法在按键事件分发一文中已经论述过了,在此不重复。

小结:

1,触摸事件首先是拦截,然后是分发,一直到viw才处理。

2,分发流程从Activity到ViewGroup,然后到View。只有ViewGroup才可以蓝牙触摸消息。

3, 处理都是子view优先,子view未处理时父view才会处理。

4,如果view都未处理,则Activity的onTouchEvent方法做最后的处理。

比较

分发:都是从上到下传递,很具有层次性。

触摸消息分发时,需要根据触摸坐标分给哪个view/viewgroup,而按键事件直接分配给获取焦点的那个view。

消费:

触摸消息消费时,子视图先处理,如果未处理然后才到父视图。

按键消息消费时,父视图先处理。

正因为如此,在分发触摸按键时, viewgroup才有消息onInterceptTouchEvent(拦截)函数。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android触摸事件分发是指在屏幕上发生触摸事件时,Android系统将该事件分发给适当的视图处理。触摸事件分发的过程涉及多个层级,包括Activity、ViewGroup和View。 当用户触摸屏幕时,Android系统首先将触摸事件发送给当前活动的Window。然后,Window将事件传递给顶级ViewGroup(通常是根布局),该ViewGroup负责协调子视图的事件处理。 在ViewGroup中,触摸事件会按照一定的规则进行分发。常见的分发方式有以下几种: 1. 捕获阶段(Capture Phase):从根布局向下遍历,让父级ViewGroup有机会拦截事件。可以通过重写`onInterceptTouchEvent()`方法来实现事件的拦截。 2. 目标阶段(Target Phase):如果没有被拦截,触摸事件将传递给目标View,即最终接收事件的视图。目标View将调用`onTouchEvent()`方法处理事件。 3. 冒泡阶段(Bubble Phase):如果目标View没有消耗事件事件将向上传递给父级ViewGroup,直到根布局。在这个阶段,可以通过返回值来控制是否继续向上传递。 除了上述的默认分发方式外,还可以通过重写`dispatchTouchEvent()`方法来自定义事件分发逻辑。通过调用`super.dispatchTouchEvent()`来保持默认行为,或者根据需求进行处理。 总结来说,Android触摸事件分发涉及捕获阶段、目标阶段和冒泡阶段,通过重写相关方法或自定义分发逻辑,可以实现对触摸事件的处理和控制。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值