事件分发是基础的知识点,开发中会经常遇到,平时开发只是为了解决问题,片面的理解,不够深入,不成体系。这两天有点时间,从 View,ViewGroup 到 Activity 体系的学习总结一下。
我们知道view事件处理顺序是(Down,Move,Up):
a、dispatchTouchEvent
b、 setOnTouchListener的onTouch
c、onTouchEvent
2.1、首先看下view的dispatchTouchEvent:
public boolean dispatchTouchEvent(MotionEvent event) {
if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
mOnTouchListener.onTouch(this, event)) {
return true;
}
return onTouchEvent(event);
}
第二行if的判断,mOnTouchListener != null,(mViewFlags & ENABLED_MASK) == ENABLED和mOnTouchListener.onTouch(this, event)这三个条件都为真,就返回true,否则就去执行onTouchEvent(event)方法并返回。
第一个条件mOnTouchListener != null,根据view源码找到是在:
public void setOnTouchListener(OnTouchListener l) {
mOnTouchListener = l;
}
可以看出如果我们注册了touch事件,mOnTouchListener 就不会为null。
第二个条件(mViewFlags & ENABLED_MASK) == ENABLED是判断当前点击的控件是否是enable的,按钮默认都是enable的,因此这个条件恒定为true。
第三个条件mOnTouchListener.onTouch(this, event);这里也就是注册的touch事件返回值,如果返回ture,就会让这三个条件全部成立,从而整个方法直接返回true,如果返回false,就会再去执行onTouchEvent(event)方法。
看下onTouchEvent(event)源码(部分代码):
public boolean onTouchEvent(MotionEvent event) {
final int viewFlags = mViewFlags;
if ((viewFlags & ENABLED_MASK) == DISABLED) {
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
}
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;
if ((mPrivateFlags & PRESSED) != 0 || prepressed) {
// take focus if we don't have it already and we should in
// touch mode.
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
if (!mHasPerformedLongPress) {
// This is a tap, so remove the longpress check
removeLongPressCallback();
// Only perform take click actions if we were in the pressed state
if (!focusTaken) {
// Use a Runnable and post this rather than calling
// performClick directly. This lets other visual state
// of the view update before click actions start.
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClick();
}
}
}
if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}
if (prepressed) {
mPrivateFlags |= PRESSED;
refreshDrawableState();
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
// If the post failed, unpress right now
mUnsetPressedState.run();
}
removeTapCallback();
}
break;
case MotionEvent.ACTION_DOWN:
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
mPrivateFlags |= PREPRESSED;
mHasPerformedLongPress = false;
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
break;
case MotionEvent.ACTION_CANCEL:
mPrivateFlags &= ~PRESSED;
refreshDrawableState();
removeTapCallback();
break;
case MotionEvent.ACTION_MOVE:
final int x = (int) event.getX();
final int y = (int) event.getY();
// Be lenient about moving outside of buttons
int slop = mTouchSlop;
if ((x < 0 - slop) || (x >= getWidth() + slop) ||
(y < 0 - slop) || (y >= getHeight() + slop)) {
// Outside button
removeTapCallback();
if ((mPrivateFlags & PRESSED) != 0) {
// Remove any future long press/tap checks
removeLongPressCallback();
// Need to switch from pressed to not pressed
mPrivateFlags &= ~PRESSED;
refreshDrawableState();
}
}
break;
}
return true;
}
return false;
}
代码有点多,不用慌,我们看重点,手指抬起是进入up这个case中,经过处理会进入performClick()方法中:
public boolean performClick() {
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
if (mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
mOnClickListener.onClick(this);
return true;
}
return false;
}
如果mOnClickListener !=null,就会执行onClick,到这里可以看到在dispatchTouchEvent中最先执行的就是onTouch方法,因此onTouch肯定是要优先于onClick执行的,而如果在onTouch方法里返回了true,就会让dispatchTouchEvent方法直接返回true,不会再继续往下执行onClick就不会再执行了。
先写一个demo:
TouchEventFather 继承自 LinearLayout
public class TouchEventFather extends LinearLayout {
public TouchEventFather(Context context) {
super(context);
}
public TouchEventFather(Context context, AttributeSet attrs) {
super(context, attrs);
}
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.e("TouchEventFather", "dispatchTouchEvent: " + TouchEventUtil.getTouchAction(ev.getAction()));
return super.dispatchTouchEvent(ev);
}
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.e("TouchEventFather", " onInterceptTouchEvent:" + TouchEventUtil.getTouchAction(ev.getAction()));
return super.onInterceptTouchEvent(ev);
}
public boolean onTouchEvent(MotionEvent ev) {
Log.e("TouchEventFather", "onTouchEvent :" + TouchEventUtil.getTouchAction(ev.getAction()));
return super.onTouchEvent(ev);
}
}
MyButton 继承自 Button :
public class MyButton extends Button {
private static final String TAG = "MyButton";
public MyButton(Context context) {
super(context);
}
public MyButton(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.e(TAG, "onTouchEvent: " + TouchEventUtil.getTouchAction(event.getAction()));
return super.onTouchEvent(event);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
Log.e(TAG, "dispatchTouchEvent: " + TouchEventUtil.getTouchAction(event.getAction()));
return super.dispatchTouchEvent(event);
}
}
在xml中的:
在activity中(部分代码):
private void initView() {
btn = (Button) findViewById(R.id.touch_my_button);
btn.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.e(TAG, "MyButton|onTouch: " + TouchEventUtil.getTouchAction(ev.getAction()));
return false;
}
});
}
//重写dispatchTouchEvent
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.e(TAG, " dispatchTouchEvent: " + TouchEventUtil.getTouchAction(ev.getAction()));
return super.dispatchTouchEvent(ev);
}
//重写onTouchEvent
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.e(TAG, " onTouchEvent:" + TouchEventUtil.getTouchAction(event.getAction()));
return super.onTouchEvent(event);
}
现在我们点击MyButton 控件,打印出的日志是这样:
从日志可以看出,事件大致分发流程为:TouchActivity的dispatchTouchEvent ->TouchEventFather的dispatchTouchEvent-> TouchEventFather的onInterceptTouchEvent -> MyButton的dispatchTouchEvent ->Mybutton的onTouchEvent ->Mybutton的onTouchEvent 。
在TouchEventFather控件中出现一个onInterceptTouchEvent方法, 看下源码:
public boolean onInterceptTouchEvent(MotionEvent ev) {
return false;
}
只有三行代码,默认返回flse,表示不拦截事件,ture表示拦截事件,不在继续向下传递,事件在当前控件处消费处理。
再来看下dispatchTouchEvent(MotionEvent ev)源码(部分代码):
public boolean dispatchTouchEvent(MotionEvent ev) {
if (action == MotionEvent.ACTION_DOWN) {
if (mMotionTarget != null) {
mMotionTarget = null;
}
if (disallowIntercept || !onInterceptTouchEvent(ev)) {
ev.setAction(MotionEvent.ACTION_DOWN);
final int scrolledXInt = (int) scrolledXFloat;
final int scrolledYInt = (int) scrolledYFloat;
final View[] children = mChildren;
final int count = mChildrenCount;
for (int i = count - 1; i >= 0; i--) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
|| child.getAnimation() != null) {
child.getHitRect(frame);
if (frame.contains(scrolledXInt, scrolledYInt)) {
final float xc = scrolledXFloat - child.mLeft;
final float yc = scrolledYFloat - child.mTop;
ev.setLocation(xc, yc);
child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
if (child.dispatchTouchEvent(ev)) {
mMotionTarget = child;
return true;
}
}
}
}
}
}
}
在第6行可以看到一个条件判断,如果disallowIntercept和!onInterceptTouchEvent(ev)其中有一个为true,就会进入到这个条件判断中。disallowIntercept是指是否禁用掉事件拦截的功能,默认是false,也可以通过调用requestDisallowInterceptTouchEvent方法对这个值进行修改。那么当第一个值为false的时候就会完全依赖第二个值来决定是否可以进入到条件判断的内部,也就是onInterceptTouchEvent方法的返回值取反!在我们onInterceptTouchEvent方法中返回false,就会让第二个值为true,从而进入到条件判断的内部,如果我们在onInterceptTouchEvent方法中返回true,就会让第二个值为false,从而跳出了这个条件判断。
如果条件成立进入到第十二行,会遍历viewGroup中包涵的所有子view,接下来判断当前view是不是正在点击的view,如果是继续调用子view的dispatchTouchEvent方法,我们知道控件如果可点击dispatchTouchEvent返回值为true,那么 if (child.dispatchTouchEvent(ev)) 条件成立,当前viewGroup的dispatchTouchEvent方法返回true,所以viewGroup后面的方法也不在执行(上图日志也得以验证,TouchEventFather的onTouchEvent没有执行 )。
4.1、view事件执行顺序dispatchTouchEvent-> setOnTouchListener的onTouch->onTouchEvent,如果setOnTouchListener返回ture,后续事件onTouchEvent不在执行
4.2、onClick是在onTouchEvent(event)方法中的,所以onTouch优先于onClick执行
4.3、Android 点击事件执行顺序是Activity—>ViewGroup—>View
4.4、如果子View将传递的事件消费掉,ViewGroup中将无法接收到任何事件
4.5、在ViewGroup中onInterceptTouchEvent方法对事件传递进行拦截,onInterceptTouchEvent方法返回true代表不允许事件继续向子View传递,把事件交给自己处理,则会执行自己对应的onTouchEvent方法。返回false代表不对事件进行拦截,事件继续向下传递,默认返回false(也可以通过调用requestDisallowInterceptTouchEvent方法对这个值进行修改)
4.6、如果重写dispatchTouchEvent方法,dispatchTouchEvent无论返回true还是false,事件都不再进行分发, 只有当其返回super.dispatchTouchEvent(ev),才表明其具有向下层分发的愿望。