1.1. 一造:直接傳給目標Viewbash
咱們先實現一個最簡單的需求:Activity 中有一堆層層嵌套的 View,有且只有最裏邊那個 View 會消費事件 (黃色高亮 View 表明能夠消費事件,藍色 View 表明不消費事件)微信
思考方案:框架
示意圖ide
open class MView {
open fun passEvent(ev: MotionEvent) {
// do sth
}
}
class MViewGroup(private val child: MView) : MView() {
override fun passEvent(ev: MotionEvent) {
child.passEvent(ev)
}
}
複製代碼
1.2. 二造:從裏向外傳給目標View佈局
而後咱們增長一條需求,讓狀況複雜一點:Activity中有一堆層層嵌套的View,有好幾個疊着的View能處理事件post
同時須要增長一條設計原則:用戶的一次操做,只能被一個View真正處理(消費)ui
若是使用第一次試造的框架,要遵照這條原則,就須要在每個能夠處理事件的View層級,判斷出本身要處理事件後,不繼續調用child的passEvent()方法了,保證只有本身處理了事件。 但若是真這樣實現了,在大部分場景下會顯得怪怪的,由於處理事件的順序不對:this
因此實現新增需求的一個關鍵是:找到那個適合處理事件的View,而咱們經過對業務場景進行分析,獲得答案是:那個最裏面的View適合處理事件spa
這就不能是等parent不處理事件了才把事件傳給child,應該反過來,你須要事件的處理順序是從裏向外:裏邊的child不要事件了,才調用parent的passEvent()方法把事件傳出來。 因而得加一條向外的通道,只能在這條向外的通道上處理事件,前面向裏的通道什麼都不幹,只管把事件往裏傳。 因此這時你有了兩條通道,改個名字吧,向裏傳遞事件是passIn()方法,向外傳遞並處理事件是passOut()方法。設計
示意圖
open class MView {
var parent: MView? = null
open fun passIn(ev: MotionEvent) {
passOut(ev)
}
open fun passOut(ev: MotionEvent) {
parent?.passOut(ev)
}
}
class MViewGroup(private val child: MView) : MView() {
init {
child.parent = this // 示意寫法
}
override fun passIn(ev: MotionEvent) {
child.passIn(ev)
}
}
複製代碼
這段代碼沒有問題,很是簡單,可是它對需求意圖的表達不夠清晰,增長了框架的使用難度
因而咱們用一個叫作dispatch()的方法單獨放事件傳遞的控制邏輯,用一個叫作onTouch()的方法做爲事件處理的鉤子,並且鉤子有一個返回值,表示鉤子中是否處理了事件:
open class MView {
open fun dispatch(ev: MotionEvent): Boolean {
return onTouch(ev)
}
open fun onTouch(ev: MotionEvent): Boolean {
return false
}
}
class MViewGroup(private val child: MView) : MView() {
override fun dispatch(ev: MotionEvent): Boolean {
var handled = child.dispatch(ev)
if (!handled) handled = onTouch(ev)
return handled
}
override fun onTouch(ev: MotionEvent): Boolean {
return false
}
}
複製代碼
這樣寫完,整個行爲其實沒有變化,但你會發現:
1.3. 三造:區分事件類型
上文的實現看上去已經初具雛形了,但其實連開始提的那條原則都沒實現完,由於原則要求一次操做只能有一個 View 進行處理,而咱們實現的是一個觸摸事件只能有一個View進行處理。 這裏就涉及到一次觸摸操做和一個觸摸事件的區別:
因而設計原則更確切地說就是:一次觸摸產生的事件流,只能被一個View消費
在上次試造的基礎上把一個事件變成一個組事件流,其實很是簡單:處理DOWN事件時跟前面處理一個事件時同樣,但須要同時記住DOWN事件的消費對象,後續的MOVE/UP事件直接交給它就好了
open class MView {
open fun dispatch(ev: MotionEvent): Boolean {
return onTouch(ev)
}
open fun onTouch(ev: MotionEvent): Boolean {
return false
}
}
class MViewGroup(private val child: MView) : MView() {
private var isChildNeedEvent = false
override fun dispatch(ev: MotionEvent): Boolean {
var handled = false
if (ev.actionMasked == MotionEvent.ACTION_DOWN) {
clearStatus()
handled = child.dispatch(ev)
if (handled) isChildNeedEvent = true
if (!handled) handled = onTouch(ev)
} else {
if (isChildNeedEvent) handled = child.dispatch(ev)
if (!handled) handled = onTouch(ev)
}
if (ev.actionMasked == MotionEvent.ACTION_UP) {
clearStatus()
}
return handled
}
private fun clearStatus() {
isChildNeedEvent = false
}
override fun onTouch(ev: MotionEvent): Boolean {
return false
}
}
複製代碼
代碼好像增長了不少,其實只多作了兩件事:
此時框架使用者仍是隻須要關心onTouch()鉤子,在須要處理事件時進行處理並返回true,其餘事情框架都作好了。
1.4. 四造:增長外部事件攔截
上面的框架已經能完成基本的事件分發工做了,但下面這個需求,你嘗試一下用如今框架能實現嗎? 需求:在可滑動View中有一個可點擊View,須要讓用戶即便按下的位置是可點擊View,再進行滑動時,也能夠滑動外面的的可滑動View。
這個需求其實很是常見,好比全部「條目可點擊的滑動列表」就是這樣的(微信/QQ聊天列表)。
假如使用上面的框架:
因此直接使用如今的模型去實現的「條目可點擊的滑動列表」會永遠滑動不了。
那怎麼辦呢?
直接想實現以爲處處是矛盾,找不到突破口,那就從頭開始吧,從什麼樣的觸摸反饋是用戶以爲天然的出發,看看這種符合直覺的反饋方案是否存在,找出來它是什麼,再考慮咱們要怎麼實現:
你先忘記前面說的原則,你想一想,不考慮其餘因素,也不是隻能用DOWN事件,只要你能判斷用戶的想法就行,你有什麼辦法?
看上去這個目標 View 斷定方案很不錯,安排得明明白白,但咱們現有的事件處理框架實現不了這樣的斷定方案,至少存在如下兩個衝突:
因此要解決上述的衝突,就確定要對上一版的事件處理框架進行修改,並且看上去一不當心就會大改
首先看第二個衝突,解決它的一個直接方案是:調整 dispatch() 方法在傳入事件過程當中的人設,讓它不是隻傳遞事件了,還能夠在往裏傳遞事件前進行攔截,可以看狀況攔截下事件並交給本身的 onTouch() 處理
基於這個解決方案,大概有如下兩個改動相對小的方案調整思路:
兩個思路總結一下:
這兩個思路都要對當前框架作改變,看似差很少,但其實仍是有比較明顯的優劣的
示意圖:
open class MView {
open fun dispatch(ev: MotionEvent): Boolean {
return onTouch(ev)
}
open fun onTouch(ev: MotionEvent): Boolean {
return false
}
}
class MViewGroup(private val child: MView) : MView() {
private var isChildNeedEvent = false
private var isSelfNeedEvent = false
override fun dispatch(ev: MotionEvent): Boolean {
var handled = false
if (ev.actionMasked == MotionEvent.ACTION_DOWN) {
clearStatus()
if (onIntercept(ev)) {
isSelfNeedEvent = true
handled = onTouch(ev)
} else {
handled = child.dispatch(ev)
if (handled) isChildNeedEvent = true
if (!handled) {
handled = onTouch(ev)
if (handled) isSelfNeedEvent = true
}
}
} else {
if (isSelfNeedEvent) {
handled = onTouch(ev)
} else if (isChildNeedEvent) {
if (onIntercept(ev)) {
isSelfNeedEvent = true
handled = onTouch(ev)
} else {
handled = child.dispatch(ev)
}
}
}
if (ev.actionMasked == MotionEvent.ACTION_UP) {
clearStatus()
}
return handled
}
private fun clearStatus() {
isChildNeedEvent = false
isSelfNeedEvent = false
}
override fun onTouch(ev: MotionEvent): Boolean {
return false
}
open fun onIntercept(ev: MotionEvent): Boolean {
return false
}
}
複製代碼
寫的過程當中增長了一些對細節的處理:
這一下代碼是否是看上去瞬間複雜了,但其實只是增長了一個事件攔截機制,對比上一次試造的輪子,會更容易理解。(要是 Markdown 支持代碼塊內自定義着色就行了)
並且對於框架的使用者來講,關注點仍是很是少
1.5. 五造:增長內部事件攔截
上面的處理思路雖然實現了需求,但可能會致使一個問題:裏邊的子 View 接收了一半的事件,可能都已經開始處理並作了一些事情,父 View 突然就不把後續事件給它了,會不會違背用戶操做的直覺?甚至出現更奇怪的現象?
這個問題確實比較麻煩,分兩類狀況討論
裏邊的 View 接收了一半事件,但尚未真正開始反饋交互,或者在進行能夠被取消的反饋
裏邊的View接收了一半事件,已經開始反饋交互了,這種反饋最好不要去取消它,或者說取消了會顯得很怪
這個時候,事情會複雜一些,並且這個場景發生的遠比你想象中的多,形式也多種多樣,不處理好的後果也比只是讓用戶感受上奇怪要嚴重得多,可能會有的功能會實現不了,下面舉兩個例子
因此這類問題是必定要解決的,但要怎麼解決呢
因此,連同上一次試造,總結一下
另外有幾個值得一提的地方:
interface ViewParent {
fun requestDisallowInterceptTouchEvent(isDisallowIntercept: Boolean)
}
open class MView {
var parent: ViewParent? = null
open fun dispatch(ev: MotionEvent): Boolean {
return onTouch(ev)
}
open fun onTouch(ev: MotionEvent): Boolean {
return false
}
}
open class MViewGroup(private val child: MView) : MView(), ViewParent {
private var isChildNeedEvent = false
private var isSelfNeedEvent = false
private var isDisallowIntercept = false
init {
child.parent = this
}
override fun dispatch(ev: MotionEvent): Boolean {
var handled = false
if (ev.actionMasked == MotionEvent.ACTION_DOWN) {
clearStatus()
// add isDisallowIntercept
if (!isDisallowIntercept && onIntercept(ev)) {
isSelfNeedEvent = true
handled = onTouch(ev)
} else {
handled = child.dispatch(ev)
if (handled) isChildNeedEvent = true
if (!handled) {
handled = onTouch(ev)
if (handled) isSelfNeedEvent = true
}
}
} else {
if (isSelfNeedEvent) {
handled = onTouch(ev)
} else if (isChildNeedEvent) {
// add isDisallowIntercept
if (!isDisallowIntercept && onIntercept(ev)) {
isSelfNeedEvent = true
// add cancel
val cancel = MotionEvent.obtain(ev)
cancel.action = MotionEvent.ACTION_CANCEL
handled = child.dispatch(cancel)
cancel.recycle()
} else {
handled = child.dispatch(ev)
}
}
}
if (ev.actionMasked == MotionEvent.ACTION_UP
|| ev.actionMasked == MotionEvent.ACTION_CANCEL) {
clearStatus()
}
return handled
}
private fun clearStatus() {
isChildNeedEvent = false
isSelfNeedEvent = false
isDisallowIntercept = false
}
override fun onTouch(ev: MotionEvent): Boolean {
return false
}
open fun onIntercept(ev: MotionEvent): Boolean {
return false
}
override fun requestDisallowInterceptTouchEvent(isDisallowIntercept: Boolean) {
this.isDisallowIntercept = isDisallowIntercept
parent?.requestDisallowInterceptTouchEvent(isDisallowIntercept)
}
}
複製代碼
此次改動主要是增長了發出CANCEL事件和requestDisallowInterceptTouchEvent機制
雖然目前整個框架的代碼有點複雜,但對於使用者來講,依然很是簡單,只是在上一版框架的基礎上增長了:
到這裏,事件分發的主要邏輯已經講清楚了,不過還差一段 Activity 中的處理,其實它作的事情相似ViewGroup,只有這幾個區別:
open class MActivity(private val childGroup: MViewGroup) {
private var isChildNeedEvent = false
private var isSelfNeedEvent = false
open fun dispatch(ev: MotionEvent): Boolean {
var handled = false
if (ev.actionMasked == MotionEvent.ACTION_DOWN) {
clearStatus()
handled = childGroup.dispatch(ev)
if (handled) isChildNeedEvent = true
if (!handled) {
handled = onTouch(ev)
if (handled) isSelfNeedEvent = true
}
} else {
if (isSelfNeedEvent) {
handled = onTouch(ev)
} else if (isChildNeedEvent) {
handled = childGroup.dispatch(ev)
}
if (!handled) handled = onTouch(ev)
}
if (ev.actionMasked == MotionEvent.ACTION_UP
|| ev.actionMasked == MotionEvent.ACTION_CANCEL) {
clearStatus()
}
return handled
}
private fun clearStatus() {
isChildNeedEvent = false
isSelfNeedEvent = false
}
open fun onTouch(ev: MotionEvent): Boolean {
return false
}
}
複製代碼
因此回頭看,你會發現事件分發其實很簡單,它的關鍵不在於「不一樣的事件類型、不一樣的View種類、不一樣的回調方法、方法不一樣的返回值」對事件分發是怎麼影響的。
關鍵在於「它要實現什麼功能?對實現效果有什麼要求?使用了什麼解決方案?」,從這個角度,就能清晰並且簡單地把事件分發整個流程梳理清楚。
事件分發要實現的功能是:對用戶的觸摸操做進行反饋,使之符合用戶的直覺。
從用戶的直覺出發能獲得這麼兩個要求:
用戶的一次操做只有一個View去消費
咱們使用了一套簡單但有效的先到先得策略,讓內外的可消費事件的View擁有近乎平等的競爭消費者的資格:它們都能接收到事件,並在本身斷定應該消費事件的時候去發起競爭申請,申請成功後事件就所有由它消費。
模擬View和ViewGroup都不消費事件的場景:
[down]
|layer:SActivity |on:Dispatch_BE |type:down
|layer:SViewGroup |on:Dispatch_BE |type:down
|layer:SViewGroup |on:Intercept_BE |type:down
|layer:SViewGroup |on:Intercept_AF |result(super):false |type:down
|layer:SView |on:Dispatch_BE |type:down
|layer:SView |on:Touch_BE |type:down
|layer:SView |on:Touch_AF |result(super):false |type:down
|layer:SView |on:Dispatch_AF |result(super):false |type:down
|layer:SViewGroup |on:Touch_BE |type:down
|layer:SViewGroup |on:Touch_AF |result(super):false |type:down
|layer:SViewGroup |on:Dispatch_AF |result(super):false |type:down
|layer:SActivity |on:Touch_BE |type:down
|layer:SActivity |on:Touch_AF |result(super):false |type:down
|layer:SActivity |on:Dispatch_AF |result(super):false |type:down
[move]
|layer:SActivity |on:Dispatch_BE |type:move
|layer:SActivity |on:Touch_BE |type:move
|layer:SActivity |on:Touch_AF |result(super):false |type:move
|layer:SActivity |on:Dispatch_AF |result(super):false |type:move
[move]
...
[up]
|layer:SActivity |on:Dispatch_BE |type:up
|layer:SActivity |on:Touch_BE |type:up
|layer:SActivity |on:Touch_AF |result(super):false |type:up
|layer:SActivity |on:Dispatch_AF |result(super):false |type:up
複製代碼
這裏用BE表明 before,表示該方法開始處理事件的時候,用AF表明after,表示該方法結束處理事件的時候,而且打印處理的結果
從日誌中能清楚看到,當View和ViewGroup都不消費DOWN事件時,後續事件將再也不傳遞給View和ViewGroup
模擬View和ViewGroup都消費事件,同時ViewGroup在第二個MOVE事件時認爲本身須要攔截事件的場景:
[down]
|layer:SActivity |on:Dispatch_BE |type:down
|layer:SViewGroup |on:Dispatch_BE |type:down
|layer:SViewGroup |on:Intercept |result(false):false |type:down
|layer:SView |on:Dispatch_BE |type:down
|layer:SView |on:Touch |result(true):true |type:down
|layer:SView |on:Dispatch_AF |result(super):true |type:down
|layer:SViewGroup |on:Dispatch_AF |result(super):true |type:down
|layer:SActivity |on:Dispatch_AF |result(super):true |type:down
[move]
|layer:SActivity |on:Dispatch_BE |type:move
|layer:SViewGroup |on:Dispatch_BE |type:move
|layer:SViewGroup |on:Intercept |result(false):false |type:move
|layer:SView |on:Dispatch_BE |type:move
|layer:SView |on:Touch |result(true):true |type:move
|layer:SView |on:Dispatch_AF |result(super):true |type:move
|layer:SViewGroup |on:Dispatch_AF |result(super):true |type:move
|layer:SActivity |on:Dispatch_AF |result(super):true |type:move
[move]
|layer:SActivity |on:Dispatch_BE |type:move
|layer:SViewGroup |on:Dispatch_BE |type:move
|layer:SViewGroup |on:Intercept |result(true):true |type:move
|layer:SView |on:Dispatch_BE |type:cancel
|layer:SView |on:Touch_BE |type:cancel
|layer:SView |on:Touch_AF |result(super):false |type:cancel
|layer:SView |on:Dispatch_AF |result(super):false |type:cancel
|layer:SViewGroup |on:Dispatch_AF |result(super):false |type:move
|layer:SActivity |on:Touch_BE |type:move
|layer:SActivity |on:Touch_AF |result(super):false |type:move
|layer:SActivity |on:Dispatch_AF |result(super):false |type:move
[move]
|layer:SActivity |on:Dispatch_BE |type:move
|layer:SViewGroup |on:Dispatch_BE |type:move
|layer:SViewGroup |on:Touch |result(true):true |type:move
|layer:SViewGroup |on:Dispatch_AF |result(super):true |type:move
|layer:SActivity |on:Dispatch_AF |result(super):true |type:move
[up]
|layer:SActivity |on:Dispatch_BE |type:up
|layer:SViewGroup |on:Dispatch_BE |type:up
|layer:SViewGroup |on:Touch |result(true):true |type:up
|layer:SViewGroup |on:Dispatch_AF |result(super):true |type:up
|layer:SActivity |on:Dispatch_AF |result(super):true |type:up
複製代碼
從日誌中能清楚看到,在ViewGroup攔截事件先後,事件是如何分發的
public boolean dispatchTouchEvent(MotionEvent event) {
boolean result = false;
final int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Defensive cleanup for new gesture
stopNestedScroll();
}
if (onFilterTouchEventForSecurity(event)) {
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
if (!result && onTouchEvent(event)) {
result = true;
}
}
// Clean up after nested scrolls if this is the end of a gesture;
// also cancel it if we tried an ACTION_DOWN but we didn't want the rest // of the gesture. if (actionMasked == MotionEvent.ACTION_UP || actionMasked == MotionEvent.ACTION_CANCEL || (actionMasked == MotionEvent.ACTION_DOWN && !result)) { stopNestedScroll(); } return result; } 複製代碼
定義ListenerInfo局部變量,ListenerInfo是View的靜態內部類,用來定義一堆關於View的XXXListener等方法;接着if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event))語句就是重點,首先li對象天然不會爲null,li.mOnTouchListener經過下面方法賦值
public void setOnTouchListener(OnTouchListener l) {
getListenerInfo().mOnTouchListener = l;
}
複製代碼
li.mOnTouchListener是否是null取決於控件(View)是否設置setOnTouchListener監聽。接着經過位與運算肯定控件(View)是否是ENABLED 的,默認控件都是ENABLED 的;接着判斷onTouch的返回值是否是true。經過如上判斷以後若是都爲true則設置默認爲false的result爲true,那麼接下來的if (!result && onTouchEvent(event))就不會執行,最終dispatchTouchEvent也會返回true。而若是if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event))語句有一個爲false則if (!result && onTouchEvent(event))就會執行,若是onTouchEvent(event)返回false則dispatchTouchEvent返回false,不然返回true。
控件觸摸就會調運dispatchTouchEvent方法,而在dispatchTouchEvent中先執行的是onTouch方法,因此驗證了實例結論總結中的onTouch優先於onClick執行道理。若是控件是ENABLE且在onTouch方法裏返回了true則dispatchTouchEvent方法也返回true,不會再繼續往下執行;反之,onTouch返回false則會繼續向下執行onTouchEvent方法,且dispatchTouchEvent的返回值與onTouchEvent返回值相同。
總結結論
在View的觸摸屏傳遞機制中經過分析dispatchTouchEvent方法源碼咱們會得出以下基本結論:
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
if (prepressed) {
setPressed(true, x, y);
}
if (!mHasPerformedLongPress) {
removeLongPressCallback();
if (!focusTaken) {
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClick();
}
}
}
if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}
if (prepressed) {
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
mUnsetPressedState.run();
}
removeTapCallback();
}
break;
case MotionEvent.ACTION_DOWN:
mHasPerformedLongPress = false;
if (performButtonActionOnTouchDown(event)) {
break;
}
boolean isInScrollingContainer = isInScrollingContainer();
if (isInScrollingContainer) {
mPrivateFlags |= PFLAG_PREPRESSED;
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
mPendingCheckForTap.x = event.getX();
mPendingCheckForTap.y = event.getY();
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
setPressed(true, x, y);
checkForLongClick(0);
}
break;
case MotionEvent.ACTION_CANCEL:
setPressed(false);
removeTapCallback();
removeLongPressCallback();
break;
case MotionEvent.ACTION_MOVE:
drawableHotspotChanged(x, y);
if (!pointInView(x, y, mTouchSlop)) {
removeTapCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
removeLongPressCallback();
setPressed(false);
}
}
break;
}
return true;
}
return false;
}
複製代碼
若是一個控件是enable且disclickable則onTouchEvent直接返回false了;反之,若是一個控件是enable且clickable則繼續進入過於一個event的switch判斷中,而後最終onTouchEvent都返回了true。switch的ACTION_DOWN與ACTION_MOVE都進行了一些必要的設置與置位,接着到手擡起來ACTION_UP時你會發現,首先判斷了是否按下過,同時是否是能夠獲得焦點,而後嘗試獲取焦點,而後判斷若是不是longPressed則經過post在UI Thread中執行一個PerformClick的Runnable,也就是performClick方法。
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;
}
複製代碼
public void setOnClickListener(OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
getListenerInfo().mOnClickListener = l;
}
複製代碼
控件只要監聽了onClick方法則mOnClickListener就不爲null,並且有意思的是若是調運setOnClickListener方法設置監聽且控件是disclickable的狀況下默認會幫設置爲clickable。onClick就在onTouchEvent中執行的,並且是在onTouchEvent的ACTION_UP事件中執行的。
總結結論
Android View的觸摸屏事件傳遞機制有以下特徵:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
......
boolean handled = false;
......
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// 1)處理初始的ACTION_DOWN
if (actionMasked == MotionEvent.ACTION_DOWN) {
// 把ACTION_DOWN做爲一個Touch手勢的始點,清除以前的手勢狀態。
cancelAndClearTouchTargets(ev); //清除前一個手勢,*關鍵操做:mFirstTouchTarget重置爲null*
resetTouchState(); //重置Touch狀態標識
}
// 2)檢查是否會被攔截
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
// 是ACTION_DOWN的事件,或者mFirstTouchTarget不爲null(已經找到可以接收touch事件的目標組件)
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
// 判斷禁止攔截的FLAG,由於requestDisallowInterceptTouchEvent(boolean disallowIntercept)方法能夠禁止執行是否須要攔截的判斷
if (!disallowIntercept) {
// 禁止攔截的FLAG爲false,說明能夠執行攔截判斷,則執行此ViewGroup的onInterceptTouchEvent方法
intercepted = onInterceptTouchEvent(ev); // 此方法默認返回false,若是想修改默認的行爲,須要override此方法,修改返回值。
ev.setAction(action);
} else {
// 禁止攔截的FLAG爲ture,說明沒有必要去執行是否須要攔截了,這個事件是沒法攔截的,可以順利經過,因此設置攔截變量爲false
intercepted = false;
}
} else {
// 當不是ACTION_DOWN事件而且mFirstTouchTarget爲null(意味着沒有touch的目標組件)時,這個ViewGroup應該繼續執行攔截的操做。
intercepted = true;
}
// 經過前面的邏輯處理,獲得了是否須要進行攔截的變量值
final boolean canceled = resetCancelNextUpFlag(this) || actionMasked == MotionEvent.ACTION_CANCEL;
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
if (!canceled && !intercepted) {
// 不是ACTION_CANCEL而且攔截變量爲false
if (actionMasked == MotionEvent.ACTION_DOWN) {
// 在ACTION_DOWN時去尋找此次DOWN事件新出現的TouchTarget
final int actionIndex = ev.getActionIndex(); // always 0 for down
.....
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
// 根據觸摸的座標尋找可以接收這個事件的子組件
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
final View[] children = mChildren;
// 逆序遍歷全部子組件
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = i;
final View child = children[childIndex];
// 尋找可接收這個事件而且組件區域內包含點擊座標的子View
if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) {
continue;
}
newTouchTarget = getTouchTarget(child); // 找到了符合條件的子組件,賦值給newTouchTarget
......
// 把ACTION_DOWN事件傳遞給子組件進行處理
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// 若是此子ViewGroup消費了這個touch事件
mLastTouchDownTime = ev.getDownTime();
mLastTouchDownIndex = childIndex;
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
// 則爲mFirstTouchTarget賦值爲newTouchTarget,此子組件成爲新的touch事件的起點
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
}
}
......
}
}
// 通過前面的ACTION_DOWN的處理,有兩種狀況。
if (mFirstTouchTarget == null) {
// 狀況1:(mFirstTouchTarget爲null) 沒有找到可以消費touch事件的子組件或者是touch事件被攔截了,
// 那麼在ViewGroup的dispatchTransformedTouchEvent方法裏面,處理Touch事件則和普通View同樣,
// 本身沒法消費,調用super.dispatchOnTouchEvent()把事件回遞給父ViewGroup進行處理
handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS);
} else {
// 狀況2:(mFirstTouchTarget!=null) 找到了可以消費touch事件的子組件,那麼後續的touch事件均可以傳遞到子View
TouchTarget target = mFirstTouchTarget;
// (這裏爲了理解簡單,省略了一個Target List的概念,有須要的同窗再查看源碼)
while (target != null) {
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
// 若是前面利用ACTION_DOWN事件尋找符合接收條件的子組件的同時消費掉了ACTION_DOWN事件,這裏直接返回true
handled = true;
} else {
final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted;
// 對於非ACTION_DOWN事件,則繼續傳遞給目標子組件進行處理(注意這裏的非ACTION_DOWN事件已經不須要再判斷是否攔截)
if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) {
// 若是target子組件進行處理,符合某些條件的話,會傳遞ACTION_CANCEL給target子組件
// 條件是:若是ACTION_DOWN時沒有被攔截,然後面的touch事件被攔截,則須要發送ACTION_CANCEL給target子組件
handled = true;
}
......
}
}
}
if (canceled || actionMasked == MotionEvent.ACTION_UP) {
// 若是是ACTION_CANCEL或者ACTION_UP,重置Touch狀態標識,mFirstTouchTarget賦值爲null,後面的Touch事件都沒法派發給子View
resetTouchState();
}
......
return handled;
}
複製代碼
ACTION_DOWN時進行一些初始化操做,清除以往的Touch狀態而後開始新的手勢。在這裏你會發現cancelAndClearTouchTargets(ev)方法中有一個很是重要的操做就是將mFirstTouchTarget設置爲了null,接着在resetTouchState()方法中重置Touch狀態標識。
使用變量intercepted來標記ViewGroup是否攔截Touch事件的傳遞,該變量相似第一步的mFirstTouchTarget變量,在後續代碼中起着很重要的做用。if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null)這一條判斷語句說明當事件爲ACTION_DOWN或者mFirstTouchTarget不爲null(即已經找到可以接收touch事件的目標組件)時if成立,不然if不成立,而後將intercepted設置爲true,也即攔截事件。當事件爲ACTION_DOWN或者mFirstTouchTarget不爲null時判斷disallowIntercept(禁止攔截)標誌位,而這個標記在ViewGroup中提供了public的設置方法
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
return;
}
if (disallowIntercept) {
mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
} else {
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
// Pass it up to our parent
if (mParent != null) {
mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
}
}
複製代碼
在其餘地方調用requestDisallowInterceptTouchEvent(boolean disallowIntercept)方法,從而禁止執行是否須要攔截的判斷。當disallowIntercept爲true(禁止攔截判斷)時則intercepted直接設置爲false,不然調用onInterceptTouchEvent(ev)方法,而後將結果賦值給intercepted。那就來看下ViewGroup與View特有的onInterceptTouchEvent方法,以下:
public boolean onInterceptTouchEvent(MotionEvent ev) {
return false;
}
複製代碼
經過標記和action檢查cancel,而後將結果賦值給局部boolean變量canceled。
獲取一個boolean變量標記split來標記,默認是true,做用是是否把事件分發給多個子View,這個一樣在ViewGroup中提供了public的方法設置,以下:
public void setMotionEventSplittingEnabled(boolean split) {
if (split) {
mGroupFlags |= FLAG_SPLIT_MOTION_EVENTS;
} else {
mGroupFlags &= ~FLAG_SPLIT_MOTION_EVENTS;
}
}
複製代碼
if (!canceled && !intercepted)判斷代表,事件不是ACTION_CANCEL而且ViewGroup的攔截標誌位intercepted爲false(不攔截)則會進入其中。
if語句if (actionMasked == MotionEvent.ACTION_DOWN || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) || actionMasked == MotionEvent.ACTION_HOVER_MOVE)處理ACTION_DOWN事件,判斷了childrenCount個數是否不爲0,而後接着拿到了子View的list集合preorderedList;接着經過一個for循環i從childrenCount - 1開始遍歷到0,倒序遍歷全部的子view,這是由於preorderedList中的順序是按照addView或者XML佈局文件中的順序來的,後addView添加的子View,會由於Android的UI後刷新機制顯示在上層;假如點擊的地方有兩個子View都包含的點擊的座標,那麼後被添加到佈局中的那個子view會先響應事件;這樣其實也是符合人的思惟方式的,由於後被添加的子view會浮在上層,因此咱們去點擊的時候通常都會但願點擊最上層的那個組件先去響應事件。
經過getTouchTarget去查找當前子View是否在mFirstTouchTarget.next這條target鏈中的某一個targe中,若是在則返回這個target,不然返回null。在這段代碼的if判斷經過說明找到了接收Touch事件的子View,即newTouchTarget,那麼,既然已經找到了,因此執行break跳出for循環。若是沒有break則繼續向下執行,這裏你能夠看見一段if判斷的代碼if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)),調用方法dispatchTransformedTouchEvent()將Touch事件傳遞給特定的子View。該方法十分重要,在該方法中爲一個遞歸調用,會遞歸調用dispatchTouchEvent()方法。在dispatchTouchEvent()中若是子View爲ViewGroup而且Touch沒有被攔截那麼遞歸調用dispatchTouchEvent(),若是子View爲View那麼就會調用其onTouchEvent()。dispatchTransformedTouchEvent方法若是返回true則表示子View消費掉該事件,同時進入該if判斷。知足if語句後重要的操做有:
若是if判斷中的dispatchTransformedTouchEvent()方法返回false,即子View的onTouchEvent返回false(即Touch事件未被消費),那麼就不知足該if條件,也就沒法執行addTouchTarget(),從而致使mFirstTouchTarget爲null(無法對mFirstTouchTarget賦值,由於上面分析了mFirstTouchTarget一進來是ACTION_DOWN就置位爲null了),那麼該子View就沒法繼續處理ACTION_MOVE事件和ACTION_UP事件。
若是if判斷中的dispatchTransformedTouchEvent()方法返回true,即子View的onTouchEvent返回true(即Touch事件被消費),那麼就知足該if條件,從而mFirstTouchTarget不爲null。
if (newTouchTarget == null && mFirstTouchTarget != null)。該if表示通過前面的for循環沒有找到子View接收Touch事件而且以前的mFirstTouchTarget不爲空則爲真,而後newTouchTarget指向了最初的TouchTarget。
對於此處ACTION_DOWN的處理具體體如今dispatchTransformedTouchEvent()方法,該方法返回值具有以下特徵:
由於在dispatchTransformedTouchEvent()會調用遞歸調用dispatchTouchEvent()和onTouchEvent(),因此dispatchTransformedTouchEvent()的返回值其實是由onTouchEvent()決定的。簡單地說onTouchEvent()是否消費了Touch事件的返回值決定了dispatchTransformedTouchEvent()的返回值,從而決定mFirstTouchTarget是否爲null,進一步決定了ViewGroup是否處理Touch事件,通過上面對於ACTION_DOWN的處理後mFirstTouchTarget可能爲null或者不爲null。
mFirstTouchTarget爲null時,也就是說Touch事件未被消費,即沒有找到可以消費touch事件的子組件或Touch事件被攔截了,則調用ViewGroup的dispatchTransformedTouchEvent()方法處理Touch事件(和普通View同樣),即子View沒有消費Touch事件,那麼子View的上層ViewGroup纔會調用其onTouchEvent()處理Touch事件。具體就是在調用dispatchTransformedTouchEvent()時第三個參數爲null .子view對於Touch事件處理返回true那麼其上層的ViewGroup就沒法處理Touch事件了,子view對於Touch事件處理返回false那麼其上層的ViewGroup才能夠處理Touch事件。mFirstTouchTarget不爲null時,也就是說找到了能夠消費Touch事件的子View且後續Touch事件能夠傳遞到該子View。能夠看見在源碼的else中對於非ACTION_DOWN事件繼續傳遞給目標子組件進行處理,依然是遞歸調用dispatchTransformedTouchEvent()方法來實現的處理。
public boolean onInterceptTouchEvent(MotionEvent ev) {
return false;
}
複製代碼
若是ViewGroup的onInterceptTouchEvent返回false就不阻止事件繼續傳遞派發,不然阻止傳遞派發。
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(); if (cancel || oldAction == MotionEvent.ACTION_CANCEL) { event.setAction(MotionEvent.ACTION_CANCEL); if (child == null) { handled = super.dispatchTouchEvent(event); } else { handled = child.dispatchTouchEvent(event); } event.setAction(oldAction); return handled; } // Calculate the number of pointers to deliver. final int oldPointerIdBits = event.getPointerIdBits(); final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits; // If for some reason we ended up in an inconsistent state where it looks like we // might produce a motion event with no pointers in it, then drop the event. if (newPointerIdBits == 0) { return false; } // If the number of pointers is the same and we don't need to perform any fancy
// irreversible transformations, then we can reuse the motion event for this
// dispatch as long as we are careful to revert any changes we make.
// Otherwise we need to make a copy.
final MotionEvent transformedEvent;
if (newPointerIdBits == oldPointerIdBits) {
if (child == null || child.hasIdentityMatrix()) {
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
event.offsetLocation(offsetX, offsetY);
handled = child.dispatchTouchEvent(event);
event.offsetLocation(-offsetX, -offsetY);
}
return handled;
}
transformedEvent = MotionEvent.obtain(event);
} else {
transformedEvent = event.split(newPointerIdBits);
}
// Perform any necessary transformations and dispatch.
if (child == null) {
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());
}
handled = child.dispatchTouchEvent(transformedEvent);
}
// Done.
transformedEvent.recycle();
return handled;
}
複製代碼
在dispatchTouchEvent()中調用dispatchTransformedTouchEvent()將事件分發給子View處理。在此咱們須要重點分析該方法的第三個參數(View child)。在dispatchTouchEvent()中屢次調用了dispatchTransformedTouchEvent()方法,並且有時候第三個參數爲null,有時又不是,他們到底有啥區別呢?這段源碼中很明顯展現告終果。在dispatchTransformedTouchEvent()源碼中能夠發現屢次對於child是否爲null的判斷,而且均作出以下相似的操做。其中,當child == null時會將Touch事件傳遞給該ViewGroup自身的dispatchTouchEvent()處理,即super.dispatchTouchEvent(event)(也就是View的這個方法,由於ViewGroup的父類是View);當child != null時會調用該子view(固然該view多是一個View也多是一個ViewGroup)的dispatchTouchEvent(event)處理,即child.dispatchTouchEvent(event)。 ViewGroup沒有重寫View的onTouchEvent(MotionEvent event) 方法.
總結結論
Android事件派發是先傳遞到最頂級的ViewGroup,再由ViewGroup遞歸傳遞到View的。 在ViewGroup中能夠經過onInterceptTouchEvent方法對事件傳遞進行攔截,onInterceptTouchEvent方法返回true表明不容許事件繼續向子View傳遞,返回false表明不對事件進行攔截,默認返回false。 子View中若是將傳遞的事件消費掉,ViewGroup中將沒法接收到任何事件。
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
複製代碼
Activity的attach方法能夠發現getWindow()返回的就是PhoneWindow對象(PhoneWindow爲抽象Window的實現子類),那就簡單了,也就至關於PhoneWindow類的方法,而PhoneWindow類實現於Window抽象類,因此先看下Window類中抽象方法的定義,以下:
public abstract boolean superDispatchTouchEvent(MotionEvent event);
複製代碼
PhoneWindow裏看下Window抽象方法的實現吧,以下:
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
複製代碼
在PhoneWindow類裏發現,mDecor是DecorView類的實例,同時DecorView是PhoneWindow的內部類。最驚人的發現是DecorView extends FrameLayout implements RootViewSurfaceTaker,看見沒有?它是一個真正Activity的root view,它繼承了FrameLayout。
DecorView類的superDispatchTouchEvent方法吧,以下:
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
複製代碼
Activity的dispatchTouchEvent方法的if (getWindow().superDispatchTouchEvent(ev))本質執行的是一個ViewGroup的dispatchTouchEvent方法(這個ViewGroup是Activity特有的root view,也就是id爲content的FrameLayout佈局).
在Activity的觸摸屏事件派發中:
public void onUserInteraction() {
}
複製代碼
此方法是activity的方法,當此activity在棧頂時,觸屏點擊按home,back,menu鍵等都會觸發此方法。下拉statubar、旋轉屏幕、鎖屏不會觸發此方法。因此它會用在屏保應用上,由於當你觸屏機器 就會立馬觸發一個事件,而這個事件又不太明確是什麼,正好屏保知足此需求;或者對於一個Activity,控制多長時間沒有用戶點響應的時候,本身消失等。
public boolean onTouchEvent(MotionEvent event) {
if (mWindow.shouldCloseOnTouch(this, event)) {
finish();
return true;
}
return false;
}
複製代碼
若是一個屏幕觸摸事件沒有被這個Activity下的任何View所處理,Activity的onTouchEvent將會調用。這對於處理window邊界以外的Touch事件很是有用,由於一般是沒有View會接收到它們的。返回值爲true代表你已經消費了這個事件,false則表示沒有消費,默認實現中返回false。
繼續分析吧,重點就一句,mWindow.shouldCloseOnTouch(this, event)中的mWindow實際就是上面分析dispatchTouchEvent方法裏的getWindow()對象,因此直接到Window抽象類和PhoneWindow子類查看吧,發現PhoneWindow沒有重寫Window的shouldCloseOnTouch方法,因此看下Window類的shouldCloseOnTouch實現吧,以下:
public boolean shouldCloseOnTouch(Context context, MotionEvent event) {
if (mCloseOnTouchOutside && event.getAction() == MotionEvent.ACTION_DOWN
&& isOutOfBounds(context, event) && peekDecorView() != null) {
return true;
}
return false;
}
複製代碼
判斷mCloseOnTouchOutside標記及是否爲ACTION_DOWN事件,同時判斷event的x、y座標是否是超出Bounds,而後檢查FrameLayout的content的id的DecorView是否爲空。其實沒啥過重要的,這只是對於處理window邊界以外的Touch事件有判斷價值而已。