blog

ビューのイベント配信メカニズム

この記事では、ビューツリー内の入力イベントの配信メカニズムを紹介します。 入力イベントは、WMSの作成時に確立され、WMSの関連付けを介してWMSに配布され、実際には、2つ、これらの2つは、ソケットパ...

Aug 27, 2020 · 6 min. read
シェア

今回は、Viewツリーで入力イベントを分散させる仕組みを紹介します。

イベント生成

入力イベントは、InputManager の InputDispatcher を介して WMS に配信されます。WMS との関連付けは、ViewRootImpl が生成される際に確立されます。この 2 つの InputChannel は、それぞれイベント情報の読み書きを担当するソケットペアであり、 ViewRootImpl を介してビューツリーにイベントを配信することができます。

final class ViewPostImeInputStage extends InputStage {
 @Override
 protected int onProcess(QueuedInputEvent q) {
 if (q.mEvent instanceof KeyEvent) {
 return processKeyEvent(q);
 } else {
  
 final int source = q.mEvent.getSource();
 if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
 return processPointerEvent(q);
 } 
  
 }
 }
}
private int processPointerEvent(QueuedInputEvent q) {
 final MotionEvent event = (MotionEvent)q.mEvent;
 if (mView.dispatchPointerEvent(event)) {
 return FINISH_HANDLED;
 }
 return FORWARD;
}
public final boolean dispatchPointerEvent(MotionEvent event) {
 if (event.isTouchEvent()) {
 return dispatchTouchEvent(event);
 } else {
 return dispatchGenericMotionEvent(event);
 }
}

生成されたイベントは、最終的にビューツリーのルートノードであるDecorViewに配布され、Touchイベントの場合は、dispatchTouchEventを通して配布されます。

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
 if (mInputEventConsistencyVerifier != null) {
 mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
 }
 boolean handled = false;
 if (onFilterTouchEventForSecurity(ev)) {
 final int action = ev.getAction();
 final int actionMasked = action & MotionEvent.ACTION_MASK;
 
  
 // Check for interception.
 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); // 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;
 }
 // Check for cancelation.
 final boolean canceled = resetCancelNextUpFlag(this)
 || actionMasked == MotionEvent.ACTION_CANCEL;
 // Update list of touch targets for pointer down, if needed.
 final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
 TouchTarget newTouchTarget = null;
 boolean alreadyDispatchedToNewTouchTarget = false;
 if (!canceled && !intercepted) {
 if (actionMasked == MotionEvent.ACTION_DOWN
 || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
 || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
  
 final int childrenCount = mChildrenCount;
 if (newTouchTarget == null && childrenCount != 0) {
 final float x = ev.getX(actionIndex);
 final float y = ev.getY(actionIndex);
 // Find a child that can receive the event.
 // Scan children from front to back.
 final View[] children = mChildren;
 final boolean customOrder = isChildrenDrawingOrderEnabled();
 for (int i = childrenCount - 1; i >= 0; i--) {
 final int childIndex = customOrder ?
 getChildDrawingOrder(childrenCount, i) : i;
 final View child = children[childIndex];
 if (!canViewReceivePointerEvents(child)
 || !isTransformedTouchPointInView(x, y, child, null)) {
 continue;
 }
 newTouchTarget = getTouchTarget(child);
 if (newTouchTarget != null) {
 // Child is already receiving touch within its bounds.
 // Give it the new pointer in addition to the ones it is handling.
 newTouchTarget.pointerIdBits |= idBitsToAssign;
 break;
 }
 resetCancelNextUpFlag(child);
 if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
 // Child wants to receive touch within its bounds.
 mLastTouchDownTime = ev.getDownTime();
 mLastTouchDownIndex = childIndex;
 mLastTouchDownX = ev.getX();
 mLastTouchDownY = ev.getY();
 newTouchTarget = addTouchTarget(child, idBitsToAssign);
 alreadyDispatchedToNewTouchTarget = true;
 break;
 }
 }
 }
 if (newTouchTarget == null && mFirstTouchTarget != null) {
 // Did not find a child to receive the event.
 // Assign the pointer to the least recently added target.
 newTouchTarget = mFirstTouchTarget;
 while (newTouchTarget.next != null) {
 newTouchTarget = newTouchTarget.next;
 }
 newTouchTarget.pointerIdBits |= idBitsToAssign;
 }
 }
 }
 // Dispatch to touch targets.
 if (mFirstTouchTarget == null) {
 // No touch targets so treat this as an ordinary 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;
 }
 }
  
 }
 return handled;
}

ViewGroupのdispatchTouchEventでは、主に以下のことが行われています:

  1. イベントの傍受が許可されているかどうかを判断し、傍受が許可されている場合は、onInterceptTouchEventを通じてイベントを傍受し、処理結果を保存します。
  2. あなたが処理をインターセプトしない場合は、ACTION_DOWNのTargetViewを見つける必要があり、実際には、それはイベントが処理のためにサブビューに配布されます、その処理のためのサブビューである場合、dispatchTransformedTouchEventでtrueを返すと、それはTargetViewであり、後続のACTION_MOVE、ACTION_UPイベントはそれに送られます。
  3. TargetViewが見つからない場合、イベント処理のためにViewGroup自身に渡されます。
public boolean dispatchTouchEvent(MotionEvent event) {
 if (mInputEventConsistencyVerifier != null) {
 mInputEventConsistencyVerifier.onTouchEvent(event, 0);
 }
 if (onFilterTouchEventForSecurity(event)) {
 ListenerInfo li = mListenerInfo;
 if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
 && li.mOnTouchListener.onTouch(this, event)) {
 return true;
 }
 if (onTouchEvent(event)) {
 return true;
 }
 }
 if (mInputEventConsistencyVerifier != null) {
 mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
 }
 return false;
}

このイベントのonTouch消費は、その後、直接onTouchEvent処理を通じて、サブビューがイベントを処理することをtrueを返す場合は、onTouch処理を通じて、最初のイベントのサブビューは、同じ、イベントを処理することをtrueを返す、ビューのdispatchTouchEvent同様に、ViewのdispatchTouchEventはtrueを返します。

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 {
 transformedEvent = event.split(newPointerIdBits);
 }
 // Perform any necessary transformations and dispatch.
 if (child == null) {
 handled = super.dispatchTouchEvent(transformedEvent);
 } else {
 final float offsetX = mScrollX - child.mLeft;
 final float offsetY = mScrollY - child.mTop;
 transformedEvent.offsetLocation(offsetX, offsetY);
 if (! child.hasIdentityMatrix()) {
 transformedEvent.transform(child.getInverseMatrix());
 }
 handled = child.dispatchTouchEvent(transformedEvent);
 }
 // Done.
 transformedEvent.recycle();
 return handled;
}

dispatchTransformedTouchEventでは、childがnullの場合、つまり子ビューが存在しないか、子ビューがイベントを処理しない場合、イベントはViewGroup自身のdispatchTouchEventによって消化されます。handled が true の場合、イベントは消費されるため、ビューツリーでイベントを渡す必要はありません。

ビューのツリーのイベント配信機構は、比較的簡単な答えは、読者は、関連するコードのビューとviewGroup dispatchTouchEvent部分を読むことができます。

Read next

ナビゲーション・ツリーのマルチ・チェックボックス はコメントを受け付けていません。

擬似要素を配置し、画像やフォントアイコンスタイルに変更することで、ツリーのクリックイベントの親子リンクでチェックあり・なしのステータスを決定するスタイル

Aug 27, 2020 · 2 min read