Androidのビューはツリー構造であり、ビューが重複する場合があります。クリックした場所に応答できるビューが複数ある場合、このクリックイベントは誰にする必要がありますか?この問題を解決するために、イベント配信メカニズムがあります。
1.コンセプト
1.イベントとは?
ユーザーがAndroidのインターフェースを操作するすべての操作、クリック、長押し、移動、リフトなどはイベントです。
同時に、画面に触れてから画面を離れるまでのすべての時間をイベントシーケンスと呼びます。前のイベントは次のイベントの先行イベントと呼ばれます。
MotionEvent:
Androidでは、各イベントはMotionEventオブジェクトとしてカプセル化され、さまざまなタイプ
があります。開発で一般的に使用される4つのタイプが導入されています。
ACTION_DOWN:指が画面に触れた場合にのみ、指が画面に触れたことを示します(押された、動かされなかった、持ち上げられなかった)
ACTION_MOVE:指が画面上を移動
ACTION_UP:指が画面から離れた
ACTION_CANCEL:
このイベントは特別です。トリガー条件は、ビューが先行イベントを受け取ったが、次のイベントが親コントロールによってインターセプトされたときです。このとき、ビューはACTION_CANCELイベントを受け取ります。
たとえば、ボタンをスクロールビューに配置すると、ボタンが押されたときにデフォルトでクリック可能になるため、ボタンはACTION_DOWNイベントを消費します。彼をドラッグすると、ACTION_MOVEイベントが親コントロールによってインターセプトされます。この時点で、ボタンはACTION_CANCELイベントを受け取ります
2.イベント配信とは何ですか?
各イベントは画面から各ビューに転送され、特定のビューはイベント(消費イベント)の処理またはイベント(消費イベントではない)の無視のプロセス全体を制御します。イベント配布メカニズムと呼ばれます。
階層関係を渡します:
Activity-> Window-> DecorView-> ViewGroup-> View
アクティビティはPhoneWindowオブジェクトを保持し、PhoneWindowはウィンドウの唯一のサブクラスです。各PhoneWindowオブジェクトはDecorViewを保持し、DecorViewはViewGroupから継承します。
第二に、プロセスに関連する詳細なソースコード
1、活動
フローチャート:
アクティビティのメインプロセス部分のソースコード:
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();//该方法是一个空的方法,在每一个事件序列一开始会进行调用
}
if (getWindow().superDispatchTouchEvent(ev)) {//从这里正式进入事件分发流程
return true;//表示事件被消费:结束
}
return onTouchEvent(ev);//调用Activity的 onTouchEvent 方法并返回结果:结束
}
.....
public boolean onTouchEvent(MotionEvent event) {
if (mWindow.shouldCloseOnTouch(this, event)) {//允许点击空白部分消失,且点击了window之外
finish();
return true;
}
return false;
}
ウィンドウのメインプロセスのソースコードの一部:
//该方法在Window类里面,在Activity onTouchEvent()方法里被调用
public boolean shouldCloseOnTouch(Context context, MotionEvent event) {
final boolean isOutside =
event.getAction() == MotionEvent.ACTION_DOWN && isOutOfBounds(context, event)
|| event.getAction() == MotionEvent.ACTION_OUTSIDE;
if (mCloseOnTouchOutside && peekDecorView() != null && isOutside) {
表示:支持点击空白部分消失、持有DecorView对象、点击的是当前window持有DecorView之外
return true;
}
return false;
}
getWindow()。superDispatchTouchEvent(ev):
PhoneWindowではcall-> superDispatchTouchEvent(ev)、DecorViewではPhoneWindow call-> superDispatchTouchEvent(ev)、DecorViewはViewGroupから継承するため 最後に、ViewGroupのdispatchTouchEvent(ev)メソッドが呼び出されます。
2、ViewGroup:
フローチャート:
メインプロセスのソースコードの一部:
ViewGroup:dispatchTouchEvent()は主に3つのことを行い
ます。1.イベントをインターセプトする必要があるかどうかを判断するには
2.現在のviewGroupでユーザーが実際にクリックしたビューを見つけます。3
.イベントをビューに配信します。
public boolean dispatchTouchEvent(MotionEvent ev) {
...
//从这里开始
boolean handled = false;//定义为整个方法返回值
if (onFilterTouchEventForSecurity(ev)) {是否符合安全策略,该方法在view中
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
if (actionMasked == MotionEvent.ACTION_DOWN) {//down事件,全部事件的开始
//当开始一个新的触摸手势时,扔掉所有以前的状态
cancelAndClearTouchTargets(ev);//清除所有触摸事件
resetTouchState();//重置状态
}
final boolean intercepted;//是否拦截的判断
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {//该值当事件被子view消费后被赋值
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);//一般情况下直接返回false,源码在下面
ev.setAction(action); // 恢复操作
} else {
intercepted = false;
}
} else {
intercepted = true;
}
...
// 检查是否是取消事件
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) {//不是取消事件且不拦截
//是按下事件
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
final int actionIndex = ev.getActionIndex(); // 事件索引
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
: TouchTarget.ALL_POINTER_IDS;
//清除早期触摸事件
removePointersFromTouchTargets(idBitsToAssign);
final int childrenCount = mChildrenCount;//子view数量
if (newTouchTarget == null && childrenCount != 0) {
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
// 获取所有能接收事件的子view.
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
//是否自定义view绘制顺序
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
//从前到后遍历子view
for (int i = childrenCount - 1; i >= 0; i--) {
//获取view的真实索引,解释在下方
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
//根据索引获取目标view
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
...
//当前view是否能接受事件,当前view是否在该事件点击范围之内
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
// 表示当前子view获取到了触摸事件
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
resetCancelNextUpFlag(child);
//在dispatchTransformedTouchEvent方法中进行下层view的分发
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
.....
//一旦返回为true,则代表事件被子view消费
//addTouchTarget 在方法中为mFirstTouchTarget赋值
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
ev.setTargetAccessibilityFocus(false);
}
//循环结束
if (preorderedList != null) preorderedList.clear();
}
.....
}
}
if (mFirstTouchTarget == null) {
// 依然没有子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 (!handled && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
}
return handled;//最后返回
}
//是否拦截事件
public boolean onInterceptTouchEvent(MotionEvent ev) {
//是鼠标事件且点击了左键,正常手机操作不会出现这样情况,故返回false
if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
&& ev.getAction() == MotionEvent.ACTION_DOWN
&& ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
&& isOnScrollbarThumb(ev.getX(), ev.getY())) {
return true;
}
return false;
}
//获取子view的真实索引
private int getAndVerifyPreorderedIndex(int childrenCount, int i, boolean customOrder) {
final int childIndex;
if (customOrder) {//是否自定义
//该方法需要在自定义绘制顺序时去实现,否则默认返回i
final int childIndex1 = getChildDrawingOrder(childrenCount, i);
if (childIndex1 >= childrenCount) {
throw new IndexOutOfBoundsException("getChildDrawingOrder() "
+ "returned invalid index " + childIndex1
+ " (child count is " + childrenCount + ")");
}
childIndex = childIndex1;
} else {
childIndex = i;
}
return childIndex;
}
//根据索引获取目标view
private static View getAndVerifyPreorderedView(ArrayList<View> preorderedList, View[] children,
int childIndex) {
final View child;
if (preorderedList != null) {
child = preorderedList.get(childIndex);
if (child == null) {
throw new RuntimeException("Invalid preorderedList contained null child at index "
+ childIndex);
}
} else {
child = children[childIndex];
}
return child;
}
//进行下层view的分发,主流程相关部分代码
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
//是否是取消事件
final int oldAction = event.getAction();
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
event.setAction(MotionEvent.ACTION_CANCEL);
if (child == null) {
//调用的是View的dispatchTouchEvent
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}
.........
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);
}
// 重置
transformedEvent.recycle();
return handled;
}
ビューの関連メソッド:
//是否符合安全策略
public boolean onFilterTouchEventForSecurity(MotionEvent event) {
//表示接收此事件部分的窗口被完全遮挡,不处于顶部
if ((mViewFlags & FILTER_TOUCHES_WHEN_OBSCURED) != 0
&& (event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0) {
return false;
}
return true;
}
3、見る
フローチャート:
メインプロセスのソースコード分析:
public boolean dispatchTouchEvent(MotionEvent event) {
if (event.isTargetAccessibilityFocus()) {
// 没有焦点,不能获取到事件
if (!isAccessibilityFocusedViewOrHost()) {
return false;
}
// 我们有焦点,得到事件,然后使用正常的事件调度。
event.setTargetAccessibilityFocus(false);
}
boolean result = false;
...
final int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
// 停止滚动
stopNestedScroll();
}
//以下是在view中事件分发的核心逻辑
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)) {
//注册过OnTouchListener监听,且调用的onTouch返回true
//注:OnTouchListener监听在此时被调用
result = true;
}
if (!result && onTouchEvent(event)) {
//没有注册过OnTouchListener监听或者onTouch返回false、调用onTouchEvent返回true
//注:onTouchEvent在此时被调用
result = true;
}
}
...
return result;
}
//该方法中调用mOnClickListener
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction();
//是否可点击
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
return clickable;//如果设置过禁止点击,直接返回可点击状态
}
//是否有点击事件代理
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {//内部为默认处理逻辑
case MotionEvent.ACTION_UP:
...
if (!focusTaken) {
if (!post(mPerformClick)) {
performClickInternal();//在此方法中调用mOnClickListener监听
}
}
...
break;
...
}
return true;
}
return false;
}
ビューで行うこと:
1.フォーカス
があるかどうかを決定します。2.マウスイベント
かどうかを決定します。3. OnTouchListener監視があるかどうかを確認し、監視インターフェイスのonTouchメソッドを呼び出し、onTouchEventメソッドを呼び出し、OnClickListener監視があるかどうかを決定して、onClickを呼び出します。
注:onInterceptTouchEventイベントはViewGroupでのみ使用できます。Activityはインターセプトなしで配信を開始するイベントであり、ビューはイベントを受信する最後のイベントであり、インターセプトする必要はありません。
注:
getParent().requestDisallowInterceptTouchEvent(true)
//该方法可以控制父控件是否进行拦截,true为不拦截,false为拦截。其原理是控制是否调用onInterceptTouchEvent方法