プライマー
Androidのイベント配信は、実際には何も新しいものではありませんが、正直に言うと、私はあいまいな、多くの人々はそれについて知っていると思います。本稿の目的は、再びソースレベルの外観、dispatchTouchEventのViewGroup方法に焦点からコーミングされ、この方法は、イベントでのコア分布の中核です!私たちは、配布イベントのメカニズムを理解すること、見にこれを取ります。PS、本論文では、図面(実際には怠惰な)、あなたがオンラインで画像を見ることができますが、単に多くのことを検索しないか、ソースコードと分析に焦点を当てています。
イベント配布のソースについて簡単に説明する話
多くの人がイベント配信について話す、dispatchTouchEvent Activityから始まる開始、我々は理解することがとても単純なことができますが、確かにいくつかの疑問になることを、この方法の活動はどこからそれを呼び出すことです?と 私は、簡単なデモ、ブレークポイントは、その関数呼び出しスタックを取得リガ、その後dispatchTouchEvent方法の活動を書いた下の図を参照してください。
おい、非常に多くの配布プロセスがある前に、元のアクティビティ、ビットシンプルアウトソート:おそらくInputEventReceiverからは、ViewRootImplを通じて、様々なInputStage呼び出した後、最終的にはその後、DecorViewアクティビティを通過し、DecorViewを与えました。実際には、これは非常に興味深いです、イベントを取得するDecorViewを持っていたが、それは後に、活動に割り当てられた活動とDecorView、1つの背面に1にphoneWindowバックによってイベントの後、ちょうどそれに対処するために、イベントの活動について作成するました。アクティビティパスDecorView後、DecorView呼び出しsuperDispatchTouchEvent
方法。
public boolean superDispatchTouchEvent(MotionEvent event){
return super.dispatchTouchEvent(event);
}
DecorViewがでframeLayoutあるので、それは最終的には、この記事の主人公である()のおなじみdispatchTouchEventのViewGroupを呼び出します。いわゆるイベント配布、本質的機能はonIntercepterTouchEvent、onTouchEvent、OnTouchListener、onClickListener ... balabalaのために、この再帰関数の内部で、コア、ほとんどまたはのバックボーンで動作しているように、この再帰関数は、dispatchTouchEventで、再帰的に呼ばれていますdispatchTouchEventは、私たちはそれを分析する必要があります。
ViewGroupのイベント配布
ソースが長すぎないが、それでも一見大きな頭で、私は多くのものが、なぜ知っているほとんどの人はおそらく、そのロジックを理解することができると思うが、それは、多かれ少なかれ、そのソースを読んでなければなりません。例えばmFirstTouchTargetをしているのですか?alreadyDispatchedToNewTouchTargetは、一時変数をしているのですか?リストを持っていると思われ、ああ、そうそれはなぜですか?
イベントが開始またはCANCEL UPの終わりのためのACTION_DOWNへのイベントのセットがある、ユーザーのプレスからのリフトに、それを配布することをここで少し追加します。このグループの我々の分析は、事件の背後にあります。
ソースが長く、私は擬似コードである、誰もが参照するための擬似コードを書いて、実際には、コメントを表示するには、フォーカス関数パラメータの一部を省略しますが、コードのフォーカスが含まれている、非常に包括的かつ詳細です。長すぎる場合は、直接の結論、擬似コードのルックバックの裏を見ることができます。
//本源码来自 api 28,不同版本略有不同。
public boolean dispatchTouchEvent(MotionEvent ev) {
// 第一步:处理拦截
boolean intercepted;
// 注意这个条件,后面会讲
if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
// 子view调用了parent.requestDisallowInterceptTouchEvent干预父布局的拦截,不让它爸拦截它
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action);
} else {
intercepted = false;
}
} else {
//既不是DOWN事件,mFirstTouchTarget还是null,这种情况挺常见:如果ViewGroup的所有的子View都不消费 //事件,那么当ACTION_MOVE等非DOWN事件到来时,都被拦截了。
intercepted = true;
}
// 第二步,分发ACTION_DOWN
boolean handled = false;
boolean alreadyDispatchedToNewTouchTarget = false; //注意这个变量,会用到
// 不拦截才会分发它,如果拦截了,就不分发ACTION_DOWN了
if (!intercepted) {
//处理DOWN事件,捕获第一个被触摸的mFirstTouchTarget,mFirstTouchTarget很重要,
保存了消费了ACTION_DOWN事件的子view
if (ev.getAction == MotionEvent.ACTION_DOWN) {
//遍历所有子view(看源码知子View是按照Z轴排好序的)
for (int i = childrenCount - 1; i >= 0; i--) {
//子view如果:1.不包含事件坐标 2. 在动画 则跳过
if (!isTransformedTouchPointInView() || !canViewReceivePointerEvents()) {
continue;
}
//将事件传递给子view的坐标空间,并且判断该子view是否消费这个触摸事件(分发Down事件)
if (dispatchTransformedTouchEvent()) {
//将该view加入头节点,并且赋值给mFirstTouchTarget
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
}
}
}
}
//第三步:分发非DOWN事件
//如果没有子view捕获ACTION_DOWN,则交给本ViewGroup处理这个事件。我们看到,这里并没有判断是否拦截,
//为什么呢?因为如果拦截的话,上面的代码不会执行,就会导致mFirstTouchTarget== null,于是就走下面第一 //个条件里的逻辑了
if (mFirstTouchTarget == null) {
super.dispatchTouchEvent(ev); //调用View的dispatchTouchEvent,也就是自己处理
} else {
//遍历touchTargets链表,依次分发事件
TouchTarget target = mFirstTouchTarget;
while (target != null) {
if (alreadyDispatchedToNewTouchTarget) {
handled = true
} else {
if (dispatchTransformedTouchEvent()) {
handled = true;
}
target = target.next;
}
}
}
//处理ACTION_UP和CANCEL,手指抬起来以后相关变量重置
if (ev.getAction == MotionEvent.ACTION_UP) {
reset();
}
}
return handled;
}
要約すると:のViewGroupイベント分布は3つの段階に分かれています
最初のステップ:かどうかを傍受するかを決定する:ここに条件を参照するには、分岐、文章の意味裁判官の外側の層、または確かに盗聴、傍受してもしなくてもよい、インターセプトではないかもしれないが、次の2つの条件の満たす1に必要な場合:
DOWNイベントはイベントです。
非DOWNイベントがあってもよいが、会うmFirstTouchTarget!= nullに必要ことができます。この条件は何を意味するのでしょうか?事件の前に、少なくとも一つの子ビュー撮影(消費)グループ配信イベントのためのものですDOWNイベントがあることDOWN手段は、サブビューは、このイベントを処理するために喜んであります。
可能傍受の場合には、我々は判定処理を入力傍受は単純です:あなたがインターセプト、していない呼び出す場合は、傍受の戻り値は、に基づいて決定されているかどうか、onIntercepteTouchEvent方法に来ていない場合は、子ビューを見ては何も転送parent.requestDisallowInterceptは、ありません。
ステップ2:いいえインターセプトした場合、DOWNイベントを配布する:二つの理由に基づいて、すべてのサブビュー、タッチエリアを適格消費者サブビューのイベントがあるかどうかを確認するために、判決:子ビューのアニメーションか?タッチポイントは、サブビューの範囲内にあります。最初の二つが満たされた場合、イベントは重要なステップの方法を提起サブDOWNビューに配布されます。
dispatchTransformedTouchEvent
ライブやってするこの方法は、最も重要なものです:分散サブビュー、すなわち、このアプローチはでした再帰呼び出し、興味のある学生は、独自のソースコードを読むことができます。加えて、この配布方法は、戻り値がtrueの場合、mFirstTouchTarget割り当てた、そうでなければ値はまだヌルです。この最後のステップは、一つの方法は、addTouchTarget、この方法は、リストの構築、それが保存されたもののリストが含まれているのですか?実際には、イベントの任意の位置座標のために、画面上でより多くのビューがあるかもしれないことは、座標の分布のイベント時間が含まれているビューがすべて再び、これらはビューを簡単に、それらのリストに保存されて配布されるこれらの分布のすべてを保持します背中を横断。第三段階:他のイベントを配布:子ビューを消費しない前者がnullの場合は、最初、mFirstTouchTargetを決定するには、ステップDOWNイベントを説明し、この場合は、のViewGroupの子供たちがイベントを処理するつもりはない見ることを示し、これは自然に引き渡されますViewGroup自体を処理し、親ビューのViewGroup処理と呼ばれsuper.dispatchTouchEventに引き渡さコード、(onTouchEvent)。nullではないが、説明は、サブビューイベントに対処する必要がある場合は、else文、入射ダウンの分布を入力してください。シャープ目の読者はここを参照してくださいする必要があり、第二段階は、何故ここで再び再配布する必要があり、まだDOWNイベントを配布されていないでしょうか?、別の変数出現の前で話すように、ここで、それを繰り返さない
alreadyDispatchedToNewTouchTarget
第二段階で擬似コードでこの変数の冒頭で述べたように、第二のステップは、サブビューイベントコンシューマがある場合には、変数になるだろう本当、今第三ステップがtrueの場合は、直接返す= trueを扱うだろう、という値を決定することになります、イベントはもはや配布されていません。これは、DOWNイベントが二回配布される回避できます。他のイベントでは、この変数は、それが配布のために、ロジック他の人の行くだろう、間違いなく偽です。
濃厚な外観で、少し方言を追加します。
public boolean dispatchTouchEvent(MotionEvent event) {
boolean intercepted = false;
if (DOWN 或者 DOWN的时候没有孩子想处理) {
if (孩子不让拦截?) {
intercepted = false;
} else {
intercepted = onIntercept();
}
} else {
intercepted = true;
}
if (DOWN && !intercepted) {
for (遍历孩子View) {
if(如果该孩子能消费就给分发给它,如果它真消费了DOWN事件){
给mFirstTouchTarget赋值 ;
Down事件已经分发了;
}
}
}
if (mFirstTouchTarget == null) {
孩子都不想消费,交给我自己处理吧;
} else {
while(遍历所有孩子,将事件分发下去) {
if (DOWN事件已经分发了) {
return true;
}else {
分发给子View,如果有人消费,返回true;
}
}
}
}
ここでは、我々はその後、dispatchTouchEventのビューを分析し、仕上げのViewGroupのイベント配信を置きます
ビューイベント配布
ビューは非常に簡単です
public boolean dispatchTouchEvent(MotionEvent event) {
boolean result = false;
if (onTouchListener.onTouch()) {
result = true;
}
if (!result && onTouchEvent()) {
result = true;
}
return result;
}
リスナーあれば目に見える、最初のリスナーを決定し、trueを返し、onTouchEventは、そうでない場合は、入力しonTouchEvent方法を行きません。
ビューのポイントは、興味のある学生は、自分自身のソースコードを読むだけで、ここで言うかもしれない多くのonClick、ロジックonLongclickイベントを扱っonTouchEventのメソッドのデフォルトの実装では、複雑な場所で、そのセットonClickListenerまたはonLongclickListener、それは意志onTouchEventかつて真のリターンは、それが消費者である、消費者は、他のケースでは、このようなAで記述されたソースコードをデフォルトにしません
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
クリッカブルは、そうでない場合はfalse、trueのtrueを返します。
実際に例えば
問題は、中央でframeLayoutが、その後、地域のイベントを尋ねるなど配信処理以外のボタンをクリックするボタンをクリックし、でframeLayoutとボタンがクリックイベント追加されたボタンを置く、シンプルなのですか?
ボタンを超えて見て:でframeLayoutはのViewGroupあり、かつdispatchTouchEventメソッドをオーバーライドしません。上記の分析に基づいて:
- 最初のステップ、未来に至るまで、傍受ロジックに、でframeLayout傍受ない、== falseを傍受
- イベントを下処理する第二の工程は、無サブ点タッチビューを発見したので、誰もがこの事件に対処しないであろう、mFirstTouchTarget == NULL
- 第3のステップは自分自身、自分のコールonTouchEvent、clickListenerの規定は、trueを返した場合は、消費者のイベントに対処することです。
- そして、その後の動きまでは、mFirstTouchTarget == nullのため、最初のステップは、自分の治療にそう直接、切片になり、上述した第3のステップと、同時に、時間までのイベントをクリックして応答します。
ボタン内:
- 最初のステップ、同上。
- 第二段階は、タッチポイントは、子ビュー、mFirstTouchTarget!= NULLを発見した、とイベントがサブDOWNビューに配布されます。
- 第3のステップと、mFirstTouchTarget非ヌルが、
alreadyDispatchedToNewTouchTarget
この変数が真である、直接真リターン。 - そして、その後の動きまで、最初のステップはブロックされません、それはそう第2工程を省略し、第三段階は、サブビューイベントに配布されるイベントダウンしていないので、子ビューは、リターンが真、クリックイベントに応答して、このプロセス、のViewGroup onClickイベントに応答しないことが自然である、任意のイベントを消費しません。
2は、[View]をクリックイベント追加されたときにこのように、応答は〜結果を解釈することではありません
概要
使用、2つの段階、傍受や分布、2例、イベントや非ダウンがあるその分布のイベント配布
イベントダウンダウンイベントは、一連のイベント、イベントを消費するか否かの判断の始まりである、それはすべての非以降の影響を与えますイベントダウン分布は、イベントが消費ダウンしていない場合、すべてのイベントは、もはや直接ビュー・グループ・プロセスから、サブビューの後に分配されず、mFirstTouchTargetがヌルであることを確認します。
より多くのソースコードを読むことの重要性を感じて、レッツはクソのソースコードを読んでください!