Android开发Android开发规范技巧Android开发

【view】- 触摸事件分发(2)

2020-03-27  本文已影响0人  拔萝卜占坑

简介

上一篇文章【view】- 触摸事件分发(1)讲解了从底层到上层的触摸事件传递。这篇文章将具体Activity,View组件的触摸事件的传递,以及事件的分类,拦截,不同的处理会引起触摸事件流程那些变化等。

注意

如果对源码分析不感兴趣,只想知道结论,可以直接翻到文章最后看总结,如果想了解源码实现,可以参照这篇文章,自己追踪源码。

Activity

public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) { onUserInteraction();}
    if (getWindow().superDispatchTouchEvent(ev)) {return true;}
    return onTouchEvent(ev);
}

如果是按下事件,在进行事件分发前调用onUserInteraction()方法,在Activity该方法是没有任何实现,我们可以重新该方法。

getWindow().superDispatchTouchEvent(ev)会把触摸事件交给整个View组件之间传递,如果返回true,在Activity中触摸事件传递结束,如果反复false者调用onTouchEvent(MotionEvent event)方法。

public boolean onTouchEvent(MotionEvent event) {
    if (mWindow.shouldCloseOnTouch(this, event)) {
        finish();
        return true;
    }
    return false;
}

Activity中逻辑很简单,下面讲解在DecorView顶层View之间的触摸事件传递。

DecorView

public boolean superDispatchTouchEvent(MotionEvent event) {
    return super.dispatchTouchEvent(event);
}

调用父类ViewGroup中的dispatchTouchEvent(MotionEvent ev)

辅助功能,残障。

if (mInputEventConsistencyVerifier != null) {
    mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
}
if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
    ev.setTargetAccessibilityFocus(false);
}

如果窗口是被遮盖,不可见,onFilterTouchEventForSecurity返回false,放弃这次触摸事件的处理。

public boolean onFilterTouchEventForSecurity(MotionEvent event) {
    if ((mViewFlags & FILTER_TOUCHES_WHEN_OBSCURED) != 0
            && (event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0) {
        // Window is obscured, drop this touch.
        return false;
    }
    return true;
}

如果是第一次按下操作,清除所有以前的状态,由于应用程序切换,ANR或某些其他状态更改,框架可能已放弃上一个手势的上移或取消事件,所以进行一些清理操作。

if (actionMasked == MotionEvent.ACTION_DOWN) {
    cancelAndClearTouchTargets(ev);
    resetTouchState();
}

int类型mGroupFlags变量的初始化,我把常量换成具体的值

rivate void initViewGroup() {
   ...
   mGroupFlags |= 0x1; 
   mGroupFlags |= 0x4;
   mGroupFlags |= 0x10
   mGroupFlags |= 0x40
   mGroupFlags |= 0x4000
   if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB) {
       mGroupFlags |=  0x200000
   }
   setDescendantFocusability(FOCUS_BEFORE_DESCENDANTS);
   ...
public void setDescendantFocusability(int focusability) {
   ...
    mGroupFlags &= ~0x60000;
    mGroupFlags |= (0x20000 & 0x60000);
}

检查拦截,如果是MotionEvent.ACTION_DOWN或者mFirstTouchTarget不等于null,调用onInterceptTouchEvent方法,判断当前View是否要要拦截触摸事件。

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;
}

如果被拦截,开始正常事件分发。另外,如果已经有一个正在处理手势的视图,则进行常规事件调度。

if (intercepted || mFirstTouchTarget != null) {
    ev.setTargetAccessibilityFocus(false);
}

如果事件没有撤销并且没有拦截,进入if代码块,把触摸事件分发给子控件。

获取手指索引,从注释看出,对于按下操作actionIndex ==0,单指操作。

final int actionIndex = ev.getActionIndex(); // always 0 for down
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
                            : TouchTarget.ALL_POINTER_IDS;

对子控件进行排序,因为控件会存在相互叠加的部分,优先顶层

 final ArrayList<View> preorderedList = buildTouchDispatchChildList();

从顶层View,也就是最先收到触摸事件的View向下遍历,获取子控件。

for (int i = childrenCount - 1; i >= 0; i--) {
    final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
    final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
    ...    
}

将不可见,在动画中,手指在控件之外不能接收触摸事件的控件过滤掉

if (!canViewReceivePointerEvents(child)
        || !isTransformedTouchPointInView(x, y, child, null)) {
    ev.setTargetAccessibilityFocus(false);
    continue;
}

将触摸事件分发给符合要求的子控件。

if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign))

在dispatchTransformedTouchEvent方法中,如果触摸事件已经取消或者撤销了,那么调用父类dispatchTouchEvent方法。

final int oldAction = event.getAction();
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
    event.setAction(MotionEvent.ACTION_CANCEL);
    if (child == null) {
        handled = super.dispatchTouchEvent(event);
    } else {
        handled = child.dispatchTouchEvent(event);
    }
    event.setAction(oldAction);
    return handled;
}

分发事件给子控件,调用子控件的dispatchTouchEvent方法

if (newPointerIdBits == oldPointerIdBits) {
    ...
        if (child == null) {
            handled = super.dispatchTouchEvent(event);
        } else {
            ...
            handled = child.dispatchTouchEvent(event);
            ...
        }
        return handled;
    }
   ...
}

如果子控件没有重写这个方法,那么会调用到View的dispatchTouchEvent方法。接下来分析View中的dispatchTouchEvent方法。

如果是按下操作,停止嵌套滚动

if (actionMasked == MotionEvent.ACTION_DOWN) {
    stopNestedScroll();
}

调用控件的onTouchEvent方法

if (!result && onTouchEvent(event)) {
    result = true;
}

看一下View中的onTouchEvent方法。

如果设置了委托触摸事件处理实例,者直接调用委托的onTouchEvent。

if (mTouchDelegate != null) {
    if (mTouchDelegate.onTouchEvent(event)) {return true;}
}

执行控件的点击事件监听回调中的onClick方法。

if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
    ...
        if (mPerformClick == null) {mPerformClick = new PerformClick();}
        if (!post(mPerformClick)) {performClickInternal();}
    }
}

View中的onTouchEvent方法剩余的逻辑自己分析,不在讲解。

回调ViewGroup中的dispatchTouchEvent方法中。如果dispatchTransformedTouchEvent方法返回true,代表被子空间或者父类处理了触摸事件,那么退出循环,不在分发事件给下一个子控件。

总结

下面容器以ViewGroup为中dispatchTouchEvent讲解,默认其它容器没有重写该容器里的方法。

ACTION_DOWN
ACTION_UP

按下并松开手指,这时候触发ACTION_UP事件。通过上面的分析这时候,如果ViewGroup在ACTION_DOWN不拦截,那么mFirstTouchTarget不等于null,那么不管现在ViewGroup是否拦截,不好意思,我都只会把触摸事件交给上一个消耗了ACTION_DOWN事件的View,如果都不消耗,我给Activity。

总结:如果你连ACTION_DOWN事件都不消耗,那么后面的事件序列你也别想消耗。

上一篇 下一篇

猜你喜欢

热点阅读