Androidイベント配信メカニズムの探索

8937 ワード

まず,クリックイベントの配信とは,実際にはMotionEventイベントを配信する過程であることを理解する.MotionEventが生成されると、システムはこのイベントを特定のViewに渡す必要があります.この伝達プロセスは配布プロセスです.それ以外に、同じイベントシーケンスとは、指がスクリーンに触れた瞬間から、指がスクリーンから離れた瞬間に終わることを意味し、この過程で発生した一連のイベントであり、この時間シーケンスはdownイベントで始まり、中には数の不定のmoveイベントが含まれ、最終的にupイベントで終わる.
Activityによるクリックイベントの配布プロセス
クリック操作が発生すると、イベントは現在のActivityに最初に渡され、ActivityはdispatchTouchEvent()を通過します.
public boolean dispatchTouchEvent(MotionEvent ev) {
      ...
      if (getWindow().superDiapatchTouchEvent(ev)) {    //     window superDispatchTouchEvent()  ,     ,   true,       
            return true;
      }
      return onTouchEvent(ev);   //  window superDispatchTouchEvent()      ,   false,   Activity  onTouchEvent(ev);  
}

Window(PhoneWindow)のsuperDispatchTouchEvent()メソッドを見てみましょう
public boolean superDispatchTouchEvent(MotionEvent ev){
      return mDecor.superDispatchTouchEvent(ev);         //    DecorView superDispatchTouchEvent(ev)  
}

DecorViewでは、最上位のdispatchTouchEvent(ev)メソッドを呼び出してMotionEventイベントを最上位のViewに渡します.
トップレベルのView(View Group)によるクリックイベントの配布プロセス
この部分の擬似コードは以下のように、基本的にこの過程の思想を含んでいる.
public boolean dispatchTouchEvent(MotionEvent ev){
      boolean consume = false;
      if (onInterceptTouchEvent(ev)){
            consume = onTouchEvent(ev)
      } else {
            consume = child.dispatchTouchEvent(ev)
      }
      return consume;
}

