Androidイベントの配布、ブロック、実行について詳細に説明する。


普段の開発では、クリックやスライドなどのイベントがよくあります。異なるviewの間にも様々な滑りの衝突がある場合があります。例えばレイアウトの内外の二重がスライドできると、衝突します。この時はAndroidのイベント配信の仕組みを知る必要があります。
Androidのタッチイベントの配布過程は三つの重要な方法で共同に完成します。dispatch TouchEvent、one Intercept TouchEvent、ontouch Event。まずこの3つの方法を大まかに紹介します。
 •public book dispatch Touch Event(MotionEvent ev) 
イベントの配布を行います。イベントが現在のビューに伝達されるなら、この方法は必ず呼び出され、その結果は現在のビューのオンタッチイベントと下位Viewのdispatch TouchEvent方法の影響を受けて、現在のイベントを消費するかどうかを表します。ACTION_DOWNのdispatch TouchEvent()はtrueに戻り、その後のイベント(ACT ION_)MOVE、ACTION_UP)は再送します。falseに戻ると、dispatch TouchEvent()はACTIION_を受信できません。UP、ACT ION_MOVE。簡単に言えば、dispatch TouchEventがイベントの配布を行っている時、前のactionだけがtrueに戻り、後のactionをトリガします。
 •public book on Intercept Touch Event(MotionEvent) 
この方法はdispatch TouchEvent法で呼び出され、あるイベントをブロックするために使われます。現在Viewがあるイベントをブロックしている場合、同じイベントシーケンスにおいて、この方法は再起動されず、戻りの結果は現在のイベントをブロックするかどうかを示す。これはView Groupが提供する方法で、デフォルトはfalseに戻ります。
 •public book on TouchEvent(MotionEvent) 
dispatch TouchEventメソッドで呼び出し、クリックイベントを処理し、その結果、現在のイベント(trueは消耗を表し、falseは消耗しないことを表します。)を消費しないと、同じイベントシーケンスにおいて、現在のViewはイベントを再び受信することができません。ViewとView Groupはこの方法を持っています。Viewはデフォルトでtrueに戻り、このイベントを消費したと表しています。
Viewには、二つのコールバック関数があります。
public book dispatch Touch Event(MotionEvent ev);   
public book ontouchEvent(MotionEvent ev);
View Groupには3つのコールバック関数があります。
public book dispatch Touch Event(MotionEvent ev);   
public book one Intercept TouchEvent(MotionEvent ev);   
public book ontouchEvent(MotionEvent ev);
上記の3つの方法の中で、どのような違いと関係がありますか?次は偽コードで表します。

public boolean dispatchTouchEvent(MotionEvent ev) { 
 boolean consume = false; 
 if(onInterceptTouchEvent(ev)){ 
  consume = onTouchEvent(ev); 
 } else { 
  consume = child.dispatchTouchEvent(ev); 
 } 
 return consume; 
} 

 上記の偽コードを通じて、皆様はクリックイベントの伝達ルールをより明確に認識しているかもしれません。即ち、一つのルートView Groupにとって、クリックイベントが発生したら、まずそれに伝達します。この時にdispatch TouchEventが呼び出されます。もしこのView Groupのone InterceptTouchEvent方法がtrueに戻ったら、このイベントをブロックします。このイベントはこのView Groupに任せます。つまり、on TouchEventの方法は呼び出されます。このView Groupのone Intercept TouchEvent方法がfalseに戻ると、このイベントをブロックしないということです。これは現在のイベントがそのサブ要素に伝達され続け、サブ要素のdispatch Touch Event方法が呼び出され、イベントが最終的に処理されるまで繰り返します。
以下のいくつかの図は[eoe]から参照してください。
 •図1:ACTION_DOWNは全部消費されていません
 
•図二(一):ACTION_DOWNはViewで消費されました

•図二(二):後続ACT ION_MOVEとUPはブロックされずにVIEWを探しています。

•図三:後続がブロックされました。

•図4:ACT ION_DOWNは最初からブロックされます。

Viewイベントの配布元の分析:
 •dispatch TouchEvent方法: 

