在上一篇 《Compose 事件分發(fā)(上) 尋找觸摸點(diǎn)》中已經(jīng)介紹,在觸摸 compose 組件時(shí),會(huì)從根節(jié)點(diǎn)開(kāi)始遍歷,獲取命中的 PointerInputFilter,然后對(duì)其進(jìn)行事件分發(fā),今天,我們來(lái)重點(diǎn)講解一下事件的分發(fā)過(guò)程,并且在 AndroidView 上,嵌套原生 View 的時(shí)候,事件的分發(fā)過(guò)程
一、示例
AppTheme {
// Box 組件
Box(modifier = Modifier
.background(Color.Gray)
.pointerInput(Unit) {
detectTapGestures(onPress = {
Log.i("TAG", "detectTapGestures 100 onPress")
})
}.size(300.dp)
){
// Row 組件
Row( modifier = Modifier
.background(Color.Yellow)
.pointerInput(Unit) {
detectTapGestures(onPress = {
Log.i("TAG", "detectTapGestures 50 onPress")
})
}.size(150.dp)
){}
}
}
這次我們的示例更改一下,添加兩個(gè)帶有 pointInput 的組件 Box 和 Row,以便更好的查看事件響應(yīng)。
二、分析
1、Compose 組件事件分發(fā)分析
繼續(xù)回到 pointerInputEventProcessor.process 方法:
@OptIn(InternalCoreApi::class)
// 1、root 為 AndroidComposeView 傳進(jìn)來(lái)的根節(jié)點(diǎn)
internal class PointerInputEventProcessor(val root: LayoutNode) {
...
fun process(
pointerEvent: PointerInputEvent,
positionCalculator: PositionCalculator
): ProcessResult {
// 收集 PointerInputFilter 集
// 6、分發(fā)事件 Dispatch to PointerInputFilters
val dispatchedToSomething = hitPathTracker.dispatchChanges(internalPointerEvent)
....
return ProcessResult(dispatchedToSomething, anyMovementConsumed)
}
我們來(lái)查看下 dispatchChanges 方法:
fun dispatchChanges(internalPointerEvent: InternalPointerEvent): Boolean {
// 1、遍歷子節(jié)點(diǎn),分發(fā) main 事件
var dispatchHit = root.dispatchMainEventPass(
internalPointerEvent.changes,
rootCoordinates,
internalPointerEvent
)
// 2、遍歷子節(jié)點(diǎn),分發(fā) final 事件
dispatchHit = root.dispatchFinalEventPass() || dispatchHit
return dispatchHit
}
這里的 root 再介紹一下,引用上文:
將 hitResult 集合設(shè)置到 hitPathTracker 中,內(nèi)部會(huì)對(duì) hitResult 集合轉(zhuǎn)成 Node 鏈表,在分發(fā)時(shí)會(huì)遍歷該鏈表,需要注意的是,這個(gè)鏈表的順序是從 parent layoutNode 到 child LayoutNode 的順序,跟 view 分發(fā)一致
遍歷子節(jié)點(diǎn),本質(zhì)就是遍歷 pointInput,分發(fā) main 事件
遍歷子節(jié)點(diǎn),本質(zhì)就是遍歷 pointInput,分發(fā) final 事件
來(lái)看下 dispatchMainEventPass 的處理:
override fun dispatchMainEventPass(
changes: Map<PointerId, PointerInputChange>,
parentCoordinates: LayoutCoordinates,
internalPointerEvent: InternalPointerEvent
): Boolean {
// Build the cache that will be used for both the main and final pass
buildCache(changes, parentCoordinates, internalPointerEvent)
return dispatchIfNeeded {
val event = pointerEvent!!
val size = coordinates!!.size
// 1、分發(fā) Initial 事件
pointerInputFilter.onPointerEvent(event, PointerEventPass.Initial, size)
// Dispatch to children.
if (pointerInputFilter.isAttached) {
// 2、繼續(xù)遍歷子節(jié)點(diǎn)遞歸分發(fā)
children.forEach {it.dispatchMainEventPass(relevantChanges,coordinates!!, internalPointerEvent)}
}
if (pointerInputFilter.isAttached) {
// 3、分發(fā) Main 事件
pointerInputFilter.onPointerEvent(event, PointerEventPass.Main, size)
}
}
}
Compose 對(duì)一個(gè)事件分了三種類(lèi)型,目的是更好的處理事件,翻譯自注釋?zhuān)?/p>
- Initial :允許祖先在后代之前使用 PointerInputChange 的各個(gè)方面。例如,滾動(dòng)條可能會(huì)阻止按鈕在滾動(dòng)開(kāi)始后被其他手指點(diǎn)擊
- Main :手勢(shì)過(guò)濾器應(yīng)該對(duì) PointerInputChanges 的各個(gè)方面做出反應(yīng)和使用的主要通道。這是后代將在父母之前與 PointerInputChanges 交互的主要路徑。這允許按鈕在底部的容器響應(yīng)點(diǎn)擊之前響應(yīng)點(diǎn)擊。
- Final :在這個(gè)過(guò)程中,后代可以了解在 Main 過(guò)程中祖先使用了 PointerInputChanges 的哪些方面。例如,這是一個(gè)按鈕如何確定它不應(yīng)再響應(yīng)手指離開(kāi)它的方式,因?yàn)楦笣L動(dòng)條已經(jīng)消耗了 PointerInputChange 中的移動(dòng)。
為了不陷入源碼調(diào)用陷阱,這里結(jié)合示例用圖表示調(diào)用過(guò)程:

