PS:この全体的な知識不安の時代において、誰もが学ぶことの重要性を理解し、「学ぶことができる人」にならなければならず、「最も勤勉なバカ」になってはいけません。
イベントの配信プロセスは基本的に明確に説明されていると言えます。この記事を読む前に、以下のいくつかの記事を先に読んでください:
もう一つの問題は、Android のイベント伝達プロセスにおいて、onTouch と onClick イベントが全体のイベントプロセスでどのようにイベントを伝達するかです。以下では、onTouch、onClick とイベント伝達プロセスでの呼び出しの順序について、以下のいくつかの側面から説明します:
- ソースコードの onTouch () メソッド
- ソースコードの onClick () メソッド
- onTouch () と onClick () メソッドの関係
- まとめ
ソースコードの onTouch () メソッド#
タッチイベントのリスナーを設定する際には、View クラスの OnTouchListener インターフェースを使用し、setOnClickListener を通じてタッチイベントのリスナーを設定します。これにより、特定のイベントタイプに基づいて操作を実行できます。onTouch () メソッドは、インターフェース OnTouchListener に定義されたメソッドで、View の dispatchTouchEvent () メソッド内で呼び出されます。以下は、ソースコード内での onTouch メソッドの具体的な呼び出しです:
//イベント配信
public boolean dispatchTouchEvent(MotionEvent event) {
...
//デフォルトの戻り値
boolean result = false;
...
//注意すべき条件
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
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;
}
上記のコードでは、最初に最外部の条件 onFilterTouchEventForSecurity () メソッドを見てみましょう。このメソッドの戻り値だけに注目すれば良いです。ソースコードは以下の通りです:
/**
* イベントが正常に配信されると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) {
// ウィンドウが隠れているため、このタッチを破棄します。
return false;
}
return true;
}
したがって、このメソッドは通常の場合 true を返します。重要な条件は、ListenerInfo が null でないこと、mOnTouchListener が null でないこと、onTouch () メソッドの戻り値です。以下は ListenerInfo の初期化に関するソースコードの一部です:
//getListenerInfo()の具体的な呼び出し
public void setOnTouchListener(OnTouchListener l) {
getListenerInfo().mOnTouchListener = l;
}
//ListenerInfoの初期化
ListenerInfo getListenerInfo() {
if (mListenerInfo != null) {
return mListenerInfo;
}
mListenerInfo = new ListenerInfo();
return mListenerInfo;
}
明らかに、setOnTouchListener () メソッドを通じてタッチイベントのリスナーを設定すると、ListenerInfo が初期化されます。同時に、タッチイベントリスナーを設定する際に mOnTouchListener != null が成立します。最終的に、onTouch () メソッドの戻り値が dispatchTouchEvent () メソッドが true を返すかどうかを決定します。したがって、タッチリスナーイベントが設定され、onTouch () メソッドが true を返す場合、イベントはそこで処理され、子 View に伝達されることはありません。同時に、onTouchEvent () メソッドも実行されなくなります。false を返すと、onTouchEvent () メソッドはまだ実行されます。
ソースコードの onClick () メソッド#
クリックイベントのリスナーを設定する際には、View クラスの OnClickListener インターフェースを使用し、setOnClickListener を通じてクリックイベントのリスナーを設定します。これにより、特定のイベントタイプに基づいて操作を実行できます。onClick () メソッドは OnClickListener インターフェースに定義されたメソッドで、View の onTouchEvent () メソッド内で呼び出されます。以下は、ソースコード内での onClick () メソッドの具体的な呼び出しです:
//イベント処理
public boolean onTouchEvent(MotionEvent event) {
...
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
(viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
switch (action) {
case MotionEvent.ACTION_UP:
...
//このメソッドが実行されると、その戻り値はonTouchEvent()の戻り値です
performClick();
...
break;
}
return true;
}
return false;
}
上記のコードでは、少なくとも onClick () メソッドの呼び出し位置を見つけました。以下は performClick () メソッドです:
/**
* 主にOnClickListenerをコールバックします
*/
public boolean performClick() {
final boolean result;
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
// OnClickListenerインターフェースのonClick()メソッドを呼び出しました
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
return result;
}
明らかに、コード内で setOnClickListener () メソッドを通じてクリックイベントのリスナーを設定すると、対応する View の onTouchEvent () メソッドは true を返します。もちろん、イベントはそこで消費されます。逆に false を返すと、onTouch と onClick の呼び出し順序はどうなるのでしょうか?それらは互いに影響を与えるのでしょうか?以下では、ケーススタディの観点からそれらの関係を理解します。
onTouch () と onClick () メソッドの関係#
以前のケーススタディのように、MLinearLayout が MRelativeLayout をネストし、MRelativeLayout が MTextView をネストしています。これらの 3 つの View は、すべてそれぞれのイベント配信に関連するメソッドをオーバーライドし、MTextView にタッチイベントとクリックイベントのリスナーを設定しています。具体的には以下の通りです:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.textView).setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.i("Event", "TextView-------onTouch---------------return:" + false);
return false;
}
});
findViewById(R.id.textView).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.i("Event", "TextView-------onClick");
}
});
}
- onTouch () が false を返すように設定し、ログを確認します:
結論:タッチイベントのリスナーが設定され、onTouch () メソッドが false を返すと、onTouchEvent () メソッドは onTouch () メソッドの後に実行され、イベントはそこで消費されます。その後、ACTION_DOWN の後の一連のイベントを受け取ります。途中でマウスを使用したため、ACTION_MOVE イベントはありません。また、onTouch () メソッドが false を返した場合、onClick () が実行されました。
- onTouch () が true を返すように設定し、ログを確認します:
結論:onTouch () が true を返すと、前述のように onTouchEvent () は再度実行されず、したがって onClick () も実行されません。
まとめ#
onTouch () メソッドの戻り値は onTouchEvent () メソッドを実行するかどうかを決定します。onTouch () が true を返すと、onTouchEvent () は再度実行されず、false を返すと onTouchEvent () は実行され続けます。また、onClick () のコールバックは onTouchEvent () メソッド内で呼び出されるため、onTouchEvent () が実行されなければ onClick () も実行されません。