View事件的分发(思路)

在这里插入图片描述
图1
首先认识一下Android界面的架构图,每个Activity中包含一个Window对象,这个Window对象通常由PhoneWindow来实现,然后将DecorView作为根view,DecorView是继承自FrameLayout即是一个ViewGroup,DecorView中有两个view一个是titleView,一个是ContentView(一个ID为content的FrameLayout)。
在这里插入图片描述

onTouchEvent()是View和Activity中的方法,默认返回false,如果返回true将对事件进行拦截,表示在当前view层级处理完整(down、move、up)点击事件,将不传递给上一层级的ViewGroup。
dispatchTouchEvent()既在view中也在ViewGroup中,还在activity中,返回值由OnTouchEvent决定。
onInterceptTouchEvent()是viewGroup中的方法,默认false。为true将事件进行截断,不再分发(即不再传事件给下一个view)。
使用一个例子说明:

图3
新建一个layout.xml,根布局为FrameLayout,其下有一个LinearLayout布局,两者皆属于ViewGroup,在下面有一个Button按钮是View控件。将图1和图3结合来看,ui布局时层级为Activity -->PhoneWindow --> DecorView(content中)–>FrameLayout–> LinearLayout–>Button。而View事件的分发刚好是从activity开始的,和ui开始布局的顺序一样。

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

DM1:Activity.java

@Override
public boolean superDispatchTouchEvent(MotionEvent event){
    return mDecor.superDispatchTouchEvent(event);
}

DM2:PhoneWindow.java

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

DM3 DecorView.java
通过以上三段代码来看,DM1中通过判断PhoneWindow中的dispatchTouchEvent来决定事件是否被消费了,没有消费(false)就交给Activity中的OnTouchEvent来处理。DM2中将事件分发给了DecorView。DM3中,DecorView继承自FrameLayout,所以事件就分发到了跟ViewGroup,也就是到了View中。
此时回到例子中,我们可以通过在LinearLayout和FrameLayout中重写方法OnTouchEvent()、OnInterceptTouchEvent()和dispatchTouchEvent(),在Button中重写OnTouchEvent()和dispatchTouchEvent(),并且添加log打印日志。
在这里插入图片描述
通过log日志来看,正常执行顺序时,正常执行就是从Activity到view在回到Activity中执行OnTouchEvent(),因为事件没有被消费。使用OnInterceptTouchEvent产生截断后,事件不再向下分发。使用OnTouchEvent产生截断时,表明事件被消费,所以就不再向上传递。可以看出事件的操作是由OnInterceptTouchEvent和OnTouchEvent来控制的。可以得到一个自定义view的技巧,OnTouchEvent消费以后,事件没有了,后续的OnTouchEvent不再被调用,所以有的操作可以放在DispatchTouchEvent中来避免这种情况的发生。可以得到以下图示:
在这里插入图片描述
图4
图4表明了事件在View中的传递顺序,OnInterceptTouchEvent和OnTouchEvent发生的位置。两者是根view(DecorView)的起始。
在这里插入图片描述
图5
图5表示了OnInterceptTouchEvent在LinearLayout返回true时,事件的传递顺序。
在这里插入图片描述
图6

在这里插入图片描述

图7
通过以上来看事件的分发就不叫明了了,至于为什么这样,就得查看源码了,一下是部分源码,具体没有仔细深究,但通过反推,可以大致明了:

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 {
    intercepted = true;
}
ViewGroup中dispatchTouchEvent
Intercepted表示是否截断事件,通过此可以看出通常情况下intercepted就是由OnInterceptTouchEvent(默认false)决定的。
final boolean cancelChild = resetCancelNextUpFlag(target.child)
        || intercepted;
if (dispatchTransformedTouchEvent(ev, cancelChild,
        target.child, target.pointerIdBits)) {
    handled = true;
}
ViewGroup中dispatchTouchEvent
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
if (!canceled && !intercepted) {
…………省略
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;
}
}

ViewGroup中dispatchTouchEvent
如果intercepted为false,会通过事件分发给下一个view。如果intercepted设为true,则不走这一步,则newTouchTarget为空,alreadyDispatchedToNewTouchTarget=false。

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

mFirstTouchTarget不为空,存放接受了触摸事件的view的链表。Target != newTouchTarget,resetCancelNextUPFlag(target.child)方法(重新设置取消的下一个标记。如果之前设置该标志,则返回true。),cancelChild = intercepted = true。
final boolean handled;

// Canceling motions is a special case.  We don't need to perform any transformations
// or filtering.  The important part is the action, not the contents.
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;
// Calculate the number of pointers to deliver.
final int oldPointerIdBits = event.getPointerIdBits();
final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;

// If for some reason we ended up in an inconsistent state where it looks like we
// might produce a motion event with no pointers in it, then drop the event.
if (newPointerIdBits == 0) {
    return false;
}

// If the number of pointers is the same and we don't need to perform any fancy
// irreversible transformations, then we can reuse the motion event for this
// dispatch as long as we are careful to revert any changes we make.
// Otherwise we need to make a copy.
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;

}

ViewGroup中DispatchTransformedTouchEvent
此方法主要决定是将view分发给子view,或者调用父类view中的dispatchTransformedTouchEvent()。事件到view中以后就是主要调用OnTouchEvent了。OnTouchListener>onTouchEvent>onLongClickListener>onClickListener 优先级,后者不触发。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值