dispatchTouchEventソース分析

24855 ワード

概要


dispatchTouchEventイベントの配布の理解を通してandroidイベントの処理メカニズムを理解する

イベント配信プロセス


まず、イベントがシステムによって現在のActivityに渡されたことを確認し、Activityによって配布が開始されます.主なプロセス:
Activity -> PhoneWindow -> DecorView -> ViewGroup -> … -> View
ちょっと見てdispatchTouchEvent()ソースコード
    /**
     * Called to process touch screen events.  You can override this to
     * intercept all touch screen events before they are dispatched to the
     * window.  Be sure to call this implementation for touch screen events
     * that should be handled normally.
     *
     * @param ev The touch screen event.
     *
     * @return boolean Return true if this event was consumed.( , true)
     */
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            //  
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }

ソースコードには、getWindow()の場合、Activity dispatchTouchEventでWindowのsuperDispatchTouchEventが呼び出されていることがわかる.superDispatchTouchEvent(ev)がtrueを返すと、このイベントは消費されます.そうしないと、ActivityのonTouchEventメソッドを呼び出し、Windowがどのようにイベントを配布しているかを見てみましょう.
   @Override
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return mDecor.superDispatchTouchEvent(event);
    }

Windowは、mDecorのsuperDispatchTouchEventを呼び出します.mDecorって何??
  // This is the top-level view of the window, containing the window decor.( )
    private DecorView mDecor;

    private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {

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

FrameLayoutのdispatchTouchEventを呼び出しました.ここではDecorViewとは何かを説明します.DecorViewはFrameLayoutから継承されています.これはインタフェース全体の最外層のView Groupです.つまりActivity全体のルートレイアウトの外にはDecorViewが包まれており、私たちの携帯電話のタイトルバーはDecorViewに表示されています.これでTouchイベントはActivityから最上位のViewに移行しました.引き続きDecorViewがどのように配布されているかを見てみましょう.
    /**
     * {@inheritDoc}
     */
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
        }

        if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
            ev.setTargetAccessibilityFocus(false);
        }

        boolean handled = false;
        if (onFilterTouchEventForSecurity(ev)) {
            final int action = ev.getAction();
            final int actionMasked = action & MotionEvent.ACTION_MASK;

            if (actionMasked == MotionEvent.ACTION_DOWN) {
                //  
                cancelAndClearTouchTargets(ev);
                resetTouchState();
            }

            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); 
                } else {
                    intercepted = false;
                }
            } else {
                intercepted = true;
            }

            if (intercepted || mFirstTouchTarget != null) {
                ev.setTargetAccessibilityFocus(false);
            }

            final boolean canceled = resetCancelNextUpFlag(this)
                || actionMasked == MotionEvent.ACTION_CANCEL;

            final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
            TouchTarget newTouchTarget = null;
            boolean alreadyDispatchedToNewTouchTarget = false;
            if (!canceled && !intercepted) {
                   //  
                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;

                    removePointersFromTouchTargets(idBitsToAssign);

                    final int childrenCount = mChildrenCount;
                    if (newTouchTarget == null && childrenCount != 0) {
                       // Touch 
                        final float x = ev.getX(actionIndex);
                        final float y = ev.getY(actionIndex);
                        final ArrayList preorderedList = buildOrderedChildList();
                        final boolean customOrder = preorderedList == null
                            && isChildrenDrawingOrderEnabled();
                        final View[] children = mChildren;
                        // View Touch 
                        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 (childWithAccessibilityFocus != null) {
                                if (childWithAccessibilityFocus != child) {
                                    continue;
                                }
                                childWithAccessibilityFocus = null;
                                i = childrenCount - 1;
                            }

                            if (!canViewReceivePointerEvents(child)
                                || !isTransformedTouchPointInView(x, y, child, null)) {
                                ev.setTargetAccessibilityFocus(false);
                                continue;
                            }

                            newTouchTarget = getTouchTarget(child);
                            if (newTouchTarget != null) {
                                newTouchTarget.pointerIdBits |= idBitsToAssign;
                                break;
                            }

                            resetCancelNextUpFlag(child);
                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                                mLastTouchDownTime = ev.getDownTime();
                                if (preorderedList != null) {
                                    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;
                            }

                            ev.setTargetAccessibilityFocus(false);
                        }
                        if (preorderedList != null) preorderedList.clear();
                    }

                    if (newTouchTarget == null && mFirstTouchTarget != null) {
                        newTouchTarget = mFirstTouchTarget;
                        while (newTouchTarget.next != null) {
                            newTouchTarget = newTouchTarget.next;
                        }
                        newTouchTarget.pointerIdBits |= idBitsToAssign;
                    }
                }
            }

            if (mFirstTouchTarget == null) {
                //  (child view , view )
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                    TouchTarget.ALL_POINTER_IDS);
            } else {

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

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

コードが少し多いので、簡単に4つの部分に分けて分析しました.
第1部:
   private void cancelAndClearTouchTargets(MotionEvent event) {
       ...
       clearTouchTargets();
       ...
    }

   private void clearTouchTargets() {
        TouchTarget target = mFirstTouchTarget;
        if (target != null) {
            do {
                TouchTarget next = target.next;
                target.recycle();
                target = next;
            } while (target != null);
            mFirstTouchTarget = null;
        }
    }

ACTION_DOWN時に初期化と復元を行います.mFirstTouchTargetをcancelAndClearTouchTargets()でnullに設定し、resetTouchState()でTouchステータスIDをリセットします.
第2部:
    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;
    }

