
Bu makale, Android Touch System serimin beşinci ve son bölümüdür ve Jetpack Compose hiyerarşisinde işaretçi olaylarının nasıl çalıştığını, Compose’da hareket algılamanın bazı sınırlamalarını ve nasıl özel oluşturulacağını kapsar. Modifier
s sınırlamaları aşmak için. Okuyucuların MotionEvents hakkında biraz bilgi sahibi olduğunu varsayar. Bunlara aşina değilseniz, lütfen kontrol edin Bölüm 1: Dokunma İşlevleri ve Görünüm Hiyerarşisi. 4. Bölüm jest işlemenin üzerinden geçer Modifier
s.
nasıl MotionEvent
Android çerçevesinden s tercüme Modifier
kaputun altında mı?
Compose dünyasına ya bir ComponentActivity
‘s setContent()
uzatma işlevi veya ComposeView
‘s setContent()
işlev. İkisi birden setContent()
kullanımı AndroidComposeView
içeriği görüntülemek için.
AndroidComposeView.dispatchTouchEvent()
geçersiz kılan View.dispatchTouchEvent()
dönüşümün nereden geldiği MotionEvent
s Oluşturmak için jestler olur. Bir örneğini kullanır MotionEventAdapter
dönüştürmek MotionEvent
s Oluştur’a PointerInputEvent
s, ardından olayı Compose dünyasına iletir. Bağlı olmak View.dispatchTouchEvent()
‘nin API sözleşmesi, bir Boolean
olayın bir Composable tarafından tüketilip tüketilmediğini Android çerçevesine bildirmek için. false döndürürse, AndroidComposeView
‘nin ebeveyn görüşleri bunu normal bir şekilde halledebilir MotionEvent
.
bu MotionEvent
yolu şuna benzer:
→ Activity.dispatchTouchEvent()
→ SomeViewGroup.dispatchTouchEvent()
→ ComposeView.dispatchTouchEvent()
→ AndroidComposeView.dispatchTouchEvent()
→ Dünyayı oluştur
Şimdi Compose tarafında ne olduğunu görelim.
bu PointerInputEvent
daha önce oluşturulan bir parametre olarak iletilir PointerInputEventProcessor.process()
. process()
olay türünü (yani yukarı veya aşağı veya hareket) belirlemek için önceki işaretçi olayı ile geçerli olan arasındaki değişikliği hesaplar. Nihayet, process()
Composable ağacının kök düğümünde bir isabet testi gerçekleştirir, ardından aynı testi alt düğümlerde yinelemeli olarak gerçekleştirir ve PointerInputEvent
testi geçen düğümlere.
Geleneksel Görünümlere benzer şekilde, Oluşturma Kullanıcı Arayüzü, ağaç benzeri bir hiyerarşide Birleştirilebilirlerden oluşur. Şimdi pointer olaylarının hiyerarşiden nasıl geçtiğine bir göz atalım. Neyse ki, Görünüm sisteminde nasıl çalıştığından daha sezgisel!
Olaylar, hiyerarşide yakalanan üç geçişte geçer. PointerEventPass
Sıralama:
PointerEventPass.Initial
: Olay ağaçta atadan toruna doğru ilerler ve ebeveynlerin çocuklarından önce bazı hareketleri yapmasına izin verir.PointerEventPass.Main
: Olay, ağaçta soydan ataya geri döner. Bu, çoğu hareket işlemenin gerçekleştiği birincil geçiştir.PointerEventPass.Final
: Etkinlik, ana geçiş sırasında çocukların olayın hangi yönlerinin onun soyundan gelenler tarafından tüketildiğini öğrenebilecekleri yerde, atadan nesile tekrar iner.
hepsi için Modifiers
Compose API’sinde olay işleme, Main
geçmek. Bir Yaprak Oluşturulabilir Modifier
belirli bir jest için ayarlanmışsa, hareketi tüketir ve başka bir şey kullanmaz. Composable
halletmek olsun. Eğer bir Modifier
ayarlandığında, hareket, onu idare edebilecek bir Composable’a ulaşana kadar köke doğru gönderilir.
Hiyerarşinin aynı düzeyinde çakışan iki oluşturulabilir işlev varsa, daha sonra çağrılan, daha önce çağrılan işlevin üzerine çizilir ve önce herhangi bir hareketi tüketir.
Örneğin, aşağıdaki kod verildi:
Box(modifier = Modifier.align(Alignment.Center) {
Box(
modifier = Modifier
.size(200.dp)
.background(Color.LightGray)
.clickable { Log.d(TAG, “Light gray box clicked”) }
)
Box(
modifier = Modifier
.size(100.dp)
.background(Color.DarkGray)
.clickable { Log.d(TAG, “Dark gray box clicked”) }
)
}
Kutuların üst üste geldiği yerde bir tıklama varsa, “Koyu gri kutu tıklandı” günlüğe kaydedilir.
Varsayılan olarak, işlevler kod tabanında göründükleri sırayla çağrılır, ancak şunu kullanabiliriz:Modifier.zIndex()
aynı ebeveynin çocukları için çizim sırasını açıkça kontrol etmek; daha yüksek olan çocuk zIndex
daha sonra çizilecek ve önce hareketlere erişecek.
Oluştur hareketinin bir sınırlaması Modifier
Şu anda, olay günlüğü gibi kullanım durumları için gerekli olan belirli hareketleri yutmadan algılamanın bir yolu yoktur. Bunun nedeni, tüm Modifier
arama consumeDownChange()
, etkinliği tüketilmiş olarak işaretler. Bunun için bir geçici çözüm, yeni PointerInputScope
mevcut olanlara dayalı genişletme işlevleri.
İşte, ağırlıklı olarak buna dayalı olarak, dokunma hareketlerini tüketmeden algılamak için bir uzantı işlevi. StackOverflow gönderisi. Mevcutlara çok benziyor detectTapGesture()
uygulama.
suspend fun PointerInputScope.detectTapUnconsumed(
onTap: ((Offset) -> Unit)
) {
val pressScope = PressGestureScopeImpl(this)
forEachGesture {
coroutineScope {
pressScope.reset()
awaitPointerEventScope {
awaitFirstDown(requireUnconsumed = false).also {
it.consumeDownChange()
}
val up = waitForUpOrCancellationInitial()
if (up == null) {
pressScope.cancel()
} else {
pressScope.release()
onTap(up.position)
}
}
}
}
}suspend fun AwaitPointerEventScope.waitForUpOrCancellationInitial(): PointerInputChange? {
while (true) {
val event = awaitPointerEvent(PointerEventPass.Initial)
if (event.changes.fastAll { it.changedToUp() }) {
return event.changes[0]
}
if (event.changes.fastAny {
it.consumed.downChange ||
it.isOutOfBounds(size,extendedTouchPadding)
}
) {
return null
} // Check for cancel by position consumption.
// We can look on the Final pass of the existing
// pointer event because it comes after the Main
// pass we checked above.
val consumeCheck = awaitPointerEvent(PointerEventPass.Final)
if (consumeCheck.changes.fastAny {
it.positionChangeConsumed()
}
) {
return null
}
}
}
Vurgulamak istediğim birkaç şey var:
fastAny
vefastAll
şuradanandroidx.compose.ui:ui-util
paket, bu nedenle içinde bir bağımlılık olarak dahil edilmelidirbuild.gradle
.- İçinde
detectTapUnconsumed()
kullanırızawaitFirstDown(requireUnconsumed = false)
onun yerinerequireUnconsumed = true
tüm olayları aldığımızdan emin olmak için orijinal uygulamada olduğu gibi. - biz aramayız
upOrCancel.consumeDownChange()
böylece olay tüketildi olarak işaretlenmez. waitForUpOrCancellationInitial()
ile hemen hemen aynıdırwaitForUpOrCancellation()
çağırması dışında Compose API’sinde verilenawaitPointerEvent(PointerEventPass.Initial)
onun yerineawaitPointerEvent(PointerEventPass.Main)
olayları Ana geçişte tüketilmeden önce almak için.PressGestureScopeImpl
özel bir sınıftır, bu nedenle uzantı işlevlerimizin ona erişmesine izin vermek için uygulamayı kendi kod tabanımıza kopyalamamız gerekir.
Mevcut bir başka sınırlama Modifier
s hiçbiri ebeveyn Composables’ın olayları çocuklarından önce kesmesine ve tüketmesine izin vermez. Bunun için de bir extension function oluşturabiliriz. Kod dayanmaktadırbu StackOverflow gönderisi.
suspend fun PointerInputScope.detectTapInitialPass(
onTap: ((Offset) -> Unit)
) {
val pressScope = PressGestureScopeImpl(this)
forEachGesture {
coroutineScope {
pressScope.reset()
awaitPointerEventScope {
awaitFirstDownOnPass(
pass = PointerEventPass.Initial,
requireUnconsumed = false
).also { it.consumeDownChange() }
val up = waitForUpOrCancellationInitial()
if (up == null) {
pressScope.cancel()
} else {
up.consumeDownChange()
pressScope.release()
onTap(up.position)
}
}
}
}
}
Burada dikkat edilmesi gerekenler:
awaitFirstDownOnPass()
içinde bir iç sınıftırTapGestureDetector.kt
bu yüzden uzantı işlevlerimizin erişmesine izin vermek için uygulamayı kendi kod tabanımıza kopyalamamız gerekiyor.- Farklı
detectTapUnconsumed()
up olayını tüketiriz, böylece alt Birleştirilebilirlere geçmez.
Bunu Compose 1.1.1 kararlı sürümüne dayanarak yazdım ve Modifier
Bu sınırlamaları ele alan s, gelecekteki bir sürümde API’ye eklenir.
Umarım bu makaleyi faydalı bulmuşsunuzdur! Bağlantıları diğer Android Dokunmatik Sistem makalelerine bırakmak:
Sayesinde Russel ve Kelvin değerli düzenlemeleri ve geri bildirimleri için ❤️