今天学习了一下View的事件分发,受益匪浅。一直在想一个View的事件传递是怎么样的呢?今天就进行一探究竟:
1.首先写两个控件 : ImageView 和 Button. 我们都知道ImageView是不可以点击(click)的,而Button则默认可以点击.对ImageView 和 Button 分别设置setOnTouchListener().可以发现ImageView只执行一次OnTouch(),Button则执行了两次,而当把OnTouch()方法的返回值设置成return true;时两者都执行了两次。
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Log.d("TAG", "onClick execute");
}
});
原因是:android程序中当你去触摸控件时自动回执行ACTION_DOWN事件 当返回false时,只会响应down事件,可能不会响应up事件;当返回true时,说明可以返回down事件和up事件。是因为当返回true时,当前的事件可以被消费掉,从而传到下一个动作,如果当前的事件都不能被消费,传到后面也是白费的。
2.但是有人就会想为什么点击Button可以执行两次呢?这就要看源码了。
public boolean dispatchTouchEvent(MotionEvent event) {
if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
mOnTouchListener.onTouch(this, event)) {
return true;
}
return onTouchEvent(event);
}
首先我们要知道触摸事件一定是先经过 disPatchTouchEvent() 这个方法去对这个事件进行分发,源码中可以看到如果满足单个条件就直接return true释放,如果其中一个条件不满足就return onTouchEvent(event); 这三个条件分别是1. mOnTouchListener != null 2. (mViewFlags & ENABLED_MASK) == ENABLED 3. mOnTouchListener.onTouch(this, event) 。 其中第一个条件中 mOnTouchListener看源码可以知道它就是我们前面setOnTouchListener()中new 出来的对象 所以肯定不为空,第二个条件是指当前的控件是不是enable当然是可以的啊默认就是所有控件enable,但是这句话在这里肯定有他的作用,那就是我们可以设置控件的enable状态,第三个条件则是一个回调啊也就是onTouch()方法里的返回值。这下子就明白了 onTouch()中返回false了才能进入onTouchEvent(event), 所以马上明白ImageView 执行一次 Button执行两次肯定跟这个法有关系。(同时可以证明先执行onTouch(), 后执行onTouchEvent())
3.我们再往onTouchEvent()方法里面。
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;
}
看到一大堆的代码,不要紧重点部分,第一第二个的判断可以忽略,重点是第三个判断if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {} 意思是当前控件是不是可以点击的或者可以长按的,往下看如果可以点击下面就有个ACTION_UP,fantastyBaBy!原来是这么回事啊,终于明白了为什么ImageView只是执行一次而Button执行了两次的原因吧 还有就是执行完执行默认返回true。
4.还没完呢 后来我又想到我碰到过ImageView可以设置setOnClickListener();结果一看里面的源码又发现 if (!isClickable()) { setClickable(true); } 。。。。这下彻底明白了吧 设置了setOnClickListener()之后如果不可点击就默认让它可以点击。
5.问题解决了我们还要注意一些地方:
1.就是OnTouch() 和 OnTouchEvent()方法肯定是Ontouch先执行的,前面讲了回调执行了OnTouch()返回值是false时不满足三个条件之一 就执行onTouchEvent()方法;
2.onTouch方法先于onClick()方法因为onTouch()比onTouchEvent先执行 而oclick()后执行于onTouchEvent();
6.好了 View的事件分发就讲到这里了,下次再说ViewGroup的事件分发。
补充:
1)public boolean dispatchTouchEvent(MotionEvent ev)—–这个方法用来分发TouchEvent
2)public boolean onInterceptTouchEvent(MotionEvent ev)—–这个方法用来拦截TouchEvent
3)public boolean onTouchEvent(MotionEvent ev)—–这个方法用来处理TouchEvent