public boolean dispatchTouchEvent(MotionEvent event) { 
 if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && mOnTouchListener.onTouch(this, event)) { 
  return true; 
 } 
 return onTouchEvent(event); 
}

 もしmOnTouch Listenerなら!=null,(mView Flags&ENABLED_MASK==ENABLEDとmOnTouch Listener.ontouchの3つの条件は全部本当です。trueに戻ります。そうしないとワンタッチEventの方法を実行して戻ります。
まとめたontouchが実行されるには二つの前提条件が必要です。
 1.OnTouch Listenerを設置しています。
 2.コントロールはenable状態です。
 ontouchEventは以下の3つの条件のいずれかを満たすことができます。
 1.OnTouch Listenerが設置されていません。
 2.コントロールはenableの状態ではありません。
 3.ontouch falseに戻る
 更にdispatch TouchEventの戻り値を見てみると、ontouchとontouch Event関数の戻り値によって制御されています。つまりtouchイベントは成功的に消費されてtrueに戻ります。配布が成功しました。その後のイベントシーケンスもここで配布されます。
 •ontouch Event方法:

 if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
  ...
  return true;
 }
Viewのon TouchEventはデフォルトではイベントを消費します。これはクリックできないものでない限り(clickableとlongClicableは同時にfalseです)。そして、ViewのlongClicableはデフォルトではfalseであり、clickable属性はButtonがデフォルトでtrueであり、TextView、ImageViewはデフォルトでfalseである。

public boolean performClick() { 
 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); 
 if (mOnClickListener != null) { 
  playSoundEffect(SoundEffectConstants.CLICK); 
  mOnClickListener.onClick(this); 
  return true; 
 } 
 return false; 
}

 これは私たちがよく知っているOClikListenerではないですか?元々はワンタッチイベントで呼び出されたものです。mOnClickListenerがnullでない限り、そのonClickメソッドを呼び出します。
まとめたOClickが実行されるには二つの前提条件が必要です。
 1.ワンタッチイベントまで実行できます。
 2.OClickListenerを設置しています。
 ビュー全体のイベント転送の流れは以下の通りです。
dispatch Event->set OnTouch Listener->ontouch Event->set OnClikListener
最後にもう一つの問題があります。set OnlongClickListenerとset OnClickListenerは一つしか実行できませんか?
いいえ、set OnlongClickListenerの中のオンクレックがfalseに戻ると、両方とも実行されます。trueに戻るとset OnClikListenerを遮断します。
View Groupイベントの配布元の分析:
 •dispatch TouchEvent方法:

 ...
   if (disallowIntercept || !onInterceptTouchEvent(ev)) { 
    ev.setAction(MotionEvent.ACTION_DOWN); 
    final int scrolledXInt = (int) scrolledXFloat; 
    final int scrolledYInt = (int) scrolledYFloat; 
    final View[] children = mChildren; 
    final int count = mChildrenCount; 

    for (int i = count - 1; i >= 0; i--) { 
     final View child = children[i]; 
     if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE 
       || child.getAnimation() != null) { 
      child.getHitRect(frame); 
      if (frame.contains(scrolledXInt, scrolledYInt)) { 
       final float xc = scrolledXFloat - child.mLeft; 
       final float yc = scrolledYFloat - child.mTop; 
       ev.setLocation(xc, yc); 
       child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; 
       if (child.dispatchTouchEvent(ev)) { 
        // Event handled, we have a target now. 
        mMotionTarget = child; 
        return true; 
       } 
      } 
     } 
    } 
   }

 2つはifコードセグメントに入る可能性があります。すなわちイベントはサブビューに配信されます。
1、現在はブロックが許されていません。すなわちdisallow Intercept=trueです。
2、現在ブロックされていません。つまりオンインテットTouchEvent(ev)はfalseに戻ります。
 注:disallow Interceptとは、イベントブロックを無効にする機能で、デフォルトはfalseで、View Grop.request DiscallowIntercept TouchEventによって設定できます。onIntercept TouchEvent(ev)は複写できます。