上のコードは、MotionEventイベントが渡された後、まずonInterceptTouchEvent(ev)の方法でイベントをブロックするかどうかを判断し、ブロックする場合はonTouchEvent(ev)の処理にMotionEventイベントを渡し、戻り結果を変数に保存する処理の考え方を明確に示している.ブロックしない場合、MotionEventイベントはサブview、すなわちchild.dispatchTouchEvent(ev)に渡され、戻り結果は変数に保存される.最後に結果変数を返します.結果変数のデフォルトはfalseです.
次にソースコードによって、主にViewGroupのdispatchTouchEvent()メソッドで発生するMotionEventがViewGroupで配布されるプロセスを探索する.
  • 最初のステップは、現在のViewがクリックイベントの下のコードのFLAG_をブロックするか否かを判断するDISALLOW_INTERCEPTタグビットはrequestDisallowInterceptTouchEvent()の方法で設定され、一般的にサブviewに用いられる.FLAG_DISALLOW_INTERCEPTが設定されると、ViewGroupはすでに子Viewの「ディスク」(mFirstTouchTarget ! = null)があるMove,Upイベントをブロックできません.ViewGroupでACTION_を受信しているのでDOWNイベント後、FLAG_がリセットされますDISALLOW_INTERCEPTというフラグビット.
  • //check for interception.
    final boolean intercepted;  //           View      
    if ( actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
          //mFirstTouchTarget != null              ,           ,      dispatchTouchEvent(event)   true
          final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
          if (!disallowIntercept){
                intercepted = onInterceptTouchEvent(ev);
                ev.setAction(action);
          } else {
                intercepted = false;
          }
    } else {       //    move  up,   view           (mFirstTouchTarget == 0)
          intercepted = true;
    }
    
  • 第2のステップは、第1のステップで判断された場合、ntercepted == trueであり、現在のViewは、イベントをブロックして処理する準備をしている.onTouchListener()が設定されている場合、onTouchListener()onTouch()メソッドがコールバックされます.このとき、イベントがどのように処理を継続するかはonTouch()の戻り値にも依存し、falseが返されると、現在のviewのonTouchEvent()メソッドが呼び出される.trueが返されると、onTouchEvent()メソッドは呼び出されません.onTouchEvent()メソッドでは、現在onClickListenerが設定されている場合、onClick()メソッドが呼び出されます.
  • 第3のステップでは、ViewGroupがイベントをブロックしない場合、イベントはサブviewによって処理されます.理解を容易にするために、以下では、配布に関連する主なプロセスを擬似コードで表す.
  • for (     childview){
          if(! childview        ) {                 //        view                    view    
          continue;                                          //           , continue,      view
          }
          if(dispatchTransformedTouchEvent(ev,false,child,idBitsToAssign)){        //            View dispatchTouchEvent() ,   view dispatchTouchEvent()  true,        
                                                                                 //   view dispatchTouchEvent()  fasle,        
                ...
                newTouchTarget = addTouchTarget(child, isBitsToAssign);   // mFirstTouchTarget  
                alreadyDispatchedToNewTouchTarget = true;
                break;                                                                                   //    
          }
    }
    

    次はdispatchTransformedTouchEvent(ev,false,child,idBitsToAssign)のコードの一部です
    if(child == null){
          handle = super.dispatchTouchEvent(event);
    } else {
          handle = child.dispatchTouchEvent(event);
    }
    
    addTouchTarget()メソッドのコードを次に示します.
    private TouchTarget addTouchTarget(View child, int pointerIdBits){
          TouchTarget target = TouchTarget.obtain(child,pointerIdBits);
          target.next = mFirstTouchTarget;
          mFirstTouchTarget = target;
          return target;
    }
    
    
  • すべての要素を巡回した後、イベントが適切に処理されていない場合、この2つの状況に分けられます.1つ目は、ViewGroupがイベントを受け入れることができるサブViewがないことです.2つ目のケースは、サブViewがクリックイベントを処理したが、dispatchTouchEventでfalseが返され、一般的にはonTouchEvent()でfalseが返されたためである.どちらの場合も、ViewGroupはクリックイベントを自分で処理します.
  • if (mFirstTouchTarget == null) {
          handle = dispatchTransformedTouchEvent(en,canceled,null,TouchTarget.ALL_POINTER_IDS);
    }
    

    3番目のパラメータはchildがnullに変更されたため、super.dispatchTouchEvent(event);、すなわちViewGroup親ViewのdispatchTouchEvent()メソッドが呼び出されます.ViewのdispatchTouchEvent()の方法は以下の分析を参照してください.
    ビューのクリックイベントの処理手順
    現在議論されているViewには、Viewを継承したView Groupは含まれていません.次はViewのdispatchTouchEvent()メソッドです
    public boolean dispatchTouchEvent(MotionEvent event){
          boolean result = false;
          ...
          if (onFilterTouchEventForSecurity(event)){
    
                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;
    }
    

    ビューは、クリックイベントをサブビュー(サブビューなし)に渡す必要がないため、ビューのdispatchTouchEvent()の方法は比較的簡単である.第1のステップは、onTouchListenerが設定されているかどうかを判断し、そのうちのonTouch()メソッドがtrueを返す場合、onTouchEvent()メソッドは呼び出されない.このように、onTouchListenerの優先度はonTouchEventより高く、クリックイベントの外部処理に有利である.
    以下、onToucEvent()のコードの一部であり、ビューが使用不可能な状態でイベントをクリックする処理手順である.ビューが使用不可能な状態であっても、ビューのCLICKABLEおよびLONG_CLICKABLEがtrueである限り、このイベントは消費され、trueに戻る(onTouchEvent()メソッドが終了する).
    //       View onTouchEvent()  
     if ((viewFlags & ENABLED_MASK) == DISABLED) {
          if(event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0){
                setPressed(false);
          }
          
          return ((viewFlags & CLICKABLE) == CLICKABLE ||
                      (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
    }
    

    Viewにエージェントが設定されている場合、TouchDelegateonTouchEventメソッドも実行され、このonTouchEventの動作メカニズムはOnTouchListenerと類似しているように見えます.コードは、mTouchDelegateが設定されているか否かを判断し、mTouchDelegate.onTouchEvent(event);を実行してメソッドがtrueを返すと、viewのonTouchEvent()もtrueを返す(つまり、メソッドが終了し、=イベントが消費される).
    //       View onTouchEvent()  
    if (mTouchDelegate = ! null){
          if (mTouchDelegate.onTouchEvent(event)){
                return true;
          }
    }
    

    次に、一般的なonTouchEvent()およびCLICKABLEイベントに対するLONG_CLICKABLEメソッドの処理を参照する.viewのCLICKABLEおよびLONG_CLICKABLEがtrueである限り、onTouchEvent()メソッドはtrueを返す(このメソッドが終了し、イベントが消費されることを意味する).そしてACTION_UPイベントが発生すると、performClick()メソッドがトリガーされます.
    //       View onTouchEvent()  ,           。
    if ((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKALBE)){
          switch (event.getAciont()){
                case MotionEvent.ACTION_UP:
                ...
                if(...)  performClick();
                break;
          }
          return true;
    }
    

    ビューにonClickListenerが設定されている場合、performClickメソッドの内部でonClick()メソッドが呼び出されます.コードは次のとおりです.
    public boolean performClick(){
          final boolean result;
          final ListenerInfo li = mListenerInfo;
          if ( li != null && li.mOnClickListener != null) {
                ...
                li.mOnClickListener.onClick(this);
                result = true;
          } else {
                result = fasle;
          }
          ...
          return result;
    }
    

    viewのLONG_CLICKABLEのデフォルト属性はfalseであり、CLICKABLEのデフォルト値は特定のviewに関連している.また、2つの属性は、setClickable()およびsetLongClickable()によってそれぞれtrueとすることができることに注意されたい.