ViewGroupがTouchイベントをブロックする必要があるかどうかを確認します.
まず一番外の判断条件を見てみましょう.
actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null
ACTION_DOWNはよく理解していますが、mFirstTouchTargetとは何ですか??mFirstTouchTargetはTouchTargetクラスのオブジェクトであり、TouchTargetはView Groupの内部クラスであり、タッチされたViewおよび今回のタッチに対応するIDがカプセル化されている.mFirstTouchTargetは後で分析されるが、ここでは役に立つため、FirstTouchTargetの役割を先に説明する
(1) mFirstTouchTarget!= nullは、ViewGroupがTouchイベントをブロックせず、サブViewがTouchを消費したことを示している.
(2)mFirstTouchTarget==nullは、ViewGroupがTouchイベントをブロックしているか、ViewGroupがTouchイベントをブロックしていないがサブViewもTouchを消費していないことを示している.つまり、この場合はViewGroup自身がTouchイベントを処理する必要がある
つまりchild viewに消費ACTIONがなければDOWNは、その後のmoveとupが下に伝わらず、parent viewにブロックされます.事件がブロックされたら何をするのか、ブロックされていなければ何をするのか.
まずintercepted=falseがブロックしていないことから見てみましょう.
if (!canceled && !intercepted) {

    ...

    if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
        // Child wants to receive touch within its bounds.
        newTouchTarget = addTouchTarget(child, idBitsToAssign);
        alreadyDispatchedToNewTouchTarget = true;
        break;
    }

    ....

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

      if (child == null) {
            handled = super.dispatchTouchEvent(event);
      } else {
            handled = child.dispatchTouchEvent(event);
      }

    }
    ...

    private TouchTarget addTouchTarget(View child, int pointerIdBits) {
        TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
        target.next = mFirstTouchTarget;
        mFirstTouchTarget = target;
        return target;
    }
}

このステップでは、Touchイベントを消費できるサブViewが見つかった場合にのみ、mFirstTouchTargetはnullではありません.残りの場合、例えばTouchイベントを受信できるサブViewが見つからなかったり、サブViewがTouchイベントを消費できなかったりした場合、mFirstTouchTargetはnullのままです.
Touchイベントがキャンセルされずブロックされなかった場合、ViewGroupはACTION_のタイプになります.DOWNのTouchイベントはchild Viewに配布される.
(1) child == null
ViewGroupはoverrideでdispatchTouchEventメソッドを使用していますが、ViewGroupのdispatchTouchEventにはsuper親がいません.child==nullの場合、ViewGroupを通常のViewとして処理します.
(2) child != null呼び出しchild.dispatchTouchEventは配布を継続
intercepted=trueブロック
if (intercepted || mFirstTouchTarget != null) {
    ev.setTargetAccessibilityFocus(false);
}

...

if (mFirstTouchTarget == null) {
                // No touch targets so treat this as an ordinary view.
     handled = dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS);
}


...

intercepted=true,if(!canceled&&!intercepted){…}が実行されないためmFirstTouchTarget==null,child=null,ViewGroupを通常のViewとして扱う(通常のViewのdispatchTouchEventはonTouchEvent()を呼び出す)
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;
      }
   }
}

onTouchEventがfalseに戻ると、mFirstTouchTargetはnullとなり、ViewGroupを通常のViewとしてdispatchTouchEvent(onTouchEvent)を呼び出す.これは、child viewがこのイベントを消費できない場合、parentに返されるonTouchEventで消費され、parent viewも消費できず、ActivityのdispatchTouchEventに渡されるまでアップロードを続けるが、このときgetWindow()となる.superDispatchTouchEvent=falseで、ActivityのonTouchEventが呼び出されます.

まとめ


通俗的な言葉でまとめると、イベントが来たとき、ActivityはWindow、Windowというイベントを消費できるかどうかを尋ねます.Windowを見て、ちょっと待ってください.DecorViewに消費できるかどうかを聞いてみます.DecorViewを見て、onInterceptTouchEventはfalseに戻りましょう.止めさせないでください.すぐにViewを見て、彼らが消費できるかどうかを聞いてみましょう.あの人、イベントはあなたの体にかかっています.あなたが消費できるかどうかを見てください.RelativeLayoutは見ても、私に止めさせなかったでしょう.私もこの事件があの子Viewの上で発生したことを見なければなりません.そのTextView、事件はあなたの体にあります.あなたは彼を消耗することができますか.TextViewを見ると、消費できないよ、RelativeLayoutはTextViewを見ると消費できないよ、mFirstTouchTarget=nullよ、はい、自分で消費しましょう、うん!自分のonTouchEventを見ても消費できないでしょう!あのDecorView事件は私には消耗できません.DecorViewは自分を見ると、私も消耗できません.アップロードし続けます.あのWindowですね.イベントは消費できないよ、WindowはActivityイベントは消費できないと言ったよ.Activityは自分で来なければなりませんね.自分のonTouchEventを呼び出すと、やはり消費できません.もういいです.もういいです.

ぎじふごう

public boolean dispatchTouchEvent(MotionEvent ev) {
    boolean result = false;             //  

    if (!onInterceptTouchEvent(ev)) {   //  View
        result = child.dispatchTouchEvent(ev);
    }

    if (!result) {                      //  , onTouchEvent
        result = onTouchEvent(ev);
    }

    return result;
}

子View連ACTION_DOWNが消費できない場合、mFirstTouchTarget==null、その後のACTION_UP,ACTION_MOVE子Viewも二度と届かない