AnroidView事件响应机制和ViewGroup的事件响应分发机制
注:低版本的源码内容比高版本的源码简单,分析起来方便,但是高版本源码更为严密。
站在用户的角度思考问题,与客户深入沟通,找到襄垣网站设计与襄垣网站推广的解决方案,凭借多年的经验,让设计与互联网技术结合,创造个性化、用户体验好的作品,建站类型包括:做网站、成都做网站、企业官网、英文网站、手机端网站、网站推广、域名注册、雅安服务器托管、企业邮箱。业务覆盖襄垣地区。
View的事件响应机制
涉及2个方法dispatchTouchEvent和onTouchEvent
1.View的dispatchTouchEvent方法(事件传递到View,View的这个方法就自动执行。)
dispatchTouchEvent返回true,响应事件;返回false,不响应事件。
public boolean dispatchTouchEvent(MotionEvent event) { ... 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 onTouchEvent(event)
}
...
return false; //(如果View没有setOnTouchListener,默认dispatchTouchEvent 事件就是返回false) } |
----------------------------------------------------------------------------------------------------------------------
上述源码中的ListenerInfo是View类中的一个静态成员类,里面封装了各种事件类型的监听
者XxxListener的变量,包括private OnTouchListener mOnTouchListener;
在View这个类中有ListenerInfo mListenerInfo;这个类型的成员变量
而mListenerInfo是通过下面这个方法来返回的,从方法可以看出返回值肯定不为空。
ListenerInfo getListenerInfo() {
if (mListenerInfo != null) {
return mListenerInfo;
}
mListenerInfo = new ListenerInfo();
return mListenerInfo;
}
结论1:所以View的dispatchTouchEvent方法中的if判断中li(也就是mListenerInfo)!=null
----------------------------------------------------------------------------------------------------------------------
再看View的setOnTouchListener这个方法,只要这个方法被调用了,参数不为空,那么
mListenerInfo.mOnTouchListener就不为空。
public void setOnTouchListener(OnTouchListener l) {
getListenerInfo().mOnTouchListener = l;
}
结论2:所以View的dispatchTouchEvent方法中的if判断中li.mOnTouchListener != null
所以
if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED&& li.mOnTouchListener.onTouch(this, event)) |
这个条件组只是和li.mOnTouchListener.onTouch(this, event)这个回调方法有关
如果返回true的话,ImageView的dispatchTouchEvent方法也返回true。
如果返回false的话,会继续进行下面的一个if判断,也就是onTouchEvent方法的判断。
if (onTouchEvent(event)) { return true; } 上面这3行代码等价于return onTouchEvent(event),其实2.3.3的源码就是这么写的。 |
###############################################################################
所以对于dispatchTouchEvent()方法,如果直接继承自View的控件
1.没有调用setOnTouchListener()设置监听者,那么
li.mOnTouchListener == null,dispatchTouchEvent()方法默认就会返回false,这时
onTouchEvent 方法和dispatchTouchEvent()方法就没有任何的关联了。
2.调用setOnTouchListener()设置监听者,那么li.mOnTouchListener != null,
dispatchTouchEvent()方法就会判断li.mOnTouchListener.onTouch(this, event), 即监听者
的onTouch回调方法(表明用户的意图)的返回值
***onTouch 回调方法返回true,dispatchTouchEvent()方法就会返回true。
***onTouch 回调方法返回false,dispatchTouchEvent()方法就会调用onTouchEvent处理
事件,即把事件转交给onTouchEvent方法进行处理
###############################################################################
2.View的onTouchEvent方法
public boolean onTouchEvent(MotionEvent event) {
...
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
...
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
...
if (!post(mPerformClick)) {
performClick(); /*
底层实现:li.mOnClickListener.onClick(this);
View的点击事件真正是在UP事件之后执行的。
*/
}
...
return true;
}
return false;
}
---------------------------------------------------------------------
View的onTouchEvent的返回值,取决于View是否可点击。
这就解释了为什么当ImageView和Button的监听事件的OnTouch事件都是返回false时,ImageView只能响应按下事件,而Button能响应所有事件的原因了:因为ImageView默认的clickable属性为false,而Button的clickable属性为true。
想让ImageView监听事件的onTouch返回false也能响应所有的事件,有2种方式
第1种:直接将ImageView的clickable属性设置为true。
第2种:为ImageView添加点击事件,通过查看下面的源码可以看出,点击事件会将View
的clickable属性设置为true。
public void setOnClickListener(OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
getListenerInfo().mOnClickListener = l;}
假如Button添加了点击事件,如果去屏蔽它的点击事件呢?
n 在添加点击事件的代码后面设置onclickable属性为false。
n 在onTouchListener监听的onTouch方法里返回true。
原理:因为View的点击事件的本质是由onTouchEvent方法中的,performClick
这个方法所执行的,不让View执行onTouchEvent或clickable为false
即不会执行点击事件。
由此也可以看出onTouch事件和onClick事件不是一回事
ViewGroup的事件响应和传递机制(以2.3.3的源码来分析)
涉及3个方法dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent
ViewGroup的事件传递,是伴随着递归算法查找坐标落在那一个控件的范围,由父控件
向子控件,由外到内。
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
...
final int action = ev.getAction();
final float xf = ev.getX();
final float yf = ev.getY();
final float scrolledXFloat = xf + mScrollX;
final float scrolledYFloat = yf + mScrollY;
final Rect frame = mTempRect; //矩形类 mTempRect = new Rect();
boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (action == MotionEvent.ACTION_DOWN) { ... // If we're disallowing intercept or if we're allowing and we didn't intercept //如果onInterceptTouchEvent(ev)没有拦截事件 //只有自己先不拦截,才有必要去判断有没有子View去响应这个事件。 if (disallowIntercept || !onInterceptTouchEvent(ev)) { // reset this event's action (just to protect ourselves) ev.setAction(MotionEvent.ACTION_DOWN); // We know we want to dispatch the event down, find a child // who can handle it, start with the front-most child. 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 可见或有动画时 child.getHitRect(frame); //测量子控件的矩形参数 //判断事件的坐标有没有包含在子控件的矩形范围之内 if (frame.contains(scrolledXInt, scrolledYInt)) { //计算事件在子View中的坐标 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)) { // Event handled, we have a target now.!!!! //子View如果处理了事件,就把这个View作为事件的目 标 mMotionTarget = child; return true; } // The event didn't get handled, try the next view. // Don't reset the event's location, it's not // necessary here. } } //子View能够成为mMotionTarget的前提是事件坐标落在它的 矩形范围之内,并且它响应处理了这个事件。 } } } Action_down的if结束
|
...
// The event wasn't an ACTION_DOWN, dispatch it to our target if we have one.(**没有子View响应**) final View target = mMotionTarget; if (target == null) { // We don't have a target, this means we're handling the // event as a regular view.(**如果没有子View响应这个事件,这个 事件就会当作一般的View的事件来处理,即ViewGroup执行像View 一样的去执行事件分发**) ev.setLocation(xf, yf); if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) { ev.setAction(MotionEvent.ACTION_CANCEL); mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; } return super.dispatchTouchEvent(ev); }
|
// if have a target, see if we're allowed to and want to intercept its events(**有子View响应**) if (!disallowIntercept && onInterceptTouchEvent(ev)) { final float xc = scrolledXFloat - (float) target.mLeft; final float yc = scrolledYFloat - (float) target.mTop; mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; ev.setAction(MotionEvent.ACTION_CANCEL); ev.setLocation(xc, yc); if (!target.dispatchTouchEvent(ev)) { // target didn't handle ACTION_CANCEL. not much we can do // but they should have. } // clear the target mMotionTarget = null; // Don't dispatch this event to our own view, because we already // saw it when intercepting; we just want to give the following // event to the normal onTouchEvent(). return true; }
|
// finally offset the event to the target's coordinate system and // dispatch the event.(**把事件转交给子View去处理**) final float xc = scrolledXFloat - (float) target.mLeft; final float yc = scrolledYFloat - (float) target.mTop; ev.setLocation(xc, yc);
if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) { ev.setAction(MotionEvent.ACTION_CANCEL); target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; mMotionTarget = null; }
return target.dispatchTouchEvent(ev);
|
}
网站栏目:AnroidView事件响应机制和ViewGroup的事件响应分发机制
URL分享:http://scyanting.com/article/gjjccs.html