【轉】Android筆記:觸摸事件的分析與總結----Touch事件分發方法dispatchTouchEvent()源碼分析

原文地址:http://blog.csdn.net/androiddevelop/article/details/18954211 java


觸摸事件學習系列文章詳見:android

《Android Touch事件學習系列彙總》app


當前文章的源碼基於Android 4.0 (Android 14)ide


1、Android如何分發事件

    從上一篇文章《Android Touch事件學習 7 交給哪一個視圖處理事件?》能夠簡單經過LOG角度瞭解下調用次序,本片對其具體分發的源碼進行解讀。佈局

wKioL1QjhA_R90OxAACqAyjiRiU792.jpg


wKiom1QjhACgFDVdAAC0MQWExkE946.jpg


查看點擊綠色視圖的LOG學習

RelativeLayout dispatchTouchEvent ACTION_DOWN
LinearLayout dispatchTouchEvent ACTION_DOWN
CustomView dispatchTouchEvent ACTION_DOWN
CustomView onTouchEvent ACTION_DOWN

RelativeLayout dispatchTouchEvent ACTION_UP
LinearLayout dispatchTouchEvent ACTION_UP
CustomView dispatchTouchEvent ACTION_UP
CustomView onTouchEvent ACTION_UP



2、ViewGroup的子類(各類佈局)如何分發事件?

    ViewGroup的子類都是佈局,例如:RelativeLaout、LiearLayout、FrameLayout、GridLayout、AbsoluteLayout等都是其子類。測試


    結合上面的圖片能夠看到dispatchTouchEvent是針對UI樹形結構由上向下傳遞,執行每個View的dispatchTouchEvent方法,其雖然是View視圖的方法,可是ViewGroup對其進行了覆寫,ViewGroup.dispatchTouchEvent複雜一點,按照上面的圖片和LOG來看是先執行RelativeLayout.dispatchTouchEvent,以後執行LinearLayout.dispatchTouchEvent方法,由於RelativeLayout與LinearLayout都沒有覆寫此方法且都是ViewGroup的子類,因此這兩個ViewGroup在傳遞視圖的時候都是執行ViewGroup.dispatchTouchEvent。動畫


     下面就先來看看ViewGroup.dispatchTouchEvent的源碼,其中註釋中標註數字的地方是在後面會顯示相應的解釋或者方法源碼等。ui

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev)
    {
        // 1. 用於測試目,直接忽略
        if (mInputEventConsistencyVerifier != null)
        {
            mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
        }
        
        boolean handled = false;
        // 2. 未被其餘窗口遮蓋
        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.
                // 3. 清理觸摸操做的全部痕跡,即派發取消操做
                cancelAndClearTouchTargets(ev);
                resetTouchState();
            }
            
            // 檢測是否攔截Touch Event
            // Check for interception.
            final boolean intercepted;
            if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null)
            {
                // 是否容許攔截,能夠經過requestDisallowInterceptTouchEvent方法設置
                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;
            }
            
            // 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;
            // 若是Touch Event沒有取消而且沒有被攔截,纔會考慮是否向其子視圖派發
            if (!canceled && !intercepted)
            {
                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)
                    {
                        // 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);
                        
                        // 遍歷當前ViewGroup的全部子視圖
                        for (int i = childrenCount - 1; i >= 0; i--)
                        {
                            final View child = children[i];
                            // 4 與5
                            if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null))
                            {
                                // 知足以上條件,不必接收Touch Event
                                continue;
                            }
                            
                            // 若是是在子視圖上觸摸,通過以上過濾條件,只有當前手指正下方的子視圖纔會獲取到此事件
                            // 子視圖是否在TouchTarget中
                            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);
                            // 6. 執行觸摸操做(會傳遞到最終視圖的onTouchEvent方法)
                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign))
                            {
                                // Child wants to receive touch within its
                                // bounds.
                                mLastTouchDownTime = ev.getDownTime();
                                mLastTouchDownIndex = i;
                                mLastTouchDownX = ev.getX();
                                mLastTouchDownY = ev.getY();
                                // 會改變mFirstTouchTarget的值
                                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                alreadyDispatchedToNewTouchTarget = true;
                                break;
                            }
                        }
                    }
                    
                    // 沒有發現能夠接收事件的子視圖
                    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;
                    }
                }
            }
            
            // 子視圖未消耗Touch Event 或者 被攔截未向子視圖派發Touch Event
            // Dispatch to touch targets.
            if (mFirstTouchTarget == null)
            {
                // 由當前ViewGroup處理Touch Event
                // 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;
                    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);
            }
        }
        
        // 1. 用於測試目,直接忽略
        if (!handled && mInputEventConsistencyVerifier != null)
        {
            mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
        }
        return handled;
    }


1. View.mInputEventConsistencyVerifierthis

    /**
     * Consistency verifier for debugging purposes.
     * @hide
     */
    protected final InputEventConsistencyVerifier mInputEventConsistencyVerifier =
            InputEventConsistencyVerifier.isInstrumentationEnabled() ? new InputEventConsistencyVerifier(this, 0) : null;


