View的dispatchTouchEvent方法的省略版源碼
public boolean dispatchTouchEvent(MotionEvent event) {
//dispatchTouchEvent的返回值
boolean result = false;
//...balabala省略前面部分代碼
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
//判斷是否有onTouchListener監聽,如果有,執行listener的
//onTouch方法,
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
//如果onTouch方法返回false,繼續執行onTouchEvent方法
if (!result && onTouchEvent(event)) {
result = true;
}
}
return result;
}
通過上面的代碼可以得出以下結論:
1 如果View有onTouchListener,則onTouch方法優於onTouchEvent方法先執行
2 如果 View的onTouchListener的onTouch方法返回了true,則onTouchEvent方法不會執行
3 如果View有onTouchListener,並且onTouch方法返回了true,dispatchTouchEvent方法返回true。如果onTouch返回了false或者沒有onToucheListener,則dispatchTouchEvent方法和onTouchEvent的返回值一致。
ViewGroup dispatchTouchEvent方法的省略版源碼
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
//dispatchTouchEvent方法的返回值
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
// Check for interception.是否攔截標誌
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
//調用攔截方法,該方法默認返回false
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was chang
} else {
intercepted = false;
}
} else {
intercepted = true;
}
//如果沒有攔截
if (!intercepted) {
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
}
}
}
}
}
return handled;
}
dispatchTransformedTouchEvent方法
/**
* Transforms a motion event into the coordinate space of a particular child view,
* filters out irrelevant pointer ids, and overrides its action if necessary.
* If child is null, assumes the MotionEvent will be sent to this ViewGroup instead.
*/
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
// Canceling motions is a special case. We don't need to perform any transformations
// or filtering. The important part is the action, not the contents.
final int oldAction = event.getAction();
// Calculate the number of pointers to deliver.
final int oldPointerIdBits = event.getPointerIdBits();
final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
// Perform any necessary transformations and dispatch.
if (child == null) {
//調用View的dispatchTouchEvent方法,View的dispatchTouchEvent方法如果執行則看前面分析View的dispatchTouchEvent方法源碼
handled = super.dispatchTouchEvent(transformedEvent);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
transformedEvent.offsetLocation(offsetX, offsetY);
if (! child.hasIdentityMatrix()) {
transformedEvent.transform(child.getInverseMatrix());
}
//調用子view的dispatchTouchEvent方法
handled = child.dispatchTouchEvent(transformedEvent);
}
// Done.
transformedEvent.recycle();
return handled;
}
ViewGroup的dispatchTouchEvent方法比較複雜,主要是它是個控件容器,可能有很多子控件,要考慮事件如何傳遞,傳遞給哪個子控件,該不該傳遞給子控件等等多種情況。
結論:
1 ViewGroup裏面新增了一個攔截方法onInterceptTouchEvent,控制該事件要不要傳遞給它的子控件。
2 若onInterceptTouchEvent方法返回true,則子控件接收不到事件即dispatchTouchEvent方法不會調用,而會調用父類View 的dispatchTouchEvent方法。若返回false,則會子控件的dispatchTouchEvent方法
現測試檢驗查看
MyView繼承View,裏面添加onTouchListener監聽.MyViewGroup 繼承ViewGroup.在相應的方法裏面添加打印語句,返回值全都默認值,經上面分析可知道的,猜想得知執行順序MyViewGroup.dispatchTouchEvent->MyViewGroup.onInterceptTouchEvent->MyView.dispatchTouchEvent->MyView.onTouch->MyView.onTouchEvent
打印結果如下圖,符合猜想;
1 小結:
View和ViewGroup的dispatchTouchEvent默認返回都爲false;
ViewGroup默認onInterceptTouchEvent返回也爲false;
action爲0表示爲當前事件爲ACTION_DOWN事件;
but,MOVE和UP事件跑哪去了....慢慢往下看;
其他都是默認值 現在改變View的監聽onTouch返回值爲true;
這時候View的onTouchEvent應該就得不到執行了;
打印結果如下圖,發現打印日誌就多了很多,action爲2表示ACTION_MOVE事件,等於1表示ACTION_UP事件;
2小結:
經上面分享源碼得知,此時MyView的dispatchTouchEvent方法返回的也是true,由日誌也可看出;
由打印日誌可以看出,MyViewGroup的dispatchTouchEvent方法也返回了true;
可以看出MyViewGroup的dispatchTouchEvent方法返回了true,纔有了後續的ACTION_MOVE和ACTION_UP事件;
注意看,此時MyViewGroup的onTouchEvent方法也不會執行了....Why...繼續往下慢慢分析
其他都是默認值 改變View的dispatchTouchEvent方法返回true;
這個和上面那個日誌的區別就是會執行View的onTouchEvent方法,其他的都是一樣的;
打印如下;
其他都是默認值,View的onTouchEvent返回true;
日誌和上面的差不多,差別就是前面View的onTouchEvent返回false,這裏都是true;
沒有設置dispatchTouchEvent的返回值,但是它返回的是true;
前面分析過了,這種情況dispatchTouchEvent的返回值和onTouchEvent是一樣的;
依然ViewGroup的onTouchEvent方法沒有執行;
3小結
通過小結1 和後面的對比發現,如果View的dispatchTouchEvent返回值爲true,則ViewGroup的onTouchEvent就不會執行了;
而想要View的dispatchTouchEvent返回值爲true,除了複寫該方法在dispatchTouchEvent裏面返回true之外,在則可以設置一個監聽onTouchListener返回true,或者在onTouchEvent裏面返回true;
平常我們用的最多的是複寫onTouchEvent方法;
再看View onTouchEvent方法源碼;
public boolean onTouchEvent(MotionEvent event) {
//前面省略balabala代碼....
final int action = event.getAction();
//還可以設置touch代理
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
(viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
switch (action) {
case MotionEvent.ACTION_UP:
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) !=
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
// This is a tap, so remove the longpress check
removeLongPressCallback();
if (!focusTaken) {
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
//調用onClickListener回調
if (!post(mPerformClick)) {
performClick();
}
}
}
break;
case MotionEvent.ACTION_DOWN:
mHasPerformedLongPress = false;
if (isInScrollingContainer) {
//檢測是否長按
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
// Not inside a scrolling container, so show the feedback right away
setPressed(true, x, y);
//檢測是否長按
checkForLongClick(0, x, y);
}
break;
case MotionEvent.ACTION_MOVE:
drawableHotspotChanged(x, y);
// Be lenient about moving outside of buttons
if (!pointInView(x, y, mTouchSlop)) {
// Outside button
removeTapCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
// Remove any future long press/tap checks
removeLongPressCallback();
setPressed(false);
}
}
break;
}
//這裏直接返回true了
return true;
}
return false;
}
初看一下既然返回false了,那麼switch裏面應該就不會走了;
突發奇想,既然switch都不會走了,難道onClickListener也不會走了???
MyView 和MyViewGroup所有返回值都默認,添加如下代碼,設置了onClick監聽
結果Toast神奇的彈出來了!!!
和上面預期的不一樣啊,既然switch都不走了,那爲啥onClick方法會被調用?
打印如下;
View 的onTouchEvent,dispatchTouchEvent返回值爲true;
ViewGroup的dispatchTouchEvent返回爲true了。
就是一個加setOnclickListener和不加的區別;
點進去這個方法看;
前面有個setClickable頓時恍然大悟,如夢出醒;
再看onTouchEvent方法
也就是默認這個值是false,如果這個值是false,那onTouchEvent方法也就直接返回false.這也是導致了dispatchTouchEvent方法返回了false;
google工程師用一個標誌viewFlags判斷當前是否可點擊或者長按或者是contextClickable;
點擊和長按是在onTouchEvent裏面處理的,這個onContextClickListener在onTouchEvent裏面沒看到被調用,用的也比較少,暫時略過,以後發現了用處再來補充
4 小結
在不設置setClickable,或者setLongClickable情況下,onTouchEvent返回false;
若設置setClickable,或者setLongClickable爲true,則onTouchEvent也返回true;
可以通過代碼設置,也可以在佈局文件裏面直接設置Clickable和LongClickable;
繼續測試,所有值默認,ViewGroup的dispatchTouchEvent返回true
日誌打印如下圖
5 小結
由於View的onTouchEvent返回了false,所以ViewGroup的onTouchEvent纔會被調用;
由於View的onTouchEvent在ACTION_DOWN就返回了false,所以後續的ACTION_MOVE和ACTION_UP也就沒有接收到;
即時ViewGroup的onTouchEvent在ACTION_DOWN返回了true,他的後續MOVE和UP也能接收到;
現改成如下方式;
輸出日誌如下:
6 小結
ViewGroup的dispatchTouchEvent方法裏面只要ACTION_DOWN返回了true,其他的MOVE和UP事件不受影響
繼續測試,onInterceptTouchEven返回true,代碼如下
由前面分析ViewGroup源碼得知,此時View接收不到任何事件的;
輸入日誌如下
View裏面onTouchEvent返回true
ViewGroup onInterceptTouchEvent的MOVE事件裏面返回true
打印日誌如下
這個時候View onTouchEvent方法只接收到了DOWN 和CANCEL事件;
ViewGroup 接收到了MOVE和UP事件
小結:
ViewGroup若在MOVE方法返回true,子控件只能接收到DOWN和CANCEL事件。ViewGroup可以接收到MOVE和UP事件
現在在ViewGroup 的UP事件上攔截
輸出日誌如下
View接收到onTouchEvent 方法接收到DOWN和MOVE和CANCEL事件;
ViewGroup的dispatchTouEvent裏面可以接收DOWN,MOVE,和UP事件,onTouchEvent沒有執行;
總結
1 如果一個控件能夠接受到事件則最新執行的方法肯定是dispatchTouEvent方法;
2 如果一個控件設置了onTouchLister,則onTouchListener優於onTouchEvent方法執行;
3 有onTouchListener的情況下,如果onTouchListener返回true,則dispatchTouEvent的返回值也爲true,並且onTouchEvent方法不會再執行;
4 沒onTouchListener或者onTouchListener返回false的情況下,dispatchTouEvent的返回值和onTouchEvent是一樣的;
5 ACTION_DOWN,ACTION_MOVE,ACTION_UP事件是一系列連續的,如果某個事件返回了false,則後續事件也不再調用
6 父容器控件的onInterceptTouchEvent方法也是如此,如果某個事件返回了true,則子控件會接收到ACTION_CANCEL事件,並且子控件後續事件也不在會執行。
7 字控件的dispatchTouEvent方法如果能夠執行,並且返回true.則父容器控件的onTouchEvent方法就不會執行。
http://www.bubuko.com/infodetail-1466509.html