dispatchTouchEventソース分析
24855 ワード
概要
dispatchTouchEventイベントの配布の理解を通してandroidイベントの処理メカニズムを理解する
イベント配信プロセス
まず、イベントがシステムによって現在のActivityに渡されたことを確認し、Activityによって配布が開始されます.主なプロセス:
Activity -> PhoneWindow -> DecorView -> ViewGroup -> … -> View
ちょっと見てdispatchTouchEvent()ソースコード
/**
* Called to process touch screen events. You can override this to
* intercept all touch screen events before they are dispatched to the
* window. Be sure to call this implementation for touch screen events
* that should be handled normally.
*
* @param ev The touch screen event.
*
* @return boolean Return true if this event was consumed.( , true)
*/
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
//
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
ソースコードには、getWindow()の場合、Activity dispatchTouchEventでWindowのsuperDispatchTouchEventが呼び出されていることがわかる.superDispatchTouchEvent(ev)がtrueを返すと、このイベントは消費されます.そうしないと、ActivityのonTouchEventメソッドを呼び出し、Windowがどのようにイベントを配布しているかを見てみましょう.
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
Windowは、mDecorのsuperDispatchTouchEventを呼び出します.mDecorって何??
// This is the top-level view of the window, containing the window decor.( )
private DecorView mDecor;
private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
}
FrameLayoutのdispatchTouchEventを呼び出しました.ここではDecorViewとは何かを説明します.DecorViewはFrameLayoutから継承されています.これはインタフェース全体の最外層のView Groupです.つまりActivity全体のルートレイアウトの外にはDecorViewが包まれており、私たちの携帯電話のタイトルバーはDecorViewに表示されています.これでTouchイベントはActivityから最上位のViewに移行しました.引き続きDecorViewがどのように配布されているかを見てみましょう.
/**
* {@inheritDoc}
*/
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
}
if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
ev.setTargetAccessibilityFocus(false);
}
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
if (actionMasked == MotionEvent.ACTION_DOWN) {
//
cancelAndClearTouchTargets(ev);
resetTouchState();
}
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
//
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action);
} else {
intercepted = false;
}
} else {
intercepted = true;
}
if (intercepted || mFirstTouchTarget != null) {
ev.setTargetAccessibilityFocus(false);
}
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
if (!canceled && !intercepted) {
//
View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
? findChildWithAccessibilityFocus() : null;
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
final int actionIndex = ev.getActionIndex(); // always 0 for down
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
: TouchTarget.ALL_POINTER_IDS;
removePointersFromTouchTargets(idBitsToAssign);
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
// Touch
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
final ArrayList preorderedList = buildOrderedChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
// View Touch
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = customOrder
? getChildDrawingOrder(childrenCount, i) : i;
final View child = (preorderedList == null)
? children[childIndex] : preorderedList.get(childIndex);
if (childWithAccessibilityFocus != null) {
if (childWithAccessibilityFocus != child) {
continue;
}
childWithAccessibilityFocus = null;
i = childrenCount - 1;
}
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
resetCancelNextUpFlag(child);
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
mLastTouchDownTime = ev.getDownTime();
if (preorderedList != null) {
for (int j = 0; j < childrenCount; j++) {
if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j;
break;
}
}
} else {
mLastTouchDownIndex = childIndex;
}
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
ev.setTargetAccessibilityFocus(false);
}
if (preorderedList != null) preorderedList.clear();
}
if (newTouchTarget == null && mFirstTouchTarget != null) {
newTouchTarget = mFirstTouchTarget;
while (newTouchTarget.next != null) {
newTouchTarget = newTouchTarget.next;
}
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}
}
if (mFirstTouchTarget == null) {
// (child view , view )
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
predecessor = target;
target = next;
}
}
if (canceled
|| actionMasked == MotionEvent.ACTION_UP
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
resetTouchState();
} else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
final int actionIndex = ev.getActionIndex();
final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
removePointersFromTouchTargets(idBitsToRemove);
}
}
if (!handled && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
}
return handled;
}
コードが少し多いので、簡単に4つの部分に分けて分析しました.
第1部:
private void cancelAndClearTouchTargets(MotionEvent event) {
...
clearTouchTargets();
...
}
private void clearTouchTargets() {
TouchTarget target = mFirstTouchTarget;
if (target != null) {
do {
TouchTarget next = target.next;
target.recycle();
target = next;
} while (target != null);
mFirstTouchTarget = null;
}
}
ACTION_DOWN時に初期化と復元を行います.mFirstTouchTargetをcancelAndClearTouchTargets()でnullに設定し、resetTouchState()でTouchステータスIDをリセットします.
第2部:
if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
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;
}
ViewGroupがTouchイベントをブロックする必要があるかどうかを確認します.
まず一番外の判断条件を見てみましょう.
actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null
ACTION_DOWNはよく理解していますが、mFirstTouchTargetとは何ですか??mFirstTouchTargetはTouchTargetクラスのオブジェクトであり、TouchTargetはView Groupの内部クラスであり、タッチされたViewおよび今回のタッチに対応するIDがカプセル化されている.mFirstTouchTargetは後で分析されるが、ここでは役に立つため、FirstTouchTargetの役割を先に説明する
(1) mFirstTouchTarget!= nullは、ViewGroupがTouchイベントをブロックせず、サブViewがTouchを消費したことを示している.
(2)mFirstTouchTarget==nullは、ViewGroupがTouchイベントをブロックしているか、ViewGroupがTouchイベントをブロックしていないがサブViewもTouchを消費していないことを示している.つまり、この場合はViewGroup自身がTouchイベントを処理する必要がある
つまりchild viewに消費ACTIONがなければDOWNは、その後のmoveとupが下に伝わらず、parent viewにブロックされます.事件がブロックされたら何をするのか、ブロックされていなければ何をするのか.
まずintercepted=falseがブロックしていないことから見てみましょう.
if (!canceled && !intercepted) {
...
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
....
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,View child, int desiredPointerIdBits) {
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
}
...
private TouchTarget addTouchTarget(View child, int pointerIdBits) {
TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
mFirstTouchTarget = target;
return target;
}
}
このステップでは、Touchイベントを消費できるサブViewが見つかった場合にのみ、mFirstTouchTargetはnullではありません.残りの場合、例えばTouchイベントを受信できるサブViewが見つからなかったり、サブViewがTouchイベントを消費できなかったりした場合、mFirstTouchTargetはnullのままです.
Touchイベントがキャンセルされずブロックされなかった場合、ViewGroupはACTION_のタイプになります.DOWNのTouchイベントはchild Viewに配布される.
(1) child == null
ViewGroupはoverrideでdispatchTouchEventメソッドを使用していますが、ViewGroupのdispatchTouchEventにはsuper親がいません.child==nullの場合、ViewGroupを通常のViewとして処理します.
(2) child != null呼び出しchild.dispatchTouchEventは配布を継続
intercepted=trueブロック
if (intercepted || mFirstTouchTarget != null) {
ev.setTargetAccessibilityFocus(false);
}
...
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS);
}
...
intercepted=true,if(!canceled&&!intercepted){…}が実行されないためmFirstTouchTarget==null,child=null,ViewGroupを通常のViewとして扱う(通常のViewのdispatchTouchEventはonTouchEvent()を呼び出す)
ViewのdispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent event) {
if (onFilterTouchEventForSecurity(event)) {
//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;
}
}
}
onTouchEventがfalseに戻ると、mFirstTouchTargetはnullとなり、ViewGroupを通常のViewとしてdispatchTouchEvent(onTouchEvent)を呼び出す.これは、child viewがこのイベントを消費できない場合、parentに返されるonTouchEventで消費され、parent viewも消費できず、ActivityのdispatchTouchEventに渡されるまでアップロードを続けるが、このときgetWindow()となる.superDispatchTouchEvent=falseで、ActivityのonTouchEventが呼び出されます.
まとめ
通俗的な言葉でまとめると、イベントが来たとき、ActivityはWindow、Windowというイベントを消費できるかどうかを尋ねます.Windowを見て、ちょっと待ってください.DecorViewに消費できるかどうかを聞いてみます.DecorViewを見て、onInterceptTouchEventはfalseに戻りましょう.止めさせないでください.すぐにViewを見て、彼らが消費できるかどうかを聞いてみましょう.あの人、イベントはあなたの体にかかっています.あなたが消費できるかどうかを見てください.RelativeLayoutは見ても、私に止めさせなかったでしょう.私もこの事件があの子Viewの上で発生したことを見なければなりません.そのTextView、事件はあなたの体にあります.あなたは彼を消耗することができますか.TextViewを見ると、消費できないよ、RelativeLayoutはTextViewを見ると消費できないよ、mFirstTouchTarget=nullよ、はい、自分で消費しましょう、うん!自分のonTouchEventを見ても消費できないでしょう!あのDecorView事件は私には消耗できません.DecorViewは自分を見ると、私も消耗できません.アップロードし続けます.あのWindowですね.イベントは消費できないよ、WindowはActivityイベントは消費できないと言ったよ.Activityは自分で来なければなりませんね.自分のonTouchEventを呼び出すと、やはり消費できません.もういいです.もういいです.
ぎじふごう
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean result = false; //
if (!onInterceptTouchEvent(ev)) { // View
result = child.dispatchTouchEvent(ev);
}
if (!result) { // , onTouchEvent
result = onTouchEvent(ev);
}
return result;
}
子View連ACTION_DOWNが消費できない場合、mFirstTouchTarget==null、その後のACTION_UP,ACTION_MOVE子Viewも二度と届かない