Android実現メカニズム(三)-Viewイベント配信メカニズム


Viewのイベントとは、タッチスクリーンにtouchが到達すると、システムが効果を生み出すプロセスを指し、このプロセスでは主に3つの方法が含まれます.
  • dispatchTouchEvent(MotionEvent ev)
  • onInterceptTouchEvent(MotionEvent ev)
  • onTouchEvent(MotionEvent ev)

  • これらの方法に応答するベクターは、View、View Group、またはActivityであってもよい.ここで、ViewGroupはこの3つの方法のすべてに応答することができ、ActivityはonInterceptTouchEventを処理することができず、Viewは最も原子のコントロールとしてonTouchEventに対応するしかない.
    次にTouchイベントの簡単な分析を行います
    イベント配信:public boolean dispatchTouchEvent(MotionEvent ev)
    Touchイベントが発生したときにActivityのdispatchTouchEvent(MotionEvent ev)メソッドは、ルート要素から最内層のサブ要素に順次伝達されるか、中間のある要素の中である条件によって伝達が停止されるまで、イベントを最外層ViewのdispatchTouchEvent(MotionEvent ev)メソッドに伝達します.イベントは、ビューのdispatchTouchEvent(MotionEvent ev)メソッドによって配布される.dispatchTouchEventのイベント配布ロジックは次のとおりです.
  • return trueの場合、イベントは現在のViewに配布され、dispatchTouchEventメソッドによって消費され、イベントは下への伝達を停止します.
  • return falseの場合、イベント配布は2つのケースに分けられます.
  • 現在のViewが取得したイベントが直接Activityから来た場合、イベントはActivityのonTouchEventに戻されて消費される.
  • 現在のViewが取得したイベントが外部の親コントロールから来た場合、イベントは親ViewのonTouchEventに戻されて消費されます.

  • システムのデフォルトのsuperを返すと.dispatchTouchEvent(ev)では、現在のViewのonInterceptTouchEventメソッドにイベントが自動的に配布されます.

  • イベントブロック:public boolean onInterceptTouchEvent(MotionEvent ev)
    外層ビューのdispatchTouchEvent(MotionEvent ev)メソッドはシステムのデフォルトのsuperを返す.dispatchTouchEvent(ev)の場合、イベントは現在のViewのonInterceptTouchEventメソッドに自動的に配布されます.onInterceptTouchEventのイベントブロックロジックは次のとおりです.
  • onInterceptTouchEventがtrueを返すと、イベントがブロックされ、ブロックされたイベントが現在のViewのonTouchEventに渡されて処理されることを示す.
  • onInterceptTouchEventがfalseに戻ると、イベントがローにされ、現在のView上のイベントがサブViewに渡され、サブViewのdispatchTouchEventによってこのイベントの配布が開始されることを示す.
  • onInterceptTouchEventがsuperに戻る場合.onInterceptTouchEvent(ev)では、イベントはデフォルトでブロックされ、ブロックされたイベントは現在のViewのonTouchEventに渡されます.

  • イベントレスポンス:public boolean onTouchEvent(MotionEvent ev)
    dispatchTouchEventでsuperを返す.dispatchTouchEvent(ev)、onInterceptTouchEventはtrueまたはsuperを返す.onInterceptTouchEvent(ev)の場合onTouchEventが呼び出されます.onTouchEventのイベント応答ロジックは次のとおりです.
  • イベントが現在のViewのonTouchEventメソッドに渡され、このメソッドがfalseを返すと、このイベントは現在のViewから上に渡され、上位のViewのonTouchEventによって受信され、上に渡されたonTouchEventもfalseに戻ると、このイベントは「消失」し、次のイベントは受信されません.
  • trueが返されると、イベントが受信され消費される.
  • superを返すと.onTouchEvent(ev)のデフォルト処理イベントの論理はfalseを返すときと同じです.

  • ここまで,Touchイベントに関する3つのアプローチを解析した.次に、ソースコードについて、ViewとView Groupがなぜ上記の結果を得たのかを分析します.
    Viewイベント配信メカニズム
    まずViewのdispatchTouchEventメソッドを見てみましょう
    public boolean dispatchTouchEvent(MotionEvent event) {  
            if (!onFilterTouchEventForSecurity(event)) {  
                return false;  
            }  
    
            if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&  
                    mOnTouchListener.onTouch(this, event)) {  
                return true;  
            }  
            return onTouchEvent(event);  
        }  

    このビューがonTouchListenerに設定され、このビューがenableであり、onTouchListenerの戻り値がtrueである場合、ViewのonTouchEventは呼び出されません.
    そしてViewのonTouchEventメソッドを見てみましょう
    public boolean onTouchEvent(MotionEvent event) {  
            final int viewFlags = mViewFlags;  
    
            if ((viewFlags & ENABLED_MASK) == DISABLED) {  
                // A disabled view that is clickable still consumes the touch  
                // events, it just doesn't respond to them.  
                return (((viewFlags & CLICKABLE) == CLICKABLE ||  
                        (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));  
            }  
    
            if (mTouchDelegate != null) {  
                if (mTouchDelegate.onTouchEvent(event)) {  
                    return true;  
                }  
            }  
    
            if (((viewFlags & CLICKABLE) == CLICKABLE ||  
                    (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {  
                switch (event.getAction()) {  
                    case MotionEvent.ACTION_UP:  
                        boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;  
                        if ((mPrivateFlags & PRESSED) != 0 || prepressed) {  
                            // take focus if we don't have it already and we should in  
                            // touch mode.  
                            boolean focusTaken = false;  
                            if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {  
                                focusTaken = requestFocus();  
                            }  
    
                            if (!mHasPerformedLongPress) {  
                                // This is a tap, so remove the longpress check  
                                removeLongPressCallback();  
    
                                // 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();  
                                    }  
                                }  
                            }  
    
                            if (mUnsetPressedState == null) {  
                                mUnsetPressedState = new UnsetPressedState();  
                            }  
    
                            if (prepressed) {  
                                mPrivateFlags |= PRESSED;  
                                refreshDrawableState();  
                                postDelayed(mUnsetPressedState,  
                                        ViewConfiguration.getPressedStateDuration());  
                            } else if (!post(mUnsetPressedState)) {  
                                // If the post failed, unpress right now  
                                mUnsetPressedState.run();  
                            }  
                            removeTapCallback();  
                        }  
                        break;  
    
                    case MotionEvent.ACTION_DOWN:  
                        if (mPendingCheckForTap == null) {  
                            mPendingCheckForTap = new CheckForTap();  
                        }  
                        mPrivateFlags |= PREPRESSED;  
                        mHasPerformedLongPress = false;  
                        postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());  
                        break;  
    
                    case MotionEvent.ACTION_CANCEL:  
                        mPrivateFlags &= ~PRESSED;  
                        refreshDrawableState();  
                        removeTapCallback();  
                        break;  
    
                    case MotionEvent.ACTION_MOVE:  
                        final int x = (int) event.getX();  
                        final int y = (int) event.getY();  
    
                        // Be lenient about moving outside of buttons  
                        int slop = mTouchSlop;  
                        if ((x < 0 - slop) || (x >= getWidth() + slop) ||  
                                (y < 0 - slop) || (y >= getHeight() + slop)) {  
                            // Outside button  
                            removeTapCallback();  
                            if ((mPrivateFlags & PRESSED) != 0) {  
                                // Remove any future long press/tap checks  
                                removeLongPressCallback();  
    
                                // Need to switch from pressed to not pressed  
                                mPrivateFlags &= ~PRESSED;  
                                refreshDrawableState();  
                            }  
                        }  
                        break;  
                }  
                return true;  
            }  
    
            return false;  
        }
  • 現在のViewがDisabled状態であり、クリック可能である場合、イベント(return true)が消費される.無視できますが、私たちのポイントではありません.
  • mTouchDelegateが設定されている場合、イベントはエージェントに渡され、直接return trueになります.自分のViewがtouchの範囲を増やすことを望んでいる場合は、TouchDelegateを使ってみてください.ここでもポイントではありません.無視できます.
  • このビューをクリックまたは長押しすることができる場合、trueに戻ってこのイベントを消費したことを示し、イベントタイプを順次判断してどのように操作するかを決定し、イベントタイプにはDOWN、MOVE、UPの3種類の
  • がある.
    まとめ
  • 全体のViewのイベント転送プロセスは:View.dispatchEvent->View.setOnTouchListener->View.onTouchEventはdispatchTouchEventでOnTouchListenerの判断を行い、OnTouchListenerがnullではなくtrueに戻るとイベントが消費され、onTouchEventは実行されないことを示す.そうでない場合はonTouchEventを実行します.
  • onTouchEventのDOWN,MOVE,UP
  • DOWNの場合:
  • まず設定フラグはPREPRESSEDで、mHasPerformedLongPress=falseを設定します.そして115 ms後のmPendingCheckForTapを発行します.
  • 115 ms内にUPがトリガされていない場合は、フラグをPRESSEDに設定し、PREPRESSEDフラグをクリアするとともに、500-115 msの遅延を発行し、長押しタスクメッセージを検出する.
  • 500 ms以内(DOWNトリガから計算開始)であればLongClickListenerがトリガーされる:このときLongClickListenerがnullでない場合はコールバックが実行され、LongClickListenerである.onClickはtrueを返し、mHasPerformedLongPressをtrueに設定します.そうでなければmHasPerformedLongPressはfalseのままです.

  • MOVE時:
  • は主にユーザーがコントロールを切り出したかどうかを検出することであり、もし切り出した場合:115 ms内で、直接mPendingCheckForTapを削除する.115 ms後、フラグのPRESSEDを除去し、同時に長押しのチェックを除去する:removeLongPressCallback()

  • UP時:
  • 115 ms内でUPがトリガーされ、このときPREPRESSEDがマークされると、UnsetPressedState,setPressed(false)が実行される.setPressは転送され、ViewでdispatchSetPressedメソッドを複写して受信できます.
  • 115 ms-500 ms間、すなわち長押しが発生していない場合、まず長押し検出を除去し、onClickコールバックを実行する.
  • が500 ms以降である場合、a.onLongClickListenerが設定、onLongClickListenerが設定されている.onClickがtrueを返すと、クリックイベントOnClickイベントはトリガーできません.b.onLongClickListenerまたはonLongClickListenerは設定.onClickがfalseに戻ると、イベントOnClickイベントをクリックしてもトリガーできます.
  • 最後にmUnsetPressedStateを実行する.run()は、setPressedを渡し、PRESSED IDを除去します.