手勢(shì)事件消費(fèi)
在了解手勢(shì)事件分發(fā)之后,我們接下來學(xué)習(xí)如何完成手勢(shì)事件消費(fèi),我們看到?awaitPointerEvent
?返回了一個(gè)?PointerEvent
?實(shí)例。
actual data class PointerEvent internal constructor(
? ? actual val changes: List<PointerInputChange>,
? ? internal val motionEvent: MotionEvent?
)
從 PointerEvent 類的聲明中可以看到包含了兩個(gè)屬性 changes 與 motionEvent。
motionEvent:實(shí)際上就是傳統(tǒng) View 系統(tǒng)中的 MotionEvent,由于被聲明 internal ,說明官方并不希望我們直接拿來使用。
changes:其中包含了一次手勢(shì)交互中所有手指的交互信息。在多指操作時(shí),利用?
changes
?可以輕松定制多指手勢(shì)處理。
可以看出單指交互的完整信息被封裝在了一個(gè) PointerInputChange 實(shí)例中,接下來我們看看 PointerInputChange 提供了哪些手勢(shì)信息。
class PointerInputChange(
? ? val id: PointerId, // 手指Id
? ? val uptimeMillis: Long, // 當(dāng)前手勢(shì)事件的時(shí)間戳
? ? val position: Offset, // 當(dāng)前手勢(shì)事件相對(duì)組件左上角的位置
? ? val pressed: Boolean, // 當(dāng)前手勢(shì)是否按下
? ? val previousUptimeMillis: Long, // 上一次手勢(shì)事件的時(shí)間戳
? ? val previousPosition: Offset, // 上一次手勢(shì)事件相對(duì)組件左上角的位置
? ? val previousPressed: Boolean, // 上一次手勢(shì)是否按下
? ? val consumed: ConsumedData, // 當(dāng)前手勢(shì)是否已被消費(fèi)
? ? val type: PointerType = PointerType.Touch // 手勢(shì)類型(鼠標(biāo)、手指、手寫筆、橡皮)?
)
利用這些豐富的手勢(shì)信息,我們可以在上層定制實(shí)現(xiàn)各類復(fù)雜的交互手勢(shì)。
可以看到其中的 consumed 成員記錄著該事件是否已被消費(fèi),我們可以使用?PointerInputChange
?提供的 consume 系列 API 來修改這個(gè)手勢(shì)事件的消費(fèi)標(biāo)記。
changedToDown是否已經(jīng)按下(按下手勢(shì)已消費(fèi)則返回false)
changedToDownIgnoreConsumed是否已經(jīng)按下(忽略按下手勢(shì)已消費(fèi)標(biāo)記)
changedToUp是否已經(jīng)抬起(按下手勢(shì)已消費(fèi)則返回false)
changedToUpIgnoreConsumed是否已經(jīng)抬起(忽略按下手勢(shì)已消費(fèi)標(biāo)記)
positionChanged是否位置發(fā)生了改變(移動(dòng)手勢(shì)已消費(fèi)則返回false)
positionChangedIgnoreConsumed是否位置發(fā)生了改變(忽略已消費(fèi)標(biāo)記)
positionChange位置改變量(移動(dòng)手勢(shì)已消費(fèi)則返回Offset.Zero)
positionChangeIgnoreConsumed位置改變量(忽略移動(dòng)手勢(shì)已消費(fèi)標(biāo)記)
positionChangeConsumed當(dāng)前移動(dòng)手勢(shì)是否已被消費(fèi)
anyChangeConsumed當(dāng)前按下手勢(shì)或移動(dòng)手勢(shì)是否有被消費(fèi)
consumeDownChange消費(fèi)按下手勢(shì)
consumePositionChange消費(fèi)移動(dòng)手勢(shì)
consumeAllChanges消費(fèi)按下與移動(dòng)手勢(shì)
isOutOfBounds當(dāng)前手勢(shì)是否在固定范圍內(nèi)
前面提到,我們可以通過設(shè)置?PointerEventPass
?來定制嵌套組件間手勢(shì)事件分發(fā)順序。假設(shè)分發(fā)流程中組件 A 預(yù)先獲取到了手勢(shì)信息并進(jìn)行消費(fèi),手勢(shì)事件仍然會(huì)被之后的組件 B 獲取得到。組件 B 在使用?positionChange
?獲取的偏移值時(shí)會(huì)返回?Offset.ZERO
,這是因?yàn)榇藭r(shí)該手勢(shì)事件已被標(biāo)記為已消費(fèi)的狀態(tài)。當(dāng)然組件 B 也可以通過 IgnoreConsumed 系列 API 突破已消費(fèi)標(biāo)記的限制獲取到手勢(shì)信息。
我們?nèi)匀煌ㄟ^前面使用的嵌套組件示例子來看看手勢(shì)事件的消費(fèi)。我們的嵌套組件中第一層組件使用 Inital,第二層組件使用 Final ,第三層組件使用 Main。

