ViewdispatchTouchEventソース分析(三)

13245 ワード

前節の概要


1、Activity内のイベントは、ActivityでdispatchTouchEventが先に処理されます.2、それからActivityでイベントをDecorViewのsuperDispatchTouchEventに投げて処理します.3.実際にsuperDispatchTouchEventメソッドはViewGroupのdispatchTouchEventメソッドを呼び出します.
だから本当の意味でのdispatchTouchEventの過程は間もなく本格的に始まります.
前節の記事View・InputEventからdispatchTouchEventのソースコード分析(2)を参照してください.

俯瞰dispatchTouchEventメソッド


まず、dispatchTouchEventメソッドを全体的に見てみましょう.
  • 入力されたMotionEventイベントの校正;
  • 補助関連機能;
  • イベント配信;
  • 戻り値handled;
  • @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        // 1、 
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
        }
        // 2、 , 。
        if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
                ev.setTargetAccessibilityFocus(false);
        }
    
        boolean handled = false;
        // 3、 
        if (onFilterTouchEventForSecurity(ev)) {
            // 4、 
        }
    
        if (!handled && mInputEventConsistencyVerifier != null)       
        {
        // 5、 
            mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
        }
        // 6、  handled
        return handled;
    }
    

    イベントのフィルタ、選択された配布イベント


    ウィンドウが非表示になった場合、イベントはフィルタされます.つまり、イベントの配布は実行されません.
       /**
         *  
         *
         * @param event  
         * @return   TRUE, FALSE。
         *
         * @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  , 。
                return false;
        }
        return true;
    }
    

    イベント配信


    イベント配信のコードが長いため,上から下への分解コードで解析する.各セグメントの機能のコードセグメントは、小節として個別に分析されます.

    1、新しいイベントフローかどうかを判断する


    1.1 ViewGroup#dispatchTouchEvent()


    コードでACTIONを認定するDOWNは連続イベントの開始フラグであるため、コードはACTION_を受信するDOWNイベントの場合、直ちにリセット状態操作が行われます.
    アプリケーションの切り替え、ANR、またはその他のステータスの変更により、フレームワークがACTION_を破棄する可能性があります.UPまたはACTION_CANCELイベント.
    final int action = ev.getAction();
    final int actionMasked = action & MotionEvent.ACTION_MASK;
    
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            cancelAndClearTouchTargets(ev);
            resetTouchState();
        }
    

    1.2 ViewGroup#cancelAndClearTouchTargets()

    cancelAndClearTouchTargetsメソッドは、mFirstTouchTargetオブジェクトが空であるかどうかを最初に確認するために実行される.mFirstTouchTargetが空でない場合は、以前のイベントフローが空になっていないことを意味します.mFirstTouchTargetが空であれば、すなわち、イベントフローが以前に存在しなかったことを意味するので、再解放する必要はない.mFirstTouchTargetが空でないことを前提として送信されたeventnullである場合、MotionEventオブジェクトが手動で構築され、イベントタイプがACTION_CANCELに設定される.イベントオブジェクトが存在すると、TouchTargetのチェーンを巡回し、キャンセルイベントの配布を実行します.配布が完了した後、TouchTargetのチェーンを同時にクリアします.
        private void cancelAndClearTouchTargets(MotionEvent event) {
            if (mFirstTouchTarget != null) {
                boolean syntheticEvent = false;
                if (event == null) {
                    //  
                    final long now = SystemClock.uptimeMillis();
                    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);
                    dispatchTransformedTouchEvent(event, true, target.child, target.pointerIdBits);
                }
                clearTouchTargets();
                //  , 
                if (syntheticEvent) {
                    event.recycle();
                }
            }
        }
    

    ここでは,イベントの配布がTouchTargetdispatchTransformedTouchEvent()と密接に関連しているように見えることを観察した.キャンセルイベントの配布は、TouchTargetのチェーンを巡回し、dispatchTransformedTouchEvent()の順に配布することによって完了する.
    そこで、TouchTargetdispatchTransformedTouchEvent()の2つの方法について、イベントの配布の詳細が具体的にどのように実現されているかを見てみましょう.

    1.3 TouchTarget

    TouchTargetの内部には、タッチに関連するターゲット(内部はView)を直列に接続して、後続のイベントの配布を容易にする32サイズの再利用可能なチェーン構造が提供される.
    private static final class TouchTarget {
            private static final int MAX_RECYCLED = 32;
            private static final Object sRecycleLock = new Object[0];
            private static TouchTarget sRecycleBin;
            private static int sRecycledCount;
    
            public static final int ALL_POINTER_IDS = -1; // all ones
    
            //  
            public View child;
    
            //  ,
            // The combined bit mask of pointer ids for all pointers captured by the target.
            public int pointerIdBits;
    
            //   target
            public TouchTarget next;
    
            private TouchTarget() {
            }
    
            public static TouchTarget obtain(@NonNull View child, int pointerIdBits) {
                if (child == null) {
                    throw new IllegalArgumentException("child must be non-null");
                }
    
                final TouchTarget target;
                synchronized (sRecycleLock) {
                    if (sRecycleBin == null) {
                        target = new TouchTarget();
                    } else {
                        target = sRecycleBin;
                        sRecycleBin = target.next;
                         sRecycledCount--;
                        target.next = null;
                    }
                }
                target.child = child;
                target.pointerIdBits = pointerIdBits;
                return target;
            }
    
            public void recycle() {
                if (child == null) {
                    throw new IllegalStateException("already recycled once");
                }
    
                synchronized (sRecycleLock) {
                    if (sRecycledCount < MAX_RECYCLED) {
                        next = sRecycleBin;
                        sRecycleBin = this;
                        sRecycledCount += 1;
                    } else {
                        next = null;
                    }
                    child = null;
                }
            }
        }
    

    1.4 TouchTargetの追加

    addTouchTargetが呼び出されるたびに、内部にTouchTargetオブジェクトが作成(または再利用)され、そのTouchTargetオブジェクトがリストのヘッダに設定されます.
    private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
            final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
            target.next = mFirstTouchTarget;
            mFirstTouchTarget = target;
            return target;
        }
    

    1.5誰かがaddTouchTargetを呼び出した

    ViewGroup#dispatchTouchEvent()法について述べると、ViewGroup を遍歴し、それらを鎖に構築することが分かった.では, と構築された列の対応関係を知ることが重要である.
    // ViewGroup#dispatchTouchEvent
    final int childrenCount = mChildrenCount;
    if (newTouchTarget == null && childrenCount != 0) {
                final float x = ev.getX(actionIndex);
                final float y = ev.getY(actionIndex);
                            ...
                final View[] children = mChildren;
    
                for (int i = childrenCount - 1; i >= 0; i--) {
                                    ...
                     if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                                    ...
                        mLastTouchDownX = ev.getX();
                        mLastTouchDownY = ev.getY();
                        newTouchTarget = addTouchTarget(child, idBitsToAssign);
                        alreadyDispatchedToNewTouchTarget = true;
                        break;
                      }
                                ...
                }           
    }         
    

    1.6初回イベント(ACTION_DOWN)の配布手順


    イベント配信の主線から逸脱しないように,ViewTreeのコード解析を省略する.上記のコードは、ACTION_DOWNイベントを処理する際に実行機会を得、すなわちACTION_DOWNイベントを受信すると、フレームワークがView[]を遍歴することを観察した.一方、View[]の初期化は、addView()の方法に追記することができる.
    したがって、上段のコードは最終的に、View[]を巡回し、Viewブロックイベントの戻り値に基づいて処理チェーンを構築することを表す.ポイントは方法dispatchTransformedTouchEvent()をご覧ください.
    private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
                View child, int desiredPointerIdBits) {
    final boolean handled;
          ... 
    final MotionEvent transformedEvent;
            if (newPointerIdBits == oldPointerIdBits) {
                if (child == null || child.hasIdentityMatrix()) {
                    if (child == null) {
                        handled = super.dispatchTouchEvent(event);
                    } else {
                        final float offsetX = mScrollX - child.mLeft;
                        final float offsetY = mScrollY - child.mTop;
                        event.offsetLocation(offsetX, offsetY);
    
                        handled = child.dispatchTouchEvent(event);
    
                        event.offsetLocation(-offsetX, -offsetY);
                    }
                    return handled;
                }
                transformedEvent = MotionEvent.obtain(event);
            } else {
              ... 
            }
          ... 
    }
    

    親子関係がない場合(ViewGroupにViewがない場合)は、イベントの配布を自分で処理します.そうでない場合は、サブビューに渡してイベントを配布する必要があるかどうかを決定します.

    1.6.1第1回イベント(ACTION_DOWN)の配信プロセスの配信をViewへ

    resulttrue/falseは、Viewが後続のジェスチャーイベントを受け取ることができるかどうかを決定し、下にresultの値を取ることに影響を与える場所がいくつか見られます.
  • onFilterTouchEventForSecurity(event)
  • (mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)
  • li.mOnTouchListener.onTouch(this, event)
  • onTouchEvent(event)

  • 私たちがよく使うonTouchEvent(event)setTouchListener()の方法がイベントの配布に影響を与える根本的な原因です.
    public boolean dispatchTouchEvent(MotionEvent event) {
            ... 
            boolean result = false;
            ...
            final int actionMasked = event.getActionMasked();
            ...
            if (onFilterTouchEventForSecurity(event)) {
                if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
                    result = true;
                }
                //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;
        }
    

    1.6.2第1回イベント(ACTION_DOWN)の配布プロセスの配布からView Groupへ

     @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
    
        boolean handled = false;
        ...
        if (onFilterTouchEventForSecurity(ev)) {
    
                //  
                final boolean intercepted;
                if (actionMasked == MotionEvent.ACTION_DOWN
                        || mFirstTouchTarget != null) {
                    // ViewGroup  
                    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;
                }
        }
        //  ,  ACTION_DOWN  。
        if (!canceled && !intercepted) {
               ... 
        }  
    
       //   ACTION_DOWN  ,  mFirstTouchTarget  。  ViewGroup  。
       if (mFirstTouchTarget == null) {
           //  mFirstTouchTarget, 。
            handled = dispatchTransformedTouchEvent(ev, canceled, null,
            TouchTarget.ALL_POINTER_IDS);
       } else {
                  ...
       }
    }
    

    1.6.3 ACTION_DOWNイベント配信ダイジェスト


    これまで,ACTION_DOWNイベント配信の輪郭が描かれてきた.DecorViewから特定の焦点を得るView(途中でブロックされた場合、ブロックされたView)まで、ACTION_DOWNイベントによってTouchTarget配布チェーンが決定されると、接続されたイベントはいずれもこの配布チェーンによって処理される.

    凡例の説明


    ACTION_DOWNイベント配信
    TouchTargetのチェーンを確認する
    イベント配信時

    本章のまとめ


    1.StageからDecorViewを呼び出す.dispatchTouchEvent 2、DecorViewは、ViewGroupを継承し、dispatchTouchEventメソッドを上書きしていないため、ViewGroupのdispatchTouchEventに処理を任せる.3、まずACTIONであるか否かを判断するDOWNイベントは,初期化,解放などの操作4を展開し,次にViewGroupブロッキングイベントが必要か否かを判断する.
    4.1、ブロックする必要がある場合、View GroupはViewとして配布を処理する.4.2.ブロックを必要としない場合、ViewGroupの下のサブビューを巡回し、サブビューがdispatchTouchEventを処理するかどうかを順次尋ね、問い合わせの軌跡をTouchTargetチェーンの形で記録する.4.3.サブビュー処理が確認されるまで、及びACTION_を終了するDOWN事件の配布.
    5.次に、イベントは、returnのインスタンスに戻るまで、層DecorView毎に行われる.次に、DecorViewのインスタンス制御mFirstTouchTargetオブジェクトによってイベント配信が展開される.6、……続きは次の章で続きます