Jetpack Compose’da Hareketler Nasıl Çalışır | Sherry Yuan tarafından

Jetpack Compose'da Hareketler Nasıl Çalışır |  Sherry Yuan tarafından
fotoğrafı çeken Edvard Alexander Rolvaag üzerinde Sıçramayı kaldır

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. Modifiers 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 Modifiers.

nasıl MotionEventAndroid çerçevesinden s tercüme Modifierkaputun 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 MotionEvents Oluşturmak için jestler olur. Bir örneğini kullanır MotionEventAdapter dönüştürmek MotionEvents Oluştur’a PointerInputEvents, 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 MotionEventyolu ş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:

  1. PointerEventPass.Initial: Olay ağaçta atadan toruna doğru ilerler ve ebeveynlerin çocuklarından önce bazı hareketleri yapmasına izin verir.
  2. 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.
  3. 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. Composablehalletmek 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 Modifierarama 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:

  1. fastAny ve fastAll şuradan androidx.compose.ui:ui-util paket, bu nedenle içinde bir bağımlılık olarak dahil edilmelidir build.gradle.
  2. İçinde detectTapUnconsumed()kullanırız awaitFirstDown(requireUnconsumed = false) onun yerine requireUnconsumed = true tüm olayları aldığımızdan emin olmak için orijinal uygulamada olduğu gibi.
  3. biz aramayız upOrCancel.consumeDownChange() böylece olay tüketildi olarak işaretlenmez.
  4. waitForUpOrCancellationInitial() ile hemen hemen aynıdır waitForUpOrCancellation() çağırması dışında Compose API’sinde verilen awaitPointerEvent(PointerEventPass.Initial) onun yerine awaitPointerEvent(PointerEventPass.Main)olayları Ana geçişte tüketilmeden önce almak için.
  5. 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 Modifiers 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:

  1. awaitFirstDownOnPass() içinde bir iç sınıftır TapGestureDetector.ktbu yüzden uzantı işlevlerimizin erişmesine izin vermek için uygulamayı kendi kod tabanımıza kopyalamamız gerekiyor.
  2. 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 ModifierBu 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 ❤️

Bir cevap yazın

E-posta hesabınız yayımlanmayacak.