我們?cè)诘谌龑咏M件的手勢(shì)事件監(jiān)聽中進(jìn)行消費(fèi),因?yàn)槲覀冎朗謩?shì)事件會(huì)交由第一層, 再交由第三層,最后交由第二層。第三層組件處于本次手勢(shì)分發(fā)流程的中間位置。
當(dāng)我們?cè)诘谌龑咏M件消費(fèi)了?ACTION_DOWN
?后,之后處理的第二層組件接收的手勢(shì)事件仍是被標(biāo)記為消費(fèi)狀態(tài)的。
@Composable
fun ConsumeDemo() {
? ? Box(
? ? ? ? contentAlignment = Alignment.Center,
? ? ? ? modifier = Modifier
? ? ? ? ? ? .fillMaxSize()
? ? ? ? ? ? .pointerInput(Unit) {
? ? ? ? ? ? ? ? awaitPointerEventScope {
? ? ? ? ? ? ? ? ? ? var event = awaitPointerEvent(PointerEventPass.Initial)
? ? ? ? ? ? ? ? ? ? Log.d(TAG, "first layer, downChange: ${event.changes[0].consumed.downChange}")
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ) {
? ? ? ? Box(
? ? ? ? ? ? contentAlignment = Alignment.Center,
? ? ? ? ? ? modifier = Modifier
? ? ? ? ? ? ? ? .size(400.dp)
? ? ? ? ? ? ? ? .background(Color.Blue)
? ? ? ? ? ? ? ? .pointerInput(Unit) {
? ? ? ? ? ? ? ? ? ? awaitPointerEventScope {
? ? ? ? ? ? ? ? ? ? ? ? var event = awaitPointerEvent(PointerEventPass.Final)
? ? ? ? ? ? ? ? ? ? ? ? Log.d(TAG, "second layer, downChange: ${event.changes[0].consumed.downChange}")
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? }
? ? ? ? ) {
? ? ? ? ? ? Box(
? ? ? ? ? ? ? ? Modifier
? ? ? ? ? ? ? ? ? ? .size(200.dp)
? ? ? ? ? ? ? ? ? ? .background(Color.Green)
? ? ? ? ? ? ? ? ? ? .pointerInput(Unit) {
? ? ? ? ? ? ? ? ? ? ? ? awaitPointerEventScope {
? ? ? ? ? ? ? ? ? ? ? ? ? ? var event = awaitPointerEvent()
? ? ? ? ? ? ? ? ? ? ? ? ? ? event.changes[0].consumeDownChange() // 消費(fèi)手勢(shì)事件
? ? ? ? ? ? ? ? ? ? ? ? ? ? Log.d(TAG, "third layer, downChange: ${event.changes[0].consumed.downChange}")
? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? )
? ? ? ? }
? ? }
}
介紹完 Compose 的手勢(shì)事件分發(fā)與消費(fèi),想必大家已經(jīng)對(duì)?awaitPointerEvent
?這個(gè)低級(jí)別基礎(chǔ)手勢(shì)監(jiān)聽 API 已經(jīng)有了足夠的了解。然而在實(shí)際場(chǎng)景中我們還是應(yīng)該更多的依賴上層封裝完善的 API,因?yàn)楫?dāng)手勢(shì)邏輯變得越來越復(fù)雜時(shí),維護(hù)手勢(shì)交互處理邏輯的難度也會(huì)越來越大。接下來我們來介紹?AwaitPointerEventScope
?中基于?awaitPointerEvent
?實(shí)現(xiàn)的幾個(gè)常用手勢(shì)監(jiān)聽掛起方法。