2.  View.onFilterTouchEventForSecurity

    /**
     * Filter the touch event to apply security policies.
     * 
     * @param event
     *            The motion event to be filtered.
     * @return True if the event should be dispatched, false if the event should
     *         be dropped.
     * 
     * @see #getFilterTouchesWhenObscured
     */
    public boolean onFilterTouchEventForSecurity(MotionEvent event)
    {
        // noinspection RedundantIfStatement
        // 當前被其餘窗口遮擋時須要過濾觸摸事件。
        if ((mViewFlags & FILTER_TOUCHES_WHEN_OBSCURED) != 0 && (event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0)
        {
            // Window is obscured, drop this touch.
            return false;
        }
        return true;
    }


3. ViewGroup.cancelAndClearTouchTargets

    /**
     * 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();
                // 使用靜態方法一個MotionEvent對象,動做爲ACTION_CANCEL
                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);
                // 6 向下傳遞觸摸事件(當前傳遞取消操做)
                dispatchTransformedTouchEvent(event, true, target.child, target.pointerIdBits);
            }
            // 清理全部觸摸目標
            clearTouchTargets();
            
            // 註銷拷貝的對象
            if (syntheticEvent)
            {
                event.recycle();
            }
        }
    }


4.canViewReceivePointerEvents

    /**
     * Returns true if a child view can receive pointer events.
     * 
     * @hide
     */
    private static boolean canViewReceivePointerEvents(View child)
    {
        // 當前視圖顯示或者正在執行動畫,才能夠接受觸摸事件
        return (child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null;
    }


5. isTransformedTouchPointInView

    /**
     * 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)
    {
        // 視圖有scrollTo或者scrollBy形成的滾動偏移也須要計算在內
        float localX = x + mScrollX - child.mLeft;
        float localY = y + mScrollY - child.mTop;
        if (!child.hasIdentityMatrix() && mAttachInfo != null)
        {
            final float[] localXY = mAttachInfo.mTmpTransformLocation;
            localXY[0] = localX;
            localXY[1] = localY;
            child.getInverseMatrix().mapPoints(localXY);
            localX = localXY[0];
            localY = localXY[1];
        }
        // 觸摸點是否在當前子視圖內
        final boolean isInView = child.pointInView(localX, localY);
        if (isInView && outLocalPoint != null)
        {
            outLocalPoint.set(localX, localY);
        }
        return isInView;
    }


6. 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();
        // 若是是取消動做
        if (cancel || oldAction == MotionEvent.ACTION_CANCEL)
        {
            event.setAction(MotionEvent.ACTION_CANCEL);
            if (child == null)
            {
                // 調用View類的方法,其內部會調用View.onTouchEvent
                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)
                {
                    // 調用View類的方法,其內部會調用View.onTouchEvent
                    handled = super.dispatchTouchEvent(event);
                }
                else
                {
                    final float offsetX = mScrollX - child.mLeft;
                    final float offsetY = mScrollY - child.mTop;
                    event.offsetLocation(offsetX, offsetY);
                    
                    // 若是當前視圖經過scrollTo或scrollBy對子視圖進行滾動
                    // 向下傳遞的是x,y偏移mScrollX, mScrollY後的值
                    handled = child.dispatchTouchEvent(event);
                    
                    // 一次手勢操做,例如滾動視圖,MotionEvent參數都是同一個對象
                    // 若是有疑問的話,能夠在onTouchEvent中打印其hashCode看下
                    // 因此這裏要對以前作出針對其子視圖的偏移就還原,便於以後使用
                    event.offsetLocation(-offsetX, -offsetY);
                }
                // 若是以上消耗了當前觸摸事件,直接返回
                return handled;
            }
            // 獲取當前參數的拷貝,對其作得修改不會影響當前參數對象
            transformedEvent = MotionEvent.obtain(event);
        }
        else
        {
            transformedEvent = event.split(newPointerIdBits);
        }
        
        // 與以上操做雷同,不具體解釋
        // newPointerIdBits != oldPointerIdBits ?1
        // 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;
    }


3、View而且非ViewGroup子類,如何分發事件?

 圖片是點擊綠色按鈕,並顯示出相應的LOG,而且與文章頂部的圖片是對應的

wKioL1QjhgnAEJuiAABKDf6H_xM297.gif

    以前分析了ViewGroup是如何分發事件的,能夠解釋RelativeLayout與LinearLayout如何分發事件的,下面看下View.dispatchTouchEvent的源碼,能夠了解下View或者ViewGroup(其中能夠調用super.dispatchTouchEvent(event))是如何處理事件的。

    /**
     * Pass the touch screen motion event down to the target view, or this view
     * if it is the target.
     * 
     * @param event
     *            The motion event to be dispatched.
     * @return True if the event was handled by the view, false otherwise.
     */
    public boolean dispatchTouchEvent(MotionEvent event)
    {
        // 用於測試目,直接忽略
        if (mInputEventConsistencyVerifier != null)
        {
            mInputEventConsistencyVerifier.onTouchEvent(event, 0);
        }
        
        // 上面代碼的第2個標註。 未被其餘窗口遮蓋
        if (onFilterTouchEventForSecurity(event))
        {
            // noinspection SimplifiableIfStatement
            if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && mOnTouchListener.onTouch(this, event))
            {
                // 若是有監聽器,執行監聽器的,不在執行當前視圖的onTouchEvent方法
                return true;
            }
            // 執行當前視圖的onTouchEvent方法
            if (onTouchEvent(event))
            {
                return true;
            }
        }
        
        // 用於測試目,直接忽略
        if (mInputEventConsistencyVerifier != null)
        {
            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
        }
        return false;
    }


4、事件分發簡單總結

dispatchTouchEvent事件派發顯示隧道方式、再是冒泡方式 隧道方式傳遞,直道某一個元素消耗此事件,由上至下逐層分發視圖。 冒泡方式傳遞,當某個視圖消耗事件後其return boolean 是與分發相反的方法向上傳遞。 具體分發給哪個視圖是經過當前觸摸點座標在當前層哪一個視圖上判斷onInterceptTouchEvent ViewGroup的方法,若是當前ViewGroup須要攔截傳遞給其子視圖的事件,須要return true

相關文章
相關標籤/搜索