Main 會(huì)對(duì)事件進(jìn)行消費(fèi)處理,這也是為什么子組件優(yōu)先消費(fèi)事件的原因,也即示例 demo 中,如果我們點(diǎn)擊 Row 區(qū)域的話(huà),響應(yīng)的是 Row,而不是 Box。
事件的消費(fèi)處理,是調(diào)用 pointInput 設(shè)置的 pointerInputFilter 的 onPointerEvent 方法,我們需要回到示例 demo,找到 pointInput,進(jìn)入源碼探索:
fun Modifier.pointerInput(
key1: Any?,
block: suspend PointerInputScope.() -> Unit
): Modifier = composed(
...
) {
val density = LocalDensity.current
val viewConfiguration = LocalViewConfiguration.current
// 1、pointerInputFilter 的實(shí)現(xiàn)類(lèi)是 SuspendingPointerInputFilter
remember(density) { SuspendingPointerInputFilter(viewConfiguration, density) }.apply {
LaunchedEffect(this, key1) {
// 2、啟用掛起函數(shù),block 為示例 demo 中的 detectTapGestures
block()
}
}
}
這里我們需要關(guān)注兩個(gè)點(diǎn):
- pointerInputFilter 的實(shí)現(xiàn)類(lèi)是 SuspendingPointerInputFilter,我們需要進(jìn)入到該類(lèi)查看 onPointerEvent 的調(diào)用
- 利用 LaunchedEffect,從可組合項(xiàng)內(nèi)安全調(diào)用掛起函數(shù),block 為示例中設(shè)置的 detectTapGestures 掛起函數(shù),需要注意的是,block 是在 apply 于 SuspendingPointerInputFilter 作用域內(nèi)的,后面的擴(kuò)展函數(shù)會(huì)調(diào)用 SuspendingPointerInputFilter 的 awaitPointerEventScope 方法
detectTapGestures 可以理解成是訂閱者,SuspendingPointerInputFilter 為事件的發(fā)布者,在 SuspendingPointerInputFilter 收到事件調(diào)用 onPointerEvent 方法時(shí),會(huì)觸發(fā)該訂閱者,訂閱者處理事件是否消費(fèi),并且還可以處理是單擊、雙擊還是長(zhǎng)按,然后回調(diào)自己的各個(gè)函數(shù)。
我們先來(lái)看下事件的發(fā)布者 SuspendingPointerInputFilter 的 onPointerEvent:
internal class SuspendingPointerInputFilter(
override val viewConfiguration: ViewConfiguration,
density: Density = Density(1f)
) : PointerInputFilter(),PointerInputModifier,PointerInputScope,Density by density {
private val pointerHandlers = mutableVectorOf<PointerEventHandlerCoroutine<*>>()
...
// 1、發(fā)布者會(huì)調(diào)用該方法來(lái)創(chuàng)建一個(gè)協(xié)程,并添加到 pointerHandlers 集合中
override suspend fun <R> awaitPointerEventScope(
block: suspend AwaitPointerEventScope.() -> R
): R = suspendCancellableCoroutine { continuation ->
val handlerCoroutine = PointerEventHandlerCoroutine(continuation)
synchronized(pointerHandlers) {
pointerHandlers += handlerCoroutine
block.createCoroutine(handlerCoroutine, handlerCoroutine).resume(Unit)
}
continuation.invokeOnCancellation { handlerCoroutine.cancel(it) }
}
....
override fun onPointerEvent(pointerEvent: PointerEvent,pass: PointerEventPass,bounds: IntSize ) {
...
dispatchPointerEvent(pointerEvent, pass)
...
}
// 2、遍歷 pointerHandlers ,觸發(fā) offerPointerEvent 方法
private fun dispatchPointerEvent( pointerEvent: PointerEvent,pass: PointerEventPass) {
forEachCurrentPointerHandler(pass) {
it.offerPointerEvent(pointerEvent, pass)
}
}
...
}
private inner class PointerEventHandlerCoroutine<R>(private val completion: Continuation<R>,) : AwaitPointerEventScope, Density by this@SuspendingPointerInputFilter, Continuation<R> {
private var awaitPass: PointerEventPass = PointerEventPass.Main
...
fun offerPointerEvent(event: PointerEvent, pass: PointerEventPass) {
// 2、判斷事件類(lèi)型是否是 Main 事件
if (pass == awaitPass) {
// 3、判斷 pointerAwaiter 是否為空
pointerAwaiter?.run {
pointerAwaiter = null
resume(event)
}
}
}
...
override suspend fun awaitPointerEvent(
pass: PointerEventPass
): PointerEvent = suspendCancellableCoroutine { continuation ->
awaitPass = pass
// 4、pointerAwaiter 的賦值
pointerAwaiter = continuation
}
}
- 發(fā)布者會(huì)調(diào)用該方法來(lái)創(chuàng)建一個(gè)協(xié)程,并添加到 pointerHandlers 集合中
- 遍歷 pointerHandlers 的 offerPointerEvent 方法發(fā)布事件
- 判斷事件類(lèi)型是否是 Main 事件
- 判斷 pointerAwaiter 是否為空,如果不為空的話(huà),則恢復(fù)掛起函數(shù)
- 掛起函數(shù)的注冊(cè),對(duì) pointerAwaiter 進(jìn)行賦值
然后我們?cè)俑M(jìn) detectTapGestures,看下訂閱者的處理:
suspend fun PointerInputScope.detectTapGestures(
onDoubleTap: ((Offset) -> Unit)? = null,
onLongPress: ((Offset) -> Unit)? = null,
onPress: suspend PressGestureScope.(Offset) -> Unit = NoPressGesture,
onTap: ((Offset) -> Unit)? = null
) = coroutineScope {
...
val channel = Channel<TapGestureEvent>(capacity = Channel.UNLIMITED)
...
launch{
// 1、事件轉(zhuǎn)換后最終結(jié)果,通過(guò) channel 來(lái)阻塞等待結(jié)果的返回,例如會(huì)回調(diào) onDoubleTap、onLongPress 等
}
// 2、遍歷手勢(shì)
forEachGesture {
// 3、調(diào)用 SuspendingPointerInputFilter 的 awaitPointerEventScope 方法,創(chuàng)建并注冊(cè)個(gè)協(xié)程
awaitPointerEventScope {
// 4、處理最終事件消費(fèi)的地方,然后將事件處理的最終結(jié)果發(fā)送至 channel
translatePointerEventsToChannel( this@coroutineScope,channel,consumeOnlyDownsSignal, consumeAllUntilUpSignal)
}
}
- 事件轉(zhuǎn)換后最終結(jié)果,通過(guò) channel 來(lái)阻塞等待結(jié)果的返回,例如會(huì)回調(diào) onDoubleTap、onLongPress 等
- 遍歷手勢(shì),內(nèi)部會(huì)執(zhí)行 block,并且會(huì)掛起等待所有的 Final 事件結(jié)束
- 調(diào)用 SuspendingPointerInputFilter 的 awaitPointerEventScope 方法,創(chuàng)建啟動(dòng)并注冊(cè)個(gè)協(xié)程
- 處理最終事件消費(fèi)的地方,然后將事件處理的最終結(jié)果發(fā)送至 channel
現(xiàn)在我們也大致理解了整體過(guò)程,這里還是通過(guò)繪制圖來(lái)總結(jié),避免代碼太多干擾思路:

由于內(nèi)容篇幅太長(zhǎng),這里只對(duì) down 事件進(jìn)行講解,進(jìn)入 translatePointerEventsToChannel:
private suspend fun AwaitPointerEventScope.translatePointerEventsToChannel(
scope: CoroutineScope,
channel: SendChannel<TapGestureEvent>,
detectDownsOnly: State<Boolean>,
consumeAllUntilUp: MutableState<Boolean>
) {
...
// 1、判斷事件有無(wú)消費(fèi),如果沒(méi)有消費(fèi)的話(huà)則進(jìn)入
else if (event.changes.fastAll { it.changedToDown() }) {
// 2、從 event 中取出事件
val change = event.changes[0]
// 3、消費(fèi) down 事件,其實(shí)就是設(shè)置 consumed.downChange = true
change.consumeDownChange()
// 4、將 down 結(jié)果通過(guò) channel 發(fā)送出去
channel.trySend(Down(change.position, change.uptimeMillis))
}
...
}
- 判斷事件有無(wú)消費(fèi),如果沒(méi)有消費(fèi)的話(huà)則進(jìn)入
- 從 PointerEvent 中取出事件
- 消費(fèi) down 事件,其實(shí)就是設(shè)置 consumed.downChange = true
- 將 down 結(jié)果通過(guò) channel 發(fā)送出去
消費(fèi) down 事件時(shí)標(biāo)記 downChage 為 true 很重要,因?yàn)槲覀兊?pointerInputFilter 有 2 個(gè),并且在處理 Main 事件時(shí),是從子組件往父組件開(kāi)始遍歷,也即子組件會(huì)先消費(fèi)事件,在消費(fèi)了事件之后,遍歷到父組件時(shí),則進(jìn)入不了這個(gè)判斷,也就不處理。
2、AndroidView 組件事件分發(fā)分析
通過(guò)上面的分析知道,Compose 組件是通過(guò) SuspendingPointerInputFilter 實(shí)現(xiàn)事件的處理,那 AndroidView 組件是怎么分發(fā)的呢?繼續(xù)貼一下之前的圖:

