其实一直不太理解安卓的事件处理机制,不过今天看到一位大神写的突然有点明白了,文章始于一个问题:touch和click的关系,我们都知道,当我们触摸一个view的时候都会调用改view的touch事件(这个调用是100%的)但是不一定会调用view的click事件,因为有的view是不支持clickable,比如imageview,当然我们可以设置这个imageview可以点击,那么这个view是有一定的可能性调用onCLick事件,下面我们通过一个demo来说明这个问题的原因,demo是一个activity,当中只有一个button,我们对其设置onclick和ontouch事件,注意,onntouch事件默认是返回flase的。
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Log.v("xxxx","click");
}
});
button.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.v("xxxx","touch"+event.getAction());
return flase;
}
});
然后我们点击这个button,查看log如下
我们会发现touch事件和click事件都执行了,并且touch事件的执行优于onclick事件,如果我们把onTouch事件返回true,那么我们继续查看log
此时onclick事件却没有执行,此时我们就会想到这是为什么了?其实我们可以这么理解,比如一份食物(代表该触摸事件),会经过1个人传递才回传到我的手上来,那么如果第一个人吃了食物,并且告诉我这个食物被吃了(return true),那么我就没办法接受这个食物(因为该事件不存在了),如果这个人没吃食物,就会把食物传递给我(return false),那么此时我就可以接受这个食物,并且吃掉它(onclick方法执行)
那么我们从源码的角度来看这个完整的事件过程是如何进行的,我们要记住任何view的触摸事件都会首先调用其dispatchTouchEvent方法,那我们就从这个方法开始
我们查看button没有dispatchTouchEvent方法,我们就去找其父类和其父类的父类也就是view
public boolean dispatchTouchEvent(MotionEvent event) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
if (onFilterTouchEventForSecurity(event)) {
//noinspection SimplifiableIfStatement
if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
mOnTouchListener.onTouch(this, event)) {
return true;
}
if (onTouchEvent(event)) {
return true;
}
}
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
return false;
}
重点就在if(noFilterTouchEventForSecurity(Event))里面,我们会发现第一个循环语句有3个判断,我们先来看第一个判断,也就是onTouchListener!=null 这个是在哪里申明的啊?我们继续查看源代码发现
public void setOnTouchListener(OnTouchListener l) {
mOnTouchListener = l;
}
也就是我们在代码里面setOnTouchListener的时候申明的,第二个条件因为按钮都是默认都是enable的所有是永远都是true,第三个条件就是调用onTouchListener.onTouch方法这个方法是否为true取决于我们代码中onTouch事件的返回值,如果我们return true,那么这个if是成立的就会跳出整个循环,如果return false,那么就会执行下一个if语句
在这个onTouch方法返回的时候,就已经执行了,所以我们的onTouch方法是事件执行的一个关卡,为什么要称呼其为关卡,因为它的返回值决定了是否执行onclick方法,
我们接下来查看onTouchEvn
public boolean onTouchEvent(MotionEvent event) {
final int viewFlags = mViewFlags;
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PRESSED) != 0) {
mPrivateFlags &= ~PRESSED;
refreshDrawableState();
}
// 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 (prepressed) {
// The button is being released before we actually
// showed it as pressed. Make it show the pressed
// state now (before scheduling the click) to ensure
// the user sees it.
mPrivateFlags |= PRESSED;
refreshDrawableState();
}
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) {
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
// If the post failed, unpress right now
mUnsetPressedState.run();
}
removeTapCallback();
}
break;
case MotionEvent.ACTION_DOWN:
mHasPerformedLongPress = false;
if (performButtonActionOnTouchDown(event)) {
break;
}
// Walk up the hierarchy to determine if we're inside a scrolling container.
boolean isInScrollingContainer = isInScrollingContainer();
// For views inside a scrolling container, delay the pressed feedback for
// a short period in case this is a scroll.
if (isInScrollingContainer) {
mPrivateFlags |= PREPRESSED;
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
// Not inside a scrolling container, so show the feedback right away
mPrivateFlags |= PRESSED;
refreshDrawableState();
checkForLongClick(0);
}
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
if (!pointInView(x, y, mTouchSlop)) {
// 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;
}
我们只需要关注这个if就行了
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
进而会执行
public boolean performClick() {
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
if (mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
mOnClickListener.onClick(this);
return true;
}
return false;
}
这个方法,在这个方法里面我们会发现onclick方法就是在这里执行的,那么就一切明了了,onTouch方法的返回值决定了是否执行onClick方法。
View的触摸都是从dispatchTouchEvent方法开始执行的,
但是这里还有另一个问题,就是如果OnTouch方法中,我们在Down方法的时候return false 后那么接下里的action就不会被接收执行,但是我们刚刚return false的时候
还是执行了,这是为什么?因为在onTouchEvent里面帮助我们return true了,所以都是可以执行的。View的机制我也明白具体的一点点,各位看官,我写的很差,大多数都是从一位大神那里copy过来的,详情请点击下面的链接
http://blog.youkuaiyun.com/guolin_blog/article/details/9097463,再次感谢这位大神!