1 常见滑动冲突举例
2 View事件分发机制源码分析
当一个点击操作发生时,事件最先传递给当前Activity,由Activity的dispatchTouchEvent来进行事件派发。
(1)Activity
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
onUserInteraction是空实现,没做任何处理。事件交给了Activity所属PhoneWindow的superDispatchTouchEvent进行分发。
再来看看superDispatchTouchEvent
(2)PhoneWindow
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
其中mDecor为DecorView,所以事件交给了DecorView.下面代码简要说明了DecorView实例化的过程
@Override
public final View getDecorView() {
if (mDecor == null) {
installDecor();
}
return mDecor;
}
private void installDecor() {
if (mDecor == null) {
mDecor = generateDecor();
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
......
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
mTitleView = (TextView)findViewById(com.android.internal.R.id.title);
if (mTitleView != null) {
mTitleView.setLayoutDirection(mDecor.getLayoutDirection());
......
mTitleView.setText(mTitle);
protected DecorView generateDecor() {
return new DecorView(getContext(), -1);
}{
protected ViewGroup generateLayout(DecorView decor){
......
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
......
return contentParent;
}
我们知道,在Activity中通过getWindow().getDecorView()就可获得Activity当中的DecorView ,这里提前看看DecorView在View体系的地位。
(3)DecorView(PhoneWindow内部类)
private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {......}
在这里事件已经到了Root view——DecorView,DecorView继承自FrameLayout 。public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
这里super.dispatchTouchEvent调用到了ViewGroup中的dispatchTouchEvent
(4)ViewGroup
这里重点分析dispatchTouchEvent方法
@Override
public boolean dispatchTouchEvent(MotionEvent ev){
......
//如果是ACTION_DOWN,就重置FLAG_DISALLOW_INTERCEPT,让ViewGroup一定能执行onInterceptTouchEvent
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Throw away all previous state when starting a new touch gesture.
// The framework may have dropped the up or cancel event for the previous gesture
// due to an app switch, ANR, or some other state change.
cancelAndClearTouchTargets(ev);
//重置FLAG_DISALLOW_INTERCEPT
resetTouchState();
}
//当ViewGroup子元素成功处理后,mFirstTouchTarget会被赋值并指向处理事件的子元素(即mFirstTouchTarget!=null)
// 当事件为ACTION_DOWN时,if (actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget != null)肯定为true,一定会执行
//但是当事件为ACTION_MOVE或者ACTION_UP时,如果子元素没有处理ACTION_DOWN事件,则mFirstTouchTarget==null,
//if (actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget != null)肯定为false
//则ViewGroup的onInterceptTouchEvent将不会执行,同一序列的其他事件就默认交给他处理了
//FLAG_DISALLOW_INTERCEPT设置后ViewGroup无法拦截除ACTION_DOWN以外的事情
//ACTION_DOWN因为上面已经重置FLAG_DISALLOW_INTERCEPT,所以ACTION_DOWN事件时,一定会被ViewGroup的onInterceptTouchEvent执行
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;
}
由上可知:ACTION_DOWN事件时,一定会被ViewGroup的onInterceptTouchEvent执行
如果ViewGroup不拦截事件,事件会下发分配给它的子View进行处理
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.
......
newTouchTarget = addTouchTarget(child, idBitsToAssign);
break;
}
}
首先遍历ViewGroup的所有子元素,判断子元素是否能接收到点击事件。其中,dispatchTransformedTouchEvent方法中里面有这样几行代码
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
return handled;
如果handled 返回true,则dispatchTransformedTouchEvent返回true,mFirstTouchTarget就会被赋值并终止对子元素的继续遍历,如果子元素dispatchTransformedTouchEvent返回false,ViewGroup就会把事件分发给下一个子元素,如果下一个子元素不存在,就往上传。
(5)View
这里再来看看View中的dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent event) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
if (onFilterTouchEventForSecurity(event)) {
//noinspection SimplifiableIfStatement
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;
}
}
.......
return false;
}
这里首先判断mOnTouchListener ,如果mOnTouchListener.onTouch返回True,则onTouchEvent不会被调用,所以OnTouchListener优先级高于onTouchEvent.
再来看看onTouchEvent
View在DISABLED状态下,也可以消耗点击事件,只要View的CLICKABLE和LONG_CLICKABLE有一个为true,它就消耗这个事件,即
onTouche返回true,不管view是不是DISABLE状态。
如果View设置了OnclickListener,那么performClick方法会调用他的onClick方法。
public boolean performClick() {
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
li.mOnClickListener.onClick(this);
return true;
}
return false;
}
View的LONG_CLICKABLE默认为false,其中Button的CLICKABLE默认为true,TextView的CLICKABLE默认为false。另外setOnClickListener会自动将View的CLICKABLE
设为true,setOnLongClickListener会自动将View的LONG_CLICKABLE设为true
public void setOnClickListener(OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
getListenerInfo().mOnClickListener = l;
}
public void setOnLongClickListener(OnLongClickListener l) {
if (!isLongClickable()) {
setLongClickable(true);
}
getListenerInfo().mOnLongClickListener = l;
}
3 事件分发机制总结
主要涉及到dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent三个方法
他们的关系如下
public boolean dispatchTouchEvent(MotionEvent ev){
boolean consume=false;
if(onInterceptTouchEvent(ev)){
consume=onTouchEvent(ev);
}else{
consume=child.dispatchTouchEvent(ev);
}
return consume;
}
1.对于一个根ViewGroup,点击事件产生后,先产生ACTION_DOWN事件,然后ACTION_DOWN首先传递给ViewGroup
ViewGroup的dispatchTouchEvent方法返回true表示它要拦截当前事件,接着它的onTouchEvent会被调用;如果ViewGroup
的onInterceptTouchEvent方法返回false就表示它不拦截当前事件,事件就继续传递给它的子元素,接着子元素的dispatchTouchEvent
就会被调用(子元素可能是ViewGroup或者View,他们的dispatchTouchEvent复写方法不同),如果子元素消耗事件,则退出遍历。
如果子元素不消耗,则继续遍历其他子元素,直到遍历结束,如果还没有子元素消耗,子元素的父容器onTouchEvent就会被调用。
即 Activity——>Window——>DocorView——>view(子元素可能很多,且子元素也可能有ViewGroup,遍历访问)——>ViewGroup——>Activity
2.另外,如果一个View设置了onTouchListener,那么OnTouchListener的onTouch方法会被调用,如果返回true,onTouchEvent将不会被调用,
所以OnTouchListener优先级比onTouch高。如果设置了onClickListener,那么他的onClick会在onTouchEvent中调用,所以onClickListener
优先级最低。
3 事件序列以down开始,up结束,中间会包含很多move事件
4 一旦一个view拦截了事件(拦截了ACTION_DOWN),那么这个事件序列其他事件都交给他处理
5一个View,如果不消耗ACTION_DOWN事件,则同一时间的其他事件也不会交给他处理
6.