我們可以直接看下 AndroidViewHolder,在返回的 layoutNode 中,有預(yù)設(shè)一個(gè) pointerFilter:
val layoutNode: LayoutNode = run {
// Prepare layout node that proxies measure and layout passes to the View.
val layoutNode = LayoutNode()
val coreModifier = Modifier
.pointerInteropFilter(this)
....
layoutNode.modifier = modifier.then(coreModifier)
進(jìn)入 pointerInteropFilter 查看代碼:
@ExperimentalComposeUiApi
internal fun Modifier.pointerInteropFilter(view: AndroidViewHolder): Modifier {
val filter = PointerInteropFilter()
filter.onTouchEvent = { motionEvent ->
// 1、分發(fā)事件
view.dispatchTouchEvent(motionEvent)
}
...
return this.then(filter)
}
AndroidView 的 pointFilter 實(shí)現(xiàn)是 PointerInteropFilter,并且,我們看到了很熟悉的 dispatchTouchEvent 代碼,在 PointerInteropFilter 中會(huì)回調(diào) onTouchEvent,我們看下分發(fā)事件時(shí),響應(yīng)的 PointerInteropFilter.onPointerEvent 方法
override fun onPointerEvent(
pointerEvent: PointerEvent,
pass: PointerEventPass,
bounds: IntSize ) {
...
if (state !== DispatchToViewState.NotDispatching) {
if (pass == PointerEventPass.Initial && dispatchDuringInitialTunnel) {
// 1、分發(fā)事件
dispatchToView(pointerEvent)
}
...
}
private fun dispatchToView(pointerEvent: PointerEvent) {
,,,
val changes = pointerEvent.changes
if (changes.fastAny { it.anyChangeConsumed() }) {
// 處理 cancel 事件
...
} else {
// 2、將 pointerEvent 轉(zhuǎn)成 Android 的 MotionEvent 對(duì)象
pointerEvent.toMotionEventScope(
...
) { motionEvent ->
if (motionEvent.actionMasked == MotionEvent.ACTION_DOWN) {
// 3、觸發(fā) onTouch 回調(diào)
state = if (onTouchEvent(motionEvent)) {
...
} else {
onTouchEvent(motionEvent)
}
}
...
- 判斷時(shí)間狀態(tài)
- 將 pointerEvent 轉(zhuǎn)成 Android 的 MotionEvent 對(duì)象
- 觸發(fā) onTouch 回調(diào),這時(shí)候就會(huì)回調(diào) view.dispatchTouchEvent(motionEvent) 方法
總結(jié)
至此,Compose 的事件分發(fā)流程已梳理完畢。其實(shí),里面還有很多細(xì)節(jié)點(diǎn)還是沒(méi)有講解清楚,但止于篇幅太長(zhǎng),后面再重新開(kāi)篇梳理細(xì)節(jié)點(diǎn)