一逃沿、View繪制流程機(jī)制
1疏哗、View繪制起點(diǎn)
-
performTraversals()
方法觸發(fā)了View 的繪制疲迂。
Activity調(diào)用流程 說(shuō)明:
在Activity顯示時(shí),WindowManager
將View添加到DecorView
朱嘴,兩者通過(guò)ViewRoot
連接起來(lái)倾鲫。
具體實(shí)現(xiàn)類是ViewRootImpl
。
再通過(guò)ViewRootImpl
的一系列處理萍嬉,最終調(diào)用performTraversals
方法乌昔,在performTraversals
方法中,依次調(diào)用了performMeasure()
壤追,performLayout()
磕道,performDraw()
,將View 的measure
行冰,layout溺蕉,
draw` 過(guò)程從頂層View 分發(fā)了下去。開(kāi)始了View的繪制悼做。
2疯特、View繪制流程
- 繪制過(guò)程分為三步:
measure
(測(cè)量) -->layout
(布局) -->draw
(繪制),
在draw
流程結(jié)束以后就可以在屏幕上看到view了肛走。 -
流程圖如下:
繪制流程
.1漓雅、measure
(測(cè)量)
測(cè)量的目的,是為了計(jì)算出View的大小朽色,通過(guò)
MeasureSpec
來(lái)進(jìn)行計(jì)算的邻吞。
MeasureSpec
是一個(gè)specSize
和specMode
信息的32 位int 值,其中高兩位表示specMode
葫男,低30位表示specSize
抱冷。specMode
模式:
UNSPECIFIED
:父容器不對(duì)View有任何限制,要多大有多大腾誉。常用于系統(tǒng)內(nèi)部徘层。
EXACTLY
(精確模式):父視圖為子視圖指定一個(gè)確切的尺寸SpecSize峻呕。對(duì)應(yīng)LyaoutParams中的match_parent或具體數(shù)值。
AT_MOST
(最大[限制]模式):父容器為子視圖指定一個(gè)最大尺寸SpecSize趣效,View的大小不能大于這個(gè)值瘦癌。對(duì)應(yīng)LayoutParams中的wrap_content。-
決定View大小的因素:
因素:widthMeasureSpec
和heightMeasureSpec
MeasureSpec
值由 子View的布局參數(shù)LayoutParams 和 父容器的MeasureSpec值 共同決定跷敬。具體規(guī)則見(jiàn)下圖:
MeasureSpec創(chuàng)建規(guī)則
頂級(jí)View(即DecorView)的測(cè)量在ViewRootImpl的源碼里(getRootMeasureSpec
方法):
//desire的這2個(gè)參數(shù)就代表屏幕的寬高讯私,
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
//decorView的measureSpec就是在這里確定的,其實(shí)比普通view的measurespec要簡(jiǎn)單的多
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
// Window can't resize. Force root view to be windowSize.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
// Window can resize. Set max size for root view.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
// Window wants to be an exact size. Force root view to be that size.
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
- 流程:
performMeasure() 會(huì)調(diào)用 measure()(final方法)西傀,將計(jì)算的MeasureSpec
傳遞給調(diào)用的onMeasure()
方法斤寇,此方法會(huì)調(diào)用setMeasuredDimension()
來(lái)設(shè)置自身大小拥褂;
如果是ViewGroup娘锁,先遍歷子View,測(cè)量出子View的MeasureSpec
饺鹃,再調(diào)用measureChild*
相關(guān)方法讓子View通過(guò)調(diào)用measure方法來(lái)測(cè)量自己的大心选;再根據(jù)所有子View的大小確定自身的大小悔详,其中的子View會(huì)重復(fù)此類的measure過(guò)程镊屎,如此反復(fù)至完成整個(gè)View樹(shù)的遍歷,確定各個(gè)View自身的大小茄螃。
注意:
- ViewGroup中沒(méi)有
onMeasure
方法- View會(huì)進(jìn)行多次的測(cè)量缝驳,第一次測(cè)量和最終測(cè)量的實(shí)際寬高不一定相等,在layout流程中可確定View的實(shí)際寬高
- 獲取measure()后的寬高方法:
- Activity#onWindowFocusChange()中獲取
- view.post(Runnable)將獲取的代碼投遞到消息隊(duì)列的尾部
- ViewTreeObservable方法
.2归苍、layout
(布局)
布局目的:確定View的 最終寬高 和 四個(gè)頂點(diǎn)的位置
-
流程:
performLayout()
會(huì)調(diào)用頂級(jí)View的layout()
方法用狱,其中調(diào)用setFrame()
方法來(lái)設(shè)置其四個(gè)頂點(diǎn)(mLeft、mRight拼弃、mTop齿拂、mBottom);
接著調(diào)用onLayout()
(空方法)肴敛,此方法由具體實(shí)現(xiàn)的View自身重寫,用來(lái)確定自身位置吗购,及循環(huán)其子View來(lái)確定坐標(biāo)位置医男,子View?會(huì)循環(huán)調(diào)用setChildFrame()
(就是調(diào)用View.layout()
)。
layout流程 layout和onLayout方法有什么區(qū)別捻勉?
layout是確定本身view的位置镀梭,通過(guò)serFrame方法設(shè)定本身view的四個(gè)頂點(diǎn)的位置。
onLayout是確定所有子元素的位置踱启。
View和ViewGroup的 onLayout 方法都是空方法报账。都留給我們自己給子元素布局研底。
.3、draw
(繪制)
- 繪制目的:顯示View
- 流程:
performMeasure()
會(huì)調(diào)用 ViewRootImpl的draw方法透罢,再調(diào)用drawSoftWare()
方法榜晦,其中會(huì)調(diào)用mView.draw()
,是真正繪制步驟的開(kāi)始羽圃。繪制步驟如下:(1乾胶、3、4朽寞、6四步為主要步驟)
- 1识窿、Draw the background:繪制背景 ——
drawBackground(canvas)
- If necessary, save the canvas' layers to prepare for fading:保存圖層
- Draw view's content:繪制view自身的內(nèi)容 ——
onDraw(canvas)
- Draw children:繪制子View(分發(fā)) ——
dispatchDraw(canvas)
- If necessary, draw the fading edges and restore layers:繪制一些圖層
- Draw decorations (scrollbars for instance):繪制裝飾(如滾動(dòng)條) ——
onDrawForeground(canvas)
注:View中的dispatchDraw(canvas)
是空方法,ViewGroup中的dispatchDraw(canvas)
調(diào)用了其drawChild
方法
-
繪制流程圖:
draw流程 說(shuō)明:
setWillNotDraw
:用于設(shè)置繪制的標(biāo)志位脑融,view是否需要draw繪制喻频。
若自定義的View 不需要draw,則可以設(shè)置這個(gè)方法為true肘迎。系統(tǒng)會(huì)根據(jù)此標(biāo)記來(lái)優(yōu)化執(zhí)行速度甥温。
ViewGroup 一般都默認(rèn)設(shè)置這個(gè)為true,因?yàn)閂iewGroup多數(shù)都是只負(fù)責(zé)布局膜宋,不負(fù)責(zé)draw的窿侈。
View 的這個(gè)標(biāo)志位默認(rèn)一般都是關(guān)閉的。
public void setWillNotDraw(boolean willNotDraw) {
setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
}
- 一般執(zhí)行動(dòng)畫秋茫,會(huì)多次調(diào)用onDraw方法史简,通過(guò)監(jiān)聽(tīng)動(dòng)畫的參數(shù)值變化,不斷
invalidate
肛著,不斷重繪圆兵。
invalidate
是在 主線程 中進(jìn)行調(diào)用,會(huì)引發(fā)onDraw進(jìn)行重繪
postInvalidate
是在 子線程 中調(diào)用枢贿,最終調(diào)用的仍是invalidate
參考鏈接:
Android View繪制13問(wèn)13答
要點(diǎn)提煉|開(kāi)發(fā)藝術(shù)之View
二殉农、View及ViewGroup的事件機(jī)制
1、相關(guān)概念:
- MotionEvent事件
- 事件類型:
ACTION_DOWN
:手指剛接觸屏幕局荚,按下去的那一瞬間產(chǎn)生該事件
ACTION_MOVE
:手指在屏幕上移動(dòng)時(shí)候產(chǎn)生該事件
ACTION_UP
:手指從屏幕上松開(kāi)的瞬間產(chǎn)生該事件- 事件序列:
從ACTION_DOWN開(kāi)始到ACTION_UP結(jié)束我們稱為一個(gè)事件序列
- TouchSlop
系統(tǒng)所能識(shí)別的被認(rèn)為是滑動(dòng)的最小距離超凳。
即當(dāng)手指在屏幕上滑動(dòng)時(shí),如果兩次滑動(dòng)之間的距離小于這個(gè)常量耀态,那么系統(tǒng)就不認(rèn)為你是在進(jìn)行滑動(dòng)操作轮傍。
該常量和設(shè)備有關(guān),可用它來(lái)判斷用戶的滑動(dòng)是否達(dá)到閾值首装,獲取方法:
ViewConfiguration.get(getContext()).getScaledTouchSlop()
VelocityTracker
速度追蹤创夜,用于追蹤手指在滑動(dòng)過(guò)程中的速度,包括水平和豎直方向的速度仙逻。GestureDetector
手勢(shì)檢測(cè)驰吓,用于輔助檢測(cè)用戶的單擊涧尿、滑動(dòng)、長(zhǎng)按檬贰、雙擊等行為姑廉。
2、事件分發(fā)的要點(diǎn)
事件分發(fā)的本質(zhì):
是對(duì)MotionEvent事件分發(fā)的過(guò)程偎蘸,并將事件消費(fèi)處理庄蹋。事件分發(fā)的傳遞順序:
Activity(Window) --> ViewGroup --> View
即最終調(diào)用的是 View的dispatchTouchEvent(MotionEvent event)
方法事件分發(fā)的重要方法:
1、dispatchTouchEvent(MotionEvent event)
:分發(fā)事件迷雪,返回boolean類型限书,表示事件是否消費(fèi)
2、onInterceptTouchEvent(MotionEvent event)
:中斷事件(僅ViewGroup有)章咧,返回boolean類型倦西,表示事件是否中斷
3、onTouchEvent(MotionEvent event)
:消費(fèi)事件(僅View有)赁严,返回boolean類型扰柠,表示事件是否消費(fèi)
4、perform*Click()
:執(zhí)行事件(僅View有)疼约,最終是onClick(View view)卤档,或長(zhǎng)按、雙擊等事件處理程剥。事件分發(fā)偽代碼:
// ViewGroup
public boolean dispatchTouchEvent(MotionEvent ev) {
// 事件是否被消費(fèi)
boolean consume = false;
// 調(diào)用onInterceptTouchEvent判斷是否攔截事件
if (onInterceptTouchEvent(ev)) {
// 如果攔截則調(diào)用自身的onTouchEvent方法
consume = onTouchEvent(ev);
} else {
if (targetChild == null) {
// 沒(méi)有找到目標(biāo)child劝枣,則調(diào)用父容器的分發(fā)方法
consume = super.dispatchTouchEvent(ev);
} else {
// 不攔截調(diào)用子View的dispatchTouchEvent方法
consume = child.dispatchTouchEvent(ev);
}
}
// 返回值表示事件是否被消費(fèi),true事件終止织鲸,false調(diào)用父View的onTouchEvent方法
return consume;
}
// View
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean consume = false;
// 是否實(shí)現(xiàn)了TouchListener#onTouch方法
if (onTouchListener != null) {
// 調(diào)用實(shí)現(xiàn)的onTouchListener#onTouch方法
consume = onTouchListener.onTouch(ev);
} else {
// onTouchEvent()中調(diào)用了perform*Click()等方法
consume = onTouchEvent(ev);
}
//返回值表示事件是否被消費(fèi)舔腾,true事件終止,false調(diào)用父View的onTouchEvent方法
return consume;
}
3搂擦、事件分發(fā)的機(jī)制&流程
-
示意圖
View及ViewGroup事件流程示意圖
流程詳述:
- 1稳诚、ViewGroup分發(fā)開(kāi)端:
Acivity#dispatchTouchEvent(MotionEvent)
事件最開(kāi)始從Activity開(kāi)始,由Acivity的dispatchTouchEvent方法來(lái)對(duì)事件進(jìn)行分發(fā)瀑踢。
// Activity源碼:
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
// 事件分發(fā)并返回結(jié)果
if (getWindow().superDispatchTouchEvent(ev)) {
//事件被消費(fèi)
return true;
}
// 無(wú)View 消費(fèi)事件扳还,則調(diào)用Activity#onTouchEvent方法
return onTouchEvent(ev);
}
PhoneWindow#superDispatchTouchEvent(MotionEvent)
getWindow().superDispatchTouchEvent(ev)
是Window
的抽象方法,具體由PhoneWindow
實(shí)現(xiàn)
其內(nèi)部是調(diào)用的頂級(jí)View(DecorView)的superDispatchTouchEvent
方法
// PhoneWindow源碼:
// This is the top-level view of the window, containing the window decor.
private DecorView mDecor;
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
DecorView.superDispatchTouchEvent(MotionEvent)
頂級(jí)View(DecorView)一般為ViewGroup橱夭,其方法中調(diào)用了ViewGroup#dispatchTouchEvent
方法
public boolean superDispatchTouchEvent(MotionEvent event) {
// 此處調(diào)用的是ViewGroup的dispatchTouchEvent方法
return super.dispatchTouchEvent(event);
}
- 2普办、ViewGroup事件分發(fā) —
onInterceptTouchEvent
:
MotionEvent.ACTION_DOWN
事件:
先判斷是否為MotionEvent.ACTION_DOWN
事件,是徘钥,則清除FLAG_DISALLOW_INTERCEPT
設(shè)置并且mFirstTouchTarget 設(shè)置為null,然后根據(jù)條件調(diào)用onInterceptTouchEvent
方法肢娘,來(lái)處理攔截事件呈础。
如果不是MotionEvent.ACTION_DOWN
事件舆驶,且mFirstTouchTarget == null
,則直接設(shè)置中斷標(biāo)記為true(intercepted = true)而钞,ViewGroup直接攔截其他事件(如MOVE和UP等)進(jìn)行處理沙廉。
FLAG_DISALLOW_INTERCEPT
標(biāo)志位:
如果通過(guò)requestDisallowInterceptTouchEvent
方法設(shè)置了此標(biāo)志位,則子View可以以此來(lái)干預(yù)父View的事件分發(fā)過(guò)程(ACTION_DOWN事件除外臼节,上面的原因)撬陵,而這就是我們處理滑動(dòng)沖突常用的關(guān)鍵方法。
// ViewGroup源碼:
public boolean dispatchTouchEvent(MotionEvent ev) {
...
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// Handle an initial down.
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Throw away all previous state when starting a new touch gesture.
// The framework may have dropped the up or cancel event for the previous gesture
// due to an app switch, ANR, or some other state change.
cancelAndClearTouchTargets(ev);
//清除FLAG_DISALLOW_INTERCEPT設(shè)置并且mFirstTouchTarget 設(shè)置為null
resetTouchState();
}
// Check for interception.
final boolean intercepted;//是否攔截事件
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
//FLAG_DISALLOW_INTERCEPT是子View通過(guò)
//requestDisallowInterceptTouchEvent方法進(jìn)行設(shè)置的
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
//調(diào)用onInterceptTouchEvent方法判斷是否需要攔截
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}
...
}
-
3网缝、ViewGroup事件分發(fā) — 子View遍歷
mFirstTouchTarget
:
當(dāng)ViewGroup不攔截事件巨税,則遍歷子View,找到事件接收的目標(biāo)View(mFirstTouchTarget
)粉臊,條件:- View可見(jiàn)且沒(méi)有播放動(dòng)畫:
canViewReceivePointerEvents
方法 - 事件的坐標(biāo)落在View的范圍內(nèi):
isTransformedTouchPointInView
當(dāng)mFirstTouchTarget不為null草添,則說(shuō)明已經(jīng)找到過(guò)了目標(biāo)child,則newTouchTarget不為null扼仲,會(huì)跳出循環(huán)远寸。
但此時(shí)還沒(méi)有將事件分發(fā)給子View,所以newTouchTarget為null屠凶,mFirstTouchTarget也是null驰后。
如果dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)方法
返回了true,即子View消費(fèi)了事件矗愧,則會(huì)將mFirstTouchTarget進(jìn)行賦值為該子View灶芝,終止子View的遍歷。此時(shí)贱枣,子View的遍歷完成监署。
在dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)方法
中,如果子View不為null纽哥,則調(diào)用了子View的child.dispatchTouchEvent
分發(fā)方法钠乏,進(jìn)行View的分發(fā)。 - View可見(jiàn)且沒(méi)有播放動(dòng)畫:
// ViewGroup源碼:
public boolean dispatchTouchEvent(MotionEvent ev) {
final View[] children = mChildren;
//對(duì)子View進(jìn)行遍歷
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
// If there is a view that has accessibility focus we want it
// to get the event first and if not handled we will perform a
// normal dispatch. We may do a double iteration but this is
// safer given the timeframe.
if (childWithAccessibilityFocus != null) {
if (childWithAccessibilityFocus != child) {
continue;
}
childWithAccessibilityFocus = null;
i = childrenCount - 1;
}
//判斷1春塌,View可見(jiàn)并且沒(méi)有播放動(dòng)畫晓避。2,點(diǎn)擊事件的坐標(biāo)落在View的范圍內(nèi)
//如果上述兩個(gè)條件有一項(xiàng)不滿足則continue繼續(xù)循環(huán)下一個(gè)View
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
newTouchTarget = getTouchTarget(child);
//如果有子View處理即newTouchTarget 不為null則跳出循環(huán)只壳。
if (newTouchTarget != null) {
// Child is already receiving touch within its bounds.
// Give it the new pointer in addition to the ones it is handling.
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
resetCancelNextUpFlag(child);
//dispatchTransformedTouchEvent第三個(gè)參數(shù)child這里不為null
//實(shí)際調(diào)用的是child的dispatchTouchEvent方法
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
mLastTouchDownTime = ev.getDownTime();
if (preorderedList != null) {
// childIndex points into presorted list, find original index
for (int j = 0; j < childrenCount; j++) {
if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j;
break;
}
}
} else {
mLastTouchDownIndex = childIndex;
}
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
//當(dāng)child處理了點(diǎn)擊事件俏拱,那么會(huì)設(shè)置mFirstTouchTarget 在addTouchTarget被賦值
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
//子View處理了事件,然后就跳出了for循環(huán)
break;
}
}
}
dispatchTransformedTouchEvent
方法:
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
mFirstTouchTarget
的賦值:
/**
* Adds a touch target for specified child to the beginning of the list.
* Assumes the target child is not already present.
*/
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
mFirstTouchTarget = target;
return target;
}
- 4吼句、ViewGroup事件分發(fā) —
View#dispatchTouchEvent
:
若遍歷子View后锅必,ViewGroup沒(méi)有找到事件處理者(① ViewGroup沒(méi)有子View 或 ② 子View處理了事件卻在dispatchTouchEvent方法返回了false),則ViewGroup會(huì)去處理這個(gè)事件。
即dispatchTouchEvent
方法返回了false搞隐,mFirstTouchTarget 必然為null驹愚,則再次調(diào)用自身的dispatchTransformedTouchEvent
方法(但傳入的child為null),其內(nèi)部會(huì)調(diào)用super.dispatchTouchEvent(event);
方法劣纲,將調(diào)用View#dispatchTouchEvent
逢捺,將事件傳給View,至此癞季,ViewGroup的分發(fā)過(guò)程完成劫瞳。
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
}
- 5、View的事件分發(fā) —
View#dispatchTouchEvent
mOnTouchListener.onTouch
監(jiān)聽(tīng):
View中首先判斷是否設(shè)置了OnTouchListener
監(jiān)聽(tīng)(開(kāi)發(fā)者自己實(shí)現(xiàn)的)绷柒,若設(shè)置了且onTouch
返回true
志于,則之后的onTouchEvent
方法不會(huì)調(diào)用;若沒(méi)有設(shè)置監(jiān)聽(tīng)或onTouch
返回false
辉巡,則會(huì)調(diào)用onTouchEvent
方法恨憎。
onTouchEvent
方法:
在此方法中,具體的處理了各個(gè)事件郊楣。
如果View設(shè)置成了disabled
狀態(tài)(即不可用)憔恳,只要CLICKABLE
和LONG_CLICKABLE
有一個(gè)為true
,就一定會(huì)消費(fèi)這個(gè)事件(即onTouchEvent
返回true)净蚤,只是它看起來(lái)不可用钥组。只有不可點(diǎn)擊(clickable
和longClickable
同時(shí)為false
),才會(huì)返回false今瀑,即onTouchEvent不消費(fèi)此事件程梦。
performClick()
方法:
就點(diǎn)擊事件而言,在ACTION_UP
事件的條件下橘荠,會(huì)調(diào)用performClickInternal
方法(內(nèi)部實(shí)際是performClick()
)屿附;在performClick()
方法中,如果設(shè)置了OnClickListener
哥童,則會(huì)回調(diào)onClick
方法挺份。
dispatchTouchEvent(MotionEvent)
// View源碼:
//如果窗口沒(méi)有被遮蓋
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//noinspection SimplifiableIfStatement
//當(dāng)前監(jiān)聽(tīng)事件
ListenerInfo li = mListenerInfo;
//需要特別注意這個(gè)判斷當(dāng)中的li.mOnTouchListener.onTouch(this, event)條件
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
//result為false調(diào)用自己的onTouchEvent方法處理
if (!result && onTouchEvent(event)) {
result = true;
}
}
onTouchEvent(MotionEvent)
// View源碼:
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction();
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
// 判斷是否不可用,但仍會(huì)消費(fèi)事件
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
// 此處重點(diǎn)
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return clickable;
}
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
...
if (!post(mPerformClick)) {
// 其內(nèi)部調(diào)用的是performClick方法
performClickInternal();
}
...
break;
...
}
return true;
}
return false;
}
performClick()
// View源碼:
/**
* Call this view's OnClickListener, if it is defined. Performs all normal
* actions associated with clicking: reporting accessibility event, playing
* a sound, etc.
*
* @return True there was an assigned OnClickListener that was called, false
* otherwise is returned.
*/
public boolean performClick() {
final boolean result;
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
return result;
}
說(shuō)明: setClickable
失效的原因:
View的setOnClickListener
會(huì)默認(rèn)將View的clickable設(shè)置成true贮懈。
View的setOnLongClickListener
同樣會(huì)將View的longClickable設(shè)置成true匀泊。
// View源碼:
public void setOnClickListener(@Nullable OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
getListenerInfo().mOnClickListener = l;
}
public void setOnLongClickListener(@Nullable OnLongClickListener l) {
if (!isLongClickable()) {
setLongClickable(true);
}
getListenerInfo().mOnLongClickListener = l;
}
4、事件分發(fā)的相關(guān)問(wèn)題:
-- 問(wèn)題:如果一個(gè)事件序列的 ACTION_DOWN 事件被 ViewGroup 攔截朵你,此時(shí)子 View 調(diào)用 requestDisallowInterceptTouchEvent
方法有沒(méi)有用各聘?
子View可以通過(guò) requestDisallowInterceptTouchEvent
方法干預(yù)父View的事件分發(fā)過(guò)程(ACTION_DOWN事件除外),而這就是我們處理滑動(dòng)沖突常用的關(guān)鍵方法抡医。
在
requestDisallowInterceptTouchEvent
中躲因,設(shè)置了FLAG_DISALLOW_INTERCEPT
標(biāo)志位,表示子View不希望此父級(jí)及其祖先使用ViewGroup.onInterceptTouchEvent(MotionEvent)
攔截觸摸事件。
而 ACTION_DOWN
事件是清除了這個(gè)標(biāo)志位的毛仪,所以搁嗓,requestDisallowInterceptTouchEvent
的設(shè)置對(duì)ACTION_DOWN無(wú)效。
// ViewGroup源碼箱靴,實(shí)現(xiàn)的是ViewParent的抽象方法
@Override
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
// We're already in this state, assume our ancestors are too
return;
}
if (disallowIntercept) {
mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
} else {
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
// Pass it up to our parent
if (mParent != null) {
mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
}
}
// ViewGroup#dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent ev) {
...
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
if (actionMasked == MotionEvent.ACTION_DOWN) {
....
//清除FLAG_DISALLOW_INTERCEPT設(shè)置并且mFirstTouchTarget 設(shè)置為null
resetTouchState();
}
//是否攔截事件
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
//FLAG_DISALLOW_INTERCEPT是子View通過(guò)
//requestDisallowInterceptTouchEvent方法進(jìn)行設(shè)置的
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
//調(diào)用onInterceptTouchEvent方法判斷是否需要攔截
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
intercepted = true;
}
...
}
-- 問(wèn)題:ACTION_DOWN
事件被子 View
消費(fèi)了,那 ViewGroup
能攔截剩下的事件嗎荷愕?如果攔截了剩下事件衡怀,當(dāng)前這個(gè)事件 ViewGroup 能消費(fèi)嗎?子 View 還會(huì)收到事件嗎安疗?
在ACTION_DOWN
事件被子 View
消費(fèi)后抛杨,mFirstTouchTarget
則不為null了,就會(huì)直接攔截其他事件荐类,intercepted = true;
怖现,見(jiàn)下面的源碼。
設(shè)置了攔截玉罐,就不會(huì)再遍歷子View進(jìn)行事件分發(fā)了屈嗤。則會(huì)取消cancelChild
,攔截子View的事件吊输。
// ViewGroup#dispatchTouchEvent
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
// onInterceptTouchEvent方法的判斷
......
} else {
// 重點(diǎn)在這里
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true; // <---------------------------------------重點(diǎn)
}
....
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
// 沒(méi)有子 View 消費(fèi)事件饶号,則傳入 null 去分發(fā),最終調(diào)用的是自身的 onTouchEvent 方法季蚂,進(jìn)行處理 touch 事件
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
// Dispatch to touch targets, excluding the new touch target if we already
// dispatched to it. Cancel touch targets if necessary.
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
// 如果 intercepted 就取消 cancelChild茫船,這便是攔截子 View 事件的原理
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted; // <---------------------------------------重點(diǎn)
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
//內(nèi)部會(huì)比較 pointerIdBits 和當(dāng)前事件的 pointerIdBits,一致才會(huì)處理
//這便是 Down 事件處理后后續(xù)事件都交給該 View 處理的原理
handled = true;
}
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
// 沒(méi)有next則為null扭屁,就結(jié)束了循環(huán)
target = next;
continue;
}
}
predecessor = target;
target = next;
}
}
-- 問(wèn)題:當(dāng) View Disable 時(shí)算谈,會(huì)消費(fèi)事件嗎?
會(huì)消費(fèi)事件料滥,只是設(shè)為了不可用然眼,可以看到,在源碼中的注釋為:
A disabled view that is clickable still consumes the touch events, it just doesn't respond to them.
[一個(gè)可點(diǎn)擊的禁用view幔欧,仍然可以消費(fèi)事件罪治,它只是沒(méi)有響應(yīng)它們(事件)而已。]
// View#onTouchEvent
// 判斷是否不可用礁蔗,但仍會(huì)消費(fèi)事件
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
// 此處重點(diǎn)
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return clickable;
}
參考鏈接:
一文讀懂Android View事件分發(fā)機(jī)制
必問(wèn)的事件分發(fā)觉义,你答得上來(lái)嗎
Android事件分發(fā)機(jī)制詳解:史上最全面、最易懂
要點(diǎn)提煉|開(kāi)發(fā)藝術(shù)之View