ViewdispatchTouchEventソース分析(三)
13245 ワード
前節の概要
1、Activity内のイベントは、ActivityでdispatchTouchEventが先に処理されます.2、それからActivityでイベントをDecorViewのsuperDispatchTouchEventに投げて処理します.3.実際にsuperDispatchTouchEventメソッドはViewGroupのdispatchTouchEventメソッドを呼び出します.
だから本当の意味でのdispatchTouchEventの過程は間もなく本格的に始まります.
前節の記事View・InputEventからdispatchTouchEventのソースコード分析(2)を参照してください.
俯瞰dispatchTouchEventメソッド
まず、dispatchTouchEventメソッドを全体的に見てみましょう.
まず、dispatchTouchEventメソッドを全体的に見てみましょう.
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
// 1、
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
}
// 2、 , 。
if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
ev.setTargetAccessibilityFocus(false);
}
boolean handled = false;
// 3、
if (onFilterTouchEventForSecurity(ev)) {
// 4、
}
if (!handled && mInputEventConsistencyVerifier != null)
{
// 5、
mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
}
// 6、 handled
return handled;
}
イベントのフィルタ、選択された配布イベント
ウィンドウが非表示になった場合、イベントはフィルタされます.つまり、イベントの配布は実行されません. /**
*
*
* @param event
* @return 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) {
// Window , 。
return false;
}
return true;
}
イベント配信
イベント配信のコードが長いため,上から下への分解コードで解析する.各セグメントの機能のコードセグメントは、小節として個別に分析されます.
1、新しいイベントフローかどうかを判断する
1.1 ViewGroup#dispatchTouchEvent()
コードでACTIONを認定するDOWNは連続イベントの開始フラグであるため、コードはACTION_を受信するDOWNイベントの場合、直ちにリセット状態操作が行われます.
アプリケーションの切り替え、ANR、またはその他のステータスの変更により、フレームワークがACTION_を破棄する可能性があります.UPまたはACTION_CANCELイベント.final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
if (actionMasked == MotionEvent.ACTION_DOWN) {
cancelAndClearTouchTargets(ev);
resetTouchState();
}
1.2 ViewGroup#cancelAndClearTouchTargets()
cancelAndClearTouchTargets
メソッドは、mFirstTouchTarget
オブジェクトが空であるかどうかを最初に確認するために実行される.mFirstTouchTarget
が空でない場合は、以前のイベントフローが空になっていないことを意味します.mFirstTouchTarget
が空であれば、すなわち、イベントフローが以前に存在しなかったことを意味するので、再解放する必要はない.mFirstTouchTarget
が空でないことを前提として送信されたevent
がnull
である場合、MotionEvent
オブジェクトが手動で構築され、イベントタイプがACTION_CANCEL
に設定される.イベントオブジェクトが存在すると、TouchTarget
のチェーンを巡回し、キャンセルイベントの配布を実行します.配布が完了した後、TouchTarget
のチェーンを同時にクリアします. private void cancelAndClearTouchTargets(MotionEvent event) {
if (mFirstTouchTarget != null) {
boolean syntheticEvent = false;
if (event == null) {
//
final long now = SystemClock.uptimeMillis();
event = MotionEvent.obtain(now, now,
MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
syntheticEvent = true;
}
//
for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {
resetCancelNextUpFlag(target.child);
dispatchTransformedTouchEvent(event, true, target.child, target.pointerIdBits);
}
clearTouchTargets();
// ,
if (syntheticEvent) {
event.recycle();
}
}
}
ここでは,イベントの配布がTouchTarget
とdispatchTransformedTouchEvent()
と密接に関連しているように見えることを観察した.キャンセルイベントの配布は、TouchTarget
のチェーンを巡回し、dispatchTransformedTouchEvent()
の順に配布することによって完了する.
そこで、TouchTarget
とdispatchTransformedTouchEvent()
の2つの方法について、イベントの配布の詳細が具体的にどのように実現されているかを見てみましょう.
1.3 TouchTarget
TouchTarget
の内部には、タッチに関連するターゲット(内部はView
)を直列に接続して、後続のイベントの配布を容易にする32サイズの再利用可能なチェーン構造が提供される.private static final class TouchTarget {
private static final int MAX_RECYCLED = 32;
private static final Object sRecycleLock = new Object[0];
private static TouchTarget sRecycleBin;
private static int sRecycledCount;
public static final int ALL_POINTER_IDS = -1; // all ones
//
public View child;
// ,
// The combined bit mask of pointer ids for all pointers captured by the target.
public int pointerIdBits;
// target
public TouchTarget next;
private TouchTarget() {
}
public static TouchTarget obtain(@NonNull View child, int pointerIdBits) {
if (child == null) {
throw new IllegalArgumentException("child must be non-null");
}
final TouchTarget target;
synchronized (sRecycleLock) {
if (sRecycleBin == null) {
target = new TouchTarget();
} else {
target = sRecycleBin;
sRecycleBin = target.next;
sRecycledCount--;
target.next = null;
}
}
target.child = child;
target.pointerIdBits = pointerIdBits;
return target;
}
public void recycle() {
if (child == null) {
throw new IllegalStateException("already recycled once");
}
synchronized (sRecycleLock) {
if (sRecycledCount < MAX_RECYCLED) {
next = sRecycleBin;
sRecycleBin = this;
sRecycledCount += 1;
} else {
next = null;
}
child = null;
}
}
}
1.4 TouchTargetの追加
addTouchTarget
が呼び出されるたびに、内部にTouchTarget
オブジェクトが作成(または再利用)され、そのTouchTarget
オブジェクトがリストのヘッダに設定されます.private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
mFirstTouchTarget = target;
return target;
}
1.5誰かがaddTouchTargetを呼び出した
ViewGroup#dispatchTouchEvent()
法について述べると、ViewGroup
は
を遍歴し、それらを鎖に構築することが分かった.では,
と構築された列の対応関係を知ることが重要である.// ViewGroup#dispatchTouchEvent
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
...
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) {
...
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
...
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
...
}
}
1.6初回イベント(ACTION_DOWN)の配布手順
イベント配信の主線から逸脱しないように,ViewTreeのコード解析を省略する.上記のコードは、ACTION_DOWN
イベントを処理する際に実行機会を得、すなわちACTION_DOWN
イベントを受信すると、フレームワークがView[]
を遍歴することを観察した.一方、View[]
の初期化は、addView()
の方法に追記することができる.
したがって、上段のコードは最終的に、View[]
を巡回し、Viewブロックイベントの戻り値に基づいて処理チェーンを構築することを表す.ポイントは方法dispatchTransformedTouchEvent()
をご覧ください.private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
...
final MotionEvent transformedEvent;
if (newPointerIdBits == oldPointerIdBits) {
if (child == null || child.hasIdentityMatrix()) {
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
event.offsetLocation(offsetX, offsetY);
handled = child.dispatchTouchEvent(event);
event.offsetLocation(-offsetX, -offsetY);
}
return handled;
}
transformedEvent = MotionEvent.obtain(event);
} else {
...
}
...
}
親子関係がない場合(ViewGroupにViewがない場合)は、イベントの配布を自分で処理します.そうでない場合は、サブビューに渡してイベントを配布する必要があるかどうかを決定します.
1.6.1第1回イベント(ACTION_DOWN)の配信プロセスの配信をViewへ
result
のtrue/false
は、Viewが後続のジェスチャーイベントを受け取ることができるかどうかを決定し、下にresult
の値を取ることに影響を与える場所がいくつか見られます.
/**
*
*
* @param event
* @return 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) {
// Window , 。
return false;
}
return true;
}
イベント配信のコードが長いため,上から下への分解コードで解析する.各セグメントの機能のコードセグメントは、小節として個別に分析されます.
1、新しいイベントフローかどうかを判断する
1.1 ViewGroup#dispatchTouchEvent()
コードでACTIONを認定するDOWNは連続イベントの開始フラグであるため、コードはACTION_を受信するDOWNイベントの場合、直ちにリセット状態操作が行われます.
アプリケーションの切り替え、ANR、またはその他のステータスの変更により、フレームワークがACTION_を破棄する可能性があります.UPまたはACTION_CANCELイベント.
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
if (actionMasked == MotionEvent.ACTION_DOWN) {
cancelAndClearTouchTargets(ev);
resetTouchState();
}
1.2 ViewGroup#cancelAndClearTouchTargets()
cancelAndClearTouchTargets
メソッドは、mFirstTouchTarget
オブジェクトが空であるかどうかを最初に確認するために実行される.mFirstTouchTarget
が空でない場合は、以前のイベントフローが空になっていないことを意味します.mFirstTouchTarget
が空であれば、すなわち、イベントフローが以前に存在しなかったことを意味するので、再解放する必要はない.mFirstTouchTarget
が空でないことを前提として送信されたevent
がnull
である場合、MotionEvent
オブジェクトが手動で構築され、イベントタイプがACTION_CANCEL
に設定される.イベントオブジェクトが存在すると、TouchTarget
のチェーンを巡回し、キャンセルイベントの配布を実行します.配布が完了した後、TouchTarget
のチェーンを同時にクリアします. private void cancelAndClearTouchTargets(MotionEvent event) {
if (mFirstTouchTarget != null) {
boolean syntheticEvent = false;
if (event == null) {
//
final long now = SystemClock.uptimeMillis();
event = MotionEvent.obtain(now, now,
MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
syntheticEvent = true;
}
//
for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {
resetCancelNextUpFlag(target.child);
dispatchTransformedTouchEvent(event, true, target.child, target.pointerIdBits);
}
clearTouchTargets();
// ,
if (syntheticEvent) {
event.recycle();
}
}
}
ここでは,イベントの配布が
TouchTarget
とdispatchTransformedTouchEvent()
と密接に関連しているように見えることを観察した.キャンセルイベントの配布は、TouchTarget
のチェーンを巡回し、dispatchTransformedTouchEvent()
の順に配布することによって完了する.そこで、
TouchTarget
とdispatchTransformedTouchEvent()
の2つの方法について、イベントの配布の詳細が具体的にどのように実現されているかを見てみましょう.1.3 TouchTarget
TouchTarget
の内部には、タッチに関連するターゲット(内部はView
)を直列に接続して、後続のイベントの配布を容易にする32サイズの再利用可能なチェーン構造が提供される.private static final class TouchTarget {
private static final int MAX_RECYCLED = 32;
private static final Object sRecycleLock = new Object[0];
private static TouchTarget sRecycleBin;
private static int sRecycledCount;
public static final int ALL_POINTER_IDS = -1; // all ones
//
public View child;
// ,
// The combined bit mask of pointer ids for all pointers captured by the target.
public int pointerIdBits;
// target
public TouchTarget next;
private TouchTarget() {
}
public static TouchTarget obtain(@NonNull View child, int pointerIdBits) {
if (child == null) {
throw new IllegalArgumentException("child must be non-null");
}
final TouchTarget target;
synchronized (sRecycleLock) {
if (sRecycleBin == null) {
target = new TouchTarget();
} else {
target = sRecycleBin;
sRecycleBin = target.next;
sRecycledCount--;
target.next = null;
}
}
target.child = child;
target.pointerIdBits = pointerIdBits;
return target;
}
public void recycle() {
if (child == null) {
throw new IllegalStateException("already recycled once");
}
synchronized (sRecycleLock) {
if (sRecycledCount < MAX_RECYCLED) {
next = sRecycleBin;
sRecycleBin = this;
sRecycledCount += 1;
} else {
next = null;
}
child = null;
}
}
}
1.4 TouchTargetの追加
addTouchTarget
が呼び出されるたびに、内部にTouchTarget
オブジェクトが作成(または再利用)され、そのTouchTarget
オブジェクトがリストのヘッダに設定されます.private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
mFirstTouchTarget = target;
return target;
}
1.5誰かがaddTouchTargetを呼び出した
ViewGroup#dispatchTouchEvent()
法について述べると、ViewGroup
は
を遍歴し、それらを鎖に構築することが分かった.では,
と構築された列の対応関係を知ることが重要である.// ViewGroup#dispatchTouchEvent
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
...
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) {
...
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
...
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
...
}
}
1.6初回イベント(ACTION_DOWN)の配布手順
イベント配信の主線から逸脱しないように,ViewTreeのコード解析を省略する.上記のコードは、
ACTION_DOWN
イベントを処理する際に実行機会を得、すなわちACTION_DOWN
イベントを受信すると、フレームワークがView[]
を遍歴することを観察した.一方、View[]
の初期化は、addView()
の方法に追記することができる.したがって、上段のコードは最終的に、
View[]
を巡回し、Viewブロックイベントの戻り値に基づいて処理チェーンを構築することを表す.ポイントは方法dispatchTransformedTouchEvent()
をご覧ください.private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
...
final MotionEvent transformedEvent;
if (newPointerIdBits == oldPointerIdBits) {
if (child == null || child.hasIdentityMatrix()) {
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
event.offsetLocation(offsetX, offsetY);
handled = child.dispatchTouchEvent(event);
event.offsetLocation(-offsetX, -offsetY);
}
return handled;
}
transformedEvent = MotionEvent.obtain(event);
} else {
...
}
...
}
親子関係がない場合(ViewGroupにViewがない場合)は、イベントの配布を自分で処理します.そうでない場合は、サブビューに渡してイベントを配布する必要があるかどうかを決定します.
1.6.1第1回イベント(ACTION_DOWN)の配信プロセスの配信をViewへ
result
のtrue/false
は、Viewが後続のジェスチャーイベントを受け取ることができるかどうかを決定し、下にresult
の値を取ることに影響を与える場所がいくつか見られます.onFilterTouchEventForSecurity(event)
(mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)
li.mOnTouchListener.onTouch(this, event)
onTouchEvent(event)
私たちがよく使う
onTouchEvent(event)
とsetTouchListener()
の方法がイベントの配布に影響を与える根本的な原因です.public boolean dispatchTouchEvent(MotionEvent event) {
...
boolean result = false;
...
final int actionMasked = event.getActionMasked();
...
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//noinspection SimplifiableIfStatement
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;
}
1.6.2第1回イベント(ACTION_DOWN)の配布プロセスの配布からView Groupへ
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean handled = false;
...
if (onFilterTouchEventForSecurity(ev)) {
//
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
// ViewGroup
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}
}
// , ACTION_DOWN 。
if (!canceled && !intercepted) {
...
}
// ACTION_DOWN , mFirstTouchTarget 。 ViewGroup 。
if (mFirstTouchTarget == null) {
// mFirstTouchTarget, 。
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
...
}
}
1.6.3 ACTION_DOWNイベント配信ダイジェスト
これまで,
ACTION_DOWN
イベント配信の輪郭が描かれてきた.DecorView
から特定の焦点を得るView
(途中でブロックされた場合、ブロックされたView)まで、ACTION_DOWN
イベントによってTouchTarget
配布チェーンが決定されると、接続されたイベントはいずれもこの配布チェーンによって処理される.凡例の説明
ACTION_DOWNイベント配信
TouchTargetのチェーンを確認する
イベント配信時
本章のまとめ
1.StageからDecorViewを呼び出す.dispatchTouchEvent 2、DecorViewは、ViewGroupを継承し、dispatchTouchEventメソッドを上書きしていないため、ViewGroupのdispatchTouchEventに処理を任せる.3、まずACTIONであるか否かを判断するDOWNイベントは,初期化,解放などの操作4を展開し,次にViewGroupブロッキングイベントが必要か否かを判断する.
4.1、ブロックする必要がある場合、View GroupはViewとして配布を処理する.4.2.ブロックを必要としない場合、ViewGroupの下のサブビューを巡回し、サブビューがdispatchTouchEventを処理するかどうかを順次尋ね、問い合わせの軌跡をTouchTargetチェーンの形で記録する.4.3.サブビュー処理が確認されるまで、及びACTION_を終了するDOWN事件の配布.
5.次に、イベントは、return
のインスタンスに戻るまで、層DecorView
毎に行われる.次に、DecorView
のインスタンス制御mFirstTouchTarget
オブジェクトによってイベント配信が展開される.6、……続きは次の章で続きます
1.StageからDecorViewを呼び出す.dispatchTouchEvent 2、DecorViewは、ViewGroupを継承し、dispatchTouchEventメソッドを上書きしていないため、ViewGroupのdispatchTouchEventに処理を任せる.3、まずACTIONであるか否かを判断するDOWNイベントは,初期化,解放などの操作4を展開し,次にViewGroupブロッキングイベントが必要か否かを判断する.
4.1、ブロックする必要がある場合、View GroupはViewとして配布を処理する.4.2.ブロックを必要としない場合、ViewGroupの下のサブビューを巡回し、サブビューがdispatchTouchEventを処理するかどうかを順次尋ね、問い合わせの軌跡をTouchTargetチェーンの形で記録する.4.3.サブビュー処理が確認されるまで、及びACTION_を終了するDOWN事件の配布.
5.次に、イベントは、
return
のインスタンスに戻るまで、層DecorView
毎に行われる.次に、DecorView
のインスタンス制御mFirstTouchTarget
オブジェクトによってイベント配信が展開される.6、……続きは次の章で続きます