ifコードセグメントに入ると、forループを通じて、現在のView Groupのすべてのサブビューを巡回して、現在巡回しているViewがクリックしているViewかどうかを判断します。そうであれば、Viewのdispatch TouchEventを呼び出して、Viewのイベント配布プロセスに入ります。child.dispatch TouchEvent(ev)がtrueに戻ると、mMotionTarget=childとなります。そしてreturn trueは、View Groupのdispatch TouchEventの戻り値がchild Viewのdispatch TouchEventの戻り値に影響され、サブビューイベントの配布が成功し、View Groupのイベントの配布が成功しました。その後のイベントの流れはここで配布されます。もしView Groupイベントの配布に失敗したり、子供Viewが見つからなかったら、そのone TouchEventに行きます。これからのイベントのシーケンスも配布しなくて、直接にonetouch Eventに行きます。
View Group全体のイベント転送の流れは以下の通りです。
dispatch Event->onIntercept Touch Event->child.dispatch Event-』(set OnTouch Listener->ontouch Event)
上の総括は全部基礎です。ブロックがなければ。じゃどうやってブロックしますか?
 •onIntercept TouchEvent

 public boolean onInterceptTouchEvent(MotionEvent ev) { 
 return false; 
}
 コードは簡単です。falseに戻ります。VieGroupはデフォルトでブロックされません。ブロックが必要なら、return trueでいいです。そうすると、このイベントは子供Viewに伝えられません。そして、DOWN return trueにいるなら、DOWN、MOVE、UP子Viewはイベントを捕まえることができません。もしあなたがMOVE return trueにいるなら、子ViewはMOVEとUPでイベントを捕まえることができません。
どうして止められないですか?
View Groupのone Intercept Touch Event(ev)がACT ION_MOVE時return trueは、サブビューのMOVEとUPイベントをブロックしました。この時、Viewの希望は依然としてMOVEとUPに応えられます。どうすればいいですか?
答え:one Intercept TouchEventはView Groupで定義されています。サブViewは修正できません。Androidは、ブロックが許可されているかどうかを設定する方法を提供しています。私たちはサブビューのdispatTouchEventに直接こう書きます。

 @Override 
  public boolean dispatchTouchEvent(MotionEvent event) 
  { 
   getParent().requestDisallowInterceptTouchEvent(true); 
   int action = event.getAction();  
   switch (action) { 
   case MotionEvent.ACTION_DOWN: 
    Log.e(TAG, "dispatchTouchEvent ACTION_DOWN"); 
    break; 
   case MotionEvent.ACTION_MOVE: 
    Log.e(TAG, "dispatchTouchEvent ACTION_MOVE"); 
    break; 
   case MotionEvent.ACTION_UP: 
    Log.e(TAG, "dispatchTouchEvent ACTION_UP"); 
    break;  
   default: 
    break; 
   } 
   return super.dispatchTouchEvent(event); 
  } 

 get Part().request DiallowIntercept TouchEvent(true)このようにView GroupがMOVEの時にreturn trueであっても、サブビューは依然としてMOVEおよびUPイベントを捉えることができます。
注:View Groupがone Intercept Touch Event(ev)ACT ION_DOWNの中で直接return trueになりました。それでは、Viewは仕方なく捕獲事件です。
締め括りをつける
コードフローについては上でまとめました。
1、View Groupがこのイベントを処理できるViewを見つけたら、直接にサブビューに渡して処理します。自分のonetouchEventは触発されません。
2、オンインテットタッチTouchEvent(ev)メソッドを複写することで、サブビューのイベント(つまりreturn true)をブロックし、イベントを自分に任せて処理すれば、自分で対応するonTouchEvent方法を実行します。
3、サブViewはget Part()を呼び出すことができます。request DiscallowIntercept TouchEvent(true)View GroupがMOVEまたはUPイベントをブロックすることを阻止する。
はい、実際の応用ではどのような問題が解決できますか?
例えば、ScrllViewにEditTextをネストしました。EditTextの中に文字の内容が多すぎて範囲を超えた時、上下スライドしてEditTextの中の文字をスクロールさせたいですが、スクロールするのはScrolViewです。この時私達はEditTextのワンタッチイベントを設定して、SCrollViewに私のイベントをブロックさせないように設定して、最後にUPの時に状態を変えます。

@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
  if ((view.getId() == R.id.tousuContentEditText && canVerticalScroll(tousuContentEditText))) {
   view.getParent().requestDisallowInterceptTouchEvent(true);
   if (motionEvent.getAction() == MotionEvent.ACTION_UP) {
    view.getParent().requestDisallowInterceptTouchEvent(false);
   }
  }
  return false;
 }

private boolean canVerticalScroll(EditText editText) {
  int scrollY = editText.getScrollY();
  int scrollRange = editText.getLayout().getHeight();
  int scrollExtent = editText.getHeight() - editText.getCompoundPaddingTop() - editText.getCompoundPaddingBottom();
  int scrollDifference = scrollRange - scrollExtent;
  if (scrollDifference == 0) {
   return false;
  }
  return (scrollY > 0) || (scrollY < scrollDifference - 1);
 }

以上が本文の全部です。皆さんの勉強に役に立つように、私たちを応援してください。