您好,登錄后才能下訂單哦!
注:低版本的源碼內容比高版本的源碼簡單,分析起來方便,但是高版本源碼更為嚴密。
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);
|
}
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。