Kullanıcı arayüzü bileşenleri cihaz kullanıcısına ne kadar geri bildirim verildiğini yanıt verebilme olanağı sunar. Her bileşenin kendine özgü yanıt verme yöntemi vardır etkileşimleridir, bu da kullanıcının etkileşimlerinin neler yaptığını anlamasına yardımcı olur. Örneğin, Örneğin, kullanıcı bir cihazın dokunmatik ekranındaki bir düğmeye dokunursa düğme, bir şekilde değişmesi olasıdır. Bu değişiklik kullanıcının düğmeye dokunduğunu bilmesini sağlar. Kullanıcı önce parmaklarını düğmeden uzağa sürüklemelerini Aksi takdirde düğme etkinleşir.
ziyaret edin.Hareketler oluşturma dokümanı nasıl Oluşturma bileşenleri, işaretçi hareketlerini ve alt öğeleri gösteren alt düzey işaretçi etkinliklerini tıklama sayısı. Oluşturulan ilk ayar, bu düşük düzeyli etkinlikleri soyutlayarak daha üst düzey etkileşimler. Örneğin, bir dizi işaretçi etkinlik bir düğmeye basıp bırakın. Bu üst düzey soyutlamaları anlamak kullanıcı arayüzünüzün kullanıcıya nasıl tepki vereceğini özelleştirmenize yardımcı olur. Örneğin, ekip arkadaşlarınızın kullanıcı etkileşimde bulunduğunda bileşenin görünümünün ya da yalnızca bu kullanıcı işlemlerinin kaydını tutmak isteyebilirsiniz. Bu dokümanı, standart kullanıcı arayüzü öğelerini değiştirmek için gereken bilgileri sağlar. veya kendi şablonunuzu tasarlayın.
Etkileşimler
Birçok durumda, Oluşturma bileşeninizin yalnızca bu bileşenin nasıl
Kullanıcı etkileşimlerini yorumlamak. Örneğin Button
,
Modifier.clickable
kullanıcının düğmeyi tıklayıp tıklamadığını belirlemek için kullanılır. Standart bir
uygulamanıza eklemek için düğmenin onClick
kodunu tanımlayabilir ve
Modifier.clickable
, uygun olduğunda bu kodu çalıştırır. Yani bir projeyi
kullanıcının ekrana dokunduğunu veya düğmeli bir düğmeyle mi
klavye; Modifier.clickable
, kullanıcının bir tıklama gerçekleştirdiğini anlar ve
onClick
kodunuzu çalıştırarak yanıt verir.
Ancak kullanıcı arayüzü bileşeninizin kullanıcı davranışına verdiği yanıtı özelleştirmek isterseniz işin mutfağını öğrenmeniz gerekebilir. Bu bölümde, bazılarını anlatacağım.
Bir kullanıcı, kullanıcı arayüzü bileşeniyle etkileşime geçtiğinde sistem bu bileşenin davranışını temsil eder
bir dizi oluşturarak
Interaction
etkinlikler. Örneğin, kullanıcı bir düğmeye dokunduğunda, düğme
PressInteraction.Press
.
Kullanıcı parmağını düğmenin içine sokarsa, bu işlem
PressInteraction.Release
düğme, tıklamanın sona erdiğini bilmesini sağlar. Diğer yandan,
kullanıcı parmağını düğmenin dışına sürüklediğinde parmağını kaldırdığında
oluşturur
PressInteraction.Cancel
değeri gösterilir.
Bu etkileşimler bildirimsizdir. Yani, bu alt düzey etkileşimler Kullanıcı işlemlerinin anlamını yorumlamayı amaçlamayan etkinlikler veya tıklayın. Ayrıca hangi kullanıcı işlemlerinin, öncelikli diğer işlemler.
Bu etkileşimler genellikle birer başlangıcı ve sonu olan ikili gruplar halinde gelir. İkinci
etkileşiminde ilkine referans verilir. Örneğin, bir kullanıcı
Kullanıcı bir düğmeye dokunduktan sonra parmağını kaldırırsa dokunduğunda
PressInteraction.Press
kullanıcı etkileşimi sağlar ve sürüm,
PressInteraction.Release
;
Release
, ilk adımı tanımlayan bir press
özelliğine sahiptir
PressInteraction.Press
.
Belirli bir bileşene ilişkin etkileşimleri, söz konusu bileşene ait
InteractionSource
InteractionSource
, Kotlin'in üzerine kurulmuştur
akışlar sayesinde, etkileşimleri aynı şekilde toplayabilirsiniz.
diğer akışlarla çalışırsınız. Bu tasarım kararı hakkında daha fazla bilgi için
Etkileşimleri Aydınlatan blog yayınını inceleyin.
Etkileşim durumu
Bileşenlerinizin yerleşik işlevselliğini ayrıca şunları yaparak da genişletmek isteyebilirsiniz:
kendiniz izlemelisiniz. Örneğin, bir düğmeyi tıklayarak
renk değiştiriliyor. Etkileşimleri izlemenin en basit yolu
uygun etkileşim durumunu gözlemleyin. InteractionSource
bir numara sunuyor
farklı etkileşim durumlarını ortaya koyan bir dizi yöntem içerir. Örneğin,
basılı tutup tutmadığınızı görmek için,
InteractionSource.collectIsPressedAsState()
yöntem:
val interactionSource = remember { MutableInteractionSource() } val isPressed by interactionSource.collectIsPressedAsState() Button( onClick = { /* do something */ }, interactionSource = interactionSource ) { Text(if (isPressed) "Pressed!" else "Not pressed") }
Compose, collectIsPressedAsState()
'ın yanı sıra aşağıdakileri de sunar:
collectIsFocusedAsState()
, collectIsDraggedAsState()
ve
collectIsHoveredAsState()
. Bu yöntemler aslında kolaylık
alt düzey InteractionSource
API'leri temel alınarak oluşturulur. Bazı durumlarda,
alt düzey işlevleri doğrudan kullanmak istiyorsanız.
Örneğin, bir düğmeye basılıp basılmadığını bilmeniz gerektiğini ve
aynı zamanda sürüklenip sürüklenmediğini de kontrol edebilirsiniz. collectIsPressedAsState()
ürününün her ikisini de kullanıyorsanız
ve collectIsDraggedAsState()
, Compose'da pek çok yinelenen iş var ve
Tüm etkileşimleri doğru sırayla alacağınızın garantisi yoktur. Örneğin,
bu gibi durumlarda doğrudan paydaşlarla çalışmayı
InteractionSource
Etkileşimlerin izlenmesiyle ilgili daha fazla bilgi için
daha fazla bilgi edinmek için InteractionSource
ile çalışma başlıklı makaleye göz atın.InteractionSource
Aşağıdaki bölümde,
Sırasıyla InteractionSource
ve MutableInteractionSource
.
Interaction
tüket ve yay
InteractionSource
, Interactions
salt okunur akışını temsil eder; bu değil
InteractionSource
için Interaction
yayımlanabilir. Yayınlanacak
Interaction
s, bir MutableInteractionSource
kullanmanız gerekir;
InteractionSource
.
Değiştiriciler ve bileşenler Interactions
öğesini kullanabilir, yayabilir, tüketebilir ve yayabilir.
Aşağıdaki bölümlerde, her ikisinden de etkileşimlerin nasıl tüketileceği ve
değiştiricileri ve bileşenleri hakkında bilgi edindiniz.
Kullanma değiştiricisi örneği
Odaklanmış durum için kenarlık çizen bir değiştiricide, tek yapmanız gereken
Interactions
, böylece bir InteractionSource
kabul edebilirsiniz:
fun Modifier.focusBorder(interactionSource: InteractionSource): Modifier { // ... }
İşlev imzasından bu değiştiricinin bir tüketici olduğu açıkça anlaşılıyor.
Interaction
öğelerini kullanabilir ancak yayınlayamaz.
Prodüksiyon değiştirici örneği
Modifier.hoverable
gibi fareyle üzerine gelme etkinliklerini işleyen bir değiştirici için:
Interactions
özelliğini yayınlamalı ve MutableInteractionSource
öğesini
parametresini kullanabilirsiniz:
fun Modifier.hover(interactionSource: MutableInteractionSource, enabled: Boolean): Modifier { // ... }
Bu değiştirici bir üreticidir; sağlanan
Fareyle üzerine gelindiğinde HoverInteractions
özelliğini yayınlamak için MutableInteractionSource
veya
elbette.
Kullanan ve üreten bileşenler
Malzeme Button
gibi üst düzey bileşenler, hem üretici hem de
yardımcı olur. Giriş ve odaklanma etkinliklerini işler, ayrıca görünümlerini değiştirirler
(ör. dalga göstermek veya hareketlerini canlandırmak)
rakım. Sonuç olarak, MutableInteractionSource
hatasını doğrudan
parametresini kullanabilirsiniz. Böylece, hatırlanan kendi örneğinizi sağlayabilirsiniz:
@Composable fun Button( onClick: () -> Unit, modifier: Modifier = Modifier, enabled: Boolean = true, // exposes MutableInteractionSource as a parameter interactionSource: MutableInteractionSource? = null, elevation: ButtonElevation? = ButtonDefaults.elevatedButtonElevation(), shape: Shape = MaterialTheme.shapes.small, border: BorderStroke? = null, colors: ButtonColors = ButtonDefaults.buttonColors(), contentPadding: PaddingValues = ButtonDefaults.ContentPadding, content: @Composable RowScope.() -> Unit ) { /* content() */ }
Bu sayede, özelliği kaldırabilir
MutableInteractionSource
bileşenin dışına çıkarıp tüm
Bileşen tarafından üretilen Interaction
'ler. Bunu kontrol etmek için
veya kullanıcı arayüzündeki başka bir bileşenin görünümünü değiştirebilirsiniz.
Kendi etkileşimli üst düzey bileşenlerinizi oluşturuyorsanız
MutableInteractionSource
değerini bu şekilde sunabilirsiniz. Ayrıca
Eskalasyonla ilgili en iyi uygulamaları izleyerek
bu verileri okumayı ve
Tıpkı diğer herhangi bir bileşenin görsel durumunu,
durumu (etkin durum gibi) okunabilir ve kontrol edilebilir.
Compose, katmanlı bir mimari yaklaşımı izler.
Böylece üst seviye Malzeme bileşenleri, temel binanın üzerine inşa edilir
dalgaları ve diğer özellikleri kontrol etmek için ihtiyaç duydukları Interaction
'leri üreten bloklar
görsel efektleri kullanın. Temel kitaplığı, üst düzey etkileşim değiştiricileri sunar
Örneğin Modifier.hoverable
, Modifier.focusable
ve
Modifier.draggable
.
Fareyle öğelerin üzerine gelerek etkinliklere yanıt veren bir bileşen oluşturmak için
Modifier.hoverable
ve parametre olarak bir MutableInteractionSource
iletin.
Bileşenin üzerine gelindiğinde HoverInteraction
sesi yayılır ve
bileşenin görünümünü değiştirmek için bunu kullanın.
// This InteractionSource will emit hover interactions val interactionSource = remember { MutableInteractionSource() } Box( Modifier .size(100.dp) .hoverable(interactionSource = interactionSource), contentAlignment = Alignment.Center ) { Text("Hello!") }
Ayrıca bu bileşeni odaklanılabilir hale getirmek için Modifier.focusable
ekleyip
parametre ile aynı MutableInteractionSource
. Şimdi hem
HoverInteraction.Enter/Exit
ve FocusInteraction.Focus/Unfocus
yayınlandı
aynı MutableInteractionSource
ile biçimlendirebilir ve
Aynı yerde her iki etkileşim türü için de görünüm:
// This InteractionSource will emit hover and focus interactions val interactionSource = remember { MutableInteractionSource() } Box( Modifier .size(100.dp) .hoverable(interactionSource = interactionSource) .focusable(interactionSource = interactionSource), contentAlignment = Alignment.Center ) { Text("Hello!") }
Modifier.clickable
daha da yüksek
hoverable
ve focusable
'ye göre daha düşük düzeyli soyutlama (bir bileşen için
dolaylı olarak üzerine gelinebilir ve tıklanabilen bileşenler,
odaklanabillirsiniz. Aşağıdaki özelliklere sahip bir bileşen oluşturmak için Modifier.clickable
kullanabilirsiniz:
fareyle üzerine gelme, odaklanma ve basma gibi etkileşimlerde, daha düşük düzeyde
daha fazla bilgi edineceksiniz. Bileşeninizi de tıklanabilir yapmak isterseniz
hoverable
ve focusable
değerlerini clickable
ile değiştirin:
// This InteractionSource will emit hover, focus, and press interactions val interactionSource = remember { MutableInteractionSource() } Box( Modifier .size(100.dp) .clickable( onClick = {}, interactionSource = interactionSource, // Also show a ripple effect indication = ripple() ), contentAlignment = Alignment.Center ) { Text("Hello!") }
InteractionSource
ile çalışın
Bir bileşenle kurulan etkileşimler hakkında alt düzey bilgilere ihtiyacınız varsa
söz konusu bileşenin InteractionSource
öğesi için standart akış API'leri kullanmanız gerekir.
Örneğin, basın ve sürükleme hareketlerinin bir listesini tutmak istediğinizi
InteractionSource
için etkileşimler. Bu kod işin yarısını yapar,
yeni basın mensupları listeye eklendi:
val interactionSource = remember { MutableInteractionSource() } val interactions = remember { mutableStateListOf<Interaction>() } LaunchedEffect(interactionSource) { interactionSource.interactions.collect { interaction -> when (interaction) { is PressInteraction.Press -> { interactions.add(interaction) } is DragInteraction.Start -> { interactions.add(interaction) } } } }
Ancak, yeni etkileşimleri eklemenin yanı sıra etkileşimi kaldırmanız da gerekir. kullanıcı parmağını tekrar kaldırdığında (örneğin, kullanıcı parmağını tekrar bileşeni). Bunu yapmak kolaydır, çünkü son etkileşimler her zaman ilişkilendirilmiş başlangıç etkileşimine referans vermelidir. Bu kod, Sona eren etkileşimler:
val interactionSource = remember { MutableInteractionSource() } val interactions = remember { mutableStateListOf<Interaction>() } LaunchedEffect(interactionSource) { interactionSource.interactions.collect { interaction -> when (interaction) { is PressInteraction.Press -> { interactions.add(interaction) } is PressInteraction.Release -> { interactions.remove(interaction.press) } is PressInteraction.Cancel -> { interactions.remove(interaction.press) } is DragInteraction.Start -> { interactions.add(interaction) } is DragInteraction.Stop -> { interactions.remove(interaction.start) } is DragInteraction.Cancel -> { interactions.remove(interaction.start) } } } }
Bileşene şu anda basılıp sürüklenmediğini öğrenmek isterseniz
tek yapmanız gereken interactions
alanının boş olup olmadığını kontrol etmektir:
val isPressedOrDragged = interactions.isNotEmpty()
En son etkileşimin ne olduğunu öğrenmek istiyorsanız son etkileşiminize öğesini seçin. Örneğin, Compose dalga uygulaması şu şekildedir: en son etkileşim için kullanılacak uygun durum yer paylaşımını belirler:
val lastInteraction = when (interactions.lastOrNull()) { is DragInteraction.Start -> "Dragged" is PressInteraction.Press -> "Pressed" else -> "No state" }
Tüm Interaction
'ler aynı yapıyı izlediğinden
farklı türlerde kullanıcı etkileşimleri ile çalışırken kodda farklılık gösterir.
aynıdır.
Bu bölümde yer alan örneklerin şunun Flow
öğesini temsil ettiğini unutmayın:
State
kullanan etkileşimler
Böylece, güncellenen değerleri gözlemlemeyi kolaylaştırır.
değer okunması, otomatik olarak yeniden bestelere neden olur. Ancak,
bestenin ön çerçeveden toplu olarak işlenmesini sağlar. Bu, durumda değişiklik olursa ve
aynı çerçeve içinde tekrar değiştiğinde, durumu gözlemleyen bileşenler
değişikliği görün.
Etkileşimler düzenli olarak başlayıp bitebileceği için bu önemlidir
aynı çerçevede. Örneğin, önceki örnek Button
ile kullanıldığında:
val interactionSource = remember { MutableInteractionSource() } val isPressed by interactionSource.collectIsPressedAsState() Button(onClick = { /* do something */ }, interactionSource = interactionSource) { Text(if (isPressed) "Pressed!" else "Not pressed") }
Bir basma işlemi aynı çerçeve içinde başlayıp biterse, metin hiçbir zaman
"Basıldı!". Çoğu durumda bu bir sorun değildir. Bu, kullanıcılara görsel
küçük bir süre ekranda titremeye neden olur ve çok fazla
fark edebilirsiniz. Dalga etkisi veya dalga etkisi
istiyorsanız efekti en azından minimum bir miktarda göstermek isteyebilirsiniz.
otomatik olarak ayarlamayı deneyin. Alıcı:
animasyonları doğrudan toplamanın içinden başlatabilir ve durdurabilirsiniz
lambda yazar. Bu modelin bir örneği de
Animasyonlu kenarlıkla gelişmiş Indication
oluşturma bölümünü tıklayın.
Örnek: Özel etkileşim işleme içeren derleme bileşeni
Girişe yönelik özel bir yanıtla bileşenleri nasıl oluşturacağınızı öğrenmek için örneğini görebilirsiniz. Bu örnekte, düğme yerine görünüşünü değiştirerek baskılara yanıt verir:
Bunun için, Button
tabanlı özel bir composable derleyin ve bu composable'ın
ek icon
parametresini kullanın (bu örnekte, alışveriş sepeti). Siz
kullanıcınıncollectIsPressedAsState()
düğme; bu simgelerle birlikte simgeyi de eklersiniz. Kod şu şekilde görünür:
@Composable fun PressIconButton( onClick: () -> Unit, icon: @Composable () -> Unit, text: @Composable () -> Unit, modifier: Modifier = Modifier, interactionSource: MutableInteractionSource? = null ) { val isPressed = interactionSource?.collectIsPressedAsState()?.value ?: false Button( onClick = onClick, modifier = modifier, interactionSource = interactionSource ) { AnimatedVisibility(visible = isPressed) { if (isPressed) { Row { icon() Spacer(Modifier.size(ButtonDefaults.IconSpacing)) } } } text() } }
Yeni composable'ı şu şekilde kullanabilirsiniz:
PressIconButton( onClick = {}, icon = { Icon(Icons.Filled.ShoppingCart, contentDescription = null) }, text = { Text("Add to cart") } )
Çünkü bu yeni PressIconButton
mevcut Malzemenin üzerine inşa edilmiştir
Button
, kullanıcı etkileşimlerine her zamanki şekilde tepki verir. Kullanıcı
düğmeye bastığında opaklığını normal bir
Malzeme Button
.
Indication
ile yeniden kullanılabilir özel efekt oluşturup uygulayın
Önceki bölümlerde, yanıt olarak bir bileşenin parçasını nasıl değiştireceğinizi
farklı Interaction
'lere gönderebilirsiniz. Örneğin, basıldığında bir simge görüntüleyebilirsiniz. Bu aynı
Bu yaklaşım, bir reklam grubuna sağladığınız parametrelerin değerini değiştirmek için
ya da bileşenin içinde görüntülenen içeriği değiştirebilirsiniz, ancak bu,
yalnızca bileşen bazında geçerlidir. Genellikle, bir uygulama veya tasarım sistemi
durum bilgili görsel efektler için genel bir sisteme sahip olur. Bu efekt,
ve tüm bileşenlere tutarlı bir şekilde
uygulanır.
Bu tür bir tasarım sistemi oluşturuyorsanız, bir bileşeni özelleştirerek bu özelleştirmeyi diğer bileşenlerde yeniden kullanmak, şu nedenlerle:
- Tasarım sistemindeki her bileşen aynı ortak çalışmaya ihtiyaç duyar
- Bu efekti yeni oluşturulan bileşenlere ve özel efektlere uygulamayı kolayca unutabilirsiniz tıklanabilir bileşenler
- Özel efekti diğer efektlerle birleştirmek zor olabilir
Bu sorunları önlemek ve özel bir bileşeni sisteminizde kolayca ölçeklendirmek için
Indication
kullanabilirsiniz.
Indication
, şunlara uygulanabilecek yeniden kullanılabilir bir görsel efekti temsil eder:
bileşenlerden bahsedeceğiz. Indication
, ikiye ayrıldı
parçalar:
IndicationNodeFactory
: Şu özelliklere sahipModifier.Node
örnek oluşturan bir fabrika: görsel efekt oluşturmak için kullanılır. Basit uygulamalar için bu bir tekli nesne (nesne) olabilir ve farklı bileşenlerde tüm uygulamayı kapsar.Bu örnekler durum bilgili veya durum bilgisiz olabilir. Her biri bileşenine göre, bir
CompositionLocal
bileşeninden değer alabilirler ve başka herhangi bir bileşende olduğu gibi belirli bir bileşenin içindeModifier.Node
.Modifier.indication
: Şunun içinIndication
çeken bir değiştirici: bir bileşenidir.Modifier.clickable
ve diğer üst düzey etkileşim değiştiriciler bir gösterge parametresini doğrudan kabul eder. Böylece,Interaction
'lar ancak kullandıklarıInteraction
'ler için görsel efektler de çizebilir oluşturur. Basit durumlardaModifier.clickable
olmadan da kullanabilirsinizModifier.indication
gerekiyor.
Efekti Indication
ile değiştirin
Bu bölümde, bir öğeye manuel ölçek efektinin nasıl değiştirileceği açıklanmaktadır. birden fazla cihazda yeniden kullanılabilecek gösterge eşdeğeri olan belirli bir düğme bileşenlerine ayıralım.
Aşağıdaki kod, basıldığında aşağı doğru ölçeklendirilen bir düğme oluşturur:
val interactionSource = remember { MutableInteractionSource() } val isPressed by interactionSource.collectIsPressedAsState() val scale by animateFloatAsState(targetValue = if (isPressed) 0.9f else 1f, label = "scale") Button( modifier = Modifier.scale(scale), onClick = { }, interactionSource = interactionSource ) { Text(if (isPressed) "Pressed!" else "Not pressed") }
Yukarıdaki snippet'teki ölçek efektini Indication
biçimine dönüştürmek için
şu adımları uygulayın:
Ölçek efektinin uygulanmasından sorumlu olan
Modifier.Node
dosyasını oluşturun. Takıldığında düğüm, öncekine benzer şekilde etkileşim kaynağını gözlemler örnekler. Buradaki tek fark, animasyonları doğrudan başlatma saptamak yerine bunları kullanır.Düğümün geçersiz kılması için
DrawModifierNode
öğesini uygulaması gerekirContentDrawScope#draw()
ve aynı çizimi kullanarak bir ölçek efekti oluşturun komutlarını kullanabilirsiniz.ContentDrawScope
adlı alıcıdandrawContent()
için yapılan telefon görüşmesinde çekilecekIndication
uygulanması gereken gerçek bileşene bir ölçek dönüşümü içinde çağrılması gerekir. LütfenIndication
uygulamaları bir noktada her zamandrawContent()
yöntemini çağırır; Aksi takdirde,Indication
öğesini uyguladığınız bileşen çizilmez.private class ScaleNode(private val interactionSource: InteractionSource) : Modifier.Node(), DrawModifierNode { var currentPressPosition: Offset = Offset.Zero val animatedScalePercent = Animatable(1f) private suspend fun animateToPressed(pressPosition: Offset) { currentPressPosition = pressPosition animatedScalePercent.animateTo(0.9f, spring()) } private suspend fun animateToResting() { animatedScalePercent.animateTo(1f, spring()) } override fun onAttach() { coroutineScope.launch { interactionSource.interactions.collectLatest { interaction -> when (interaction) { is PressInteraction.Press -> animateToPressed(interaction.pressPosition) is PressInteraction.Release -> animateToResting() is PressInteraction.Cancel -> animateToResting() } } } } override fun ContentDrawScope.draw() { scale( scale = animatedScalePercent.value, pivot = currentPressPosition ) { this@draw.drawContent() } } }
IndicationNodeFactory
oluşturun. Tek sorumluluğu, yeni bir yeni düğüm örneği sağlayın. Herhangi bir parametrelerini ayarlamak yerine, fabrika bir nesne olabilir:object ScaleIndication : IndicationNodeFactory { override fun create(interactionSource: InteractionSource): DelegatableNode { return ScaleNode(interactionSource) } override fun equals(other: Any?): Boolean = other === ScaleIndication override fun hashCode() = 100 }
Modifier.clickable
, dahili olarakModifier.indication
kullanıyor. Bu nedenle,ScaleIndication
ile tıklanabilir bir bileşene sahipse, tek yapmanız gerekenclickable
parametresi olarakIndication
:Box( modifier = Modifier .size(100.dp) .clickable( onClick = {}, indication = ScaleIndication, interactionSource = null ) .background(Color.Blue), contentAlignment = Alignment.Center ) { Text("Hello!", color = Color.White) }
Bu, aynı zamanda özel bir algoritma kullanarak üst düzey, yeniden kullanılabilir bileşenler oluşturmayı da
Indication
— bir düğme şöyle görünebilir:@Composable fun ScaleButton( onClick: () -> Unit, modifier: Modifier = Modifier, enabled: Boolean = true, interactionSource: MutableInteractionSource? = null, shape: Shape = CircleShape, content: @Composable RowScope.() -> Unit ) { Row( modifier = modifier .defaultMinSize(minWidth = 76.dp, minHeight = 48.dp) .clickable( enabled = enabled, indication = ScaleIndication, interactionSource = interactionSource, onClick = onClick ) .border(width = 2.dp, color = Color.Blue, shape = shape) .padding(horizontal = 16.dp, vertical = 8.dp), horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically, content = content ) }
Ardından, düğmeyi aşağıdaki şekilde kullanabilirsiniz:
ScaleButton(onClick = {}) { Icon(Icons.Filled.ShoppingCart, "") Spacer(Modifier.padding(10.dp)) Text(text = "Add to cart!") }
Animasyonlu kenarlık içeren gelişmiş Indication
oluştur
Indication
yalnızca bir öğeyi ölçeklendirerek
bir bileşenidir. IndicationNodeFactory
, Modifier.Node
döndürdüğü için
diğer çizim API'lerinde olduğu gibi içeriğin üzerinde veya altında her türlü efekt. Örneğin,
bileşenin etrafına animasyonlu bir kenarlık ve
bileşeninin üst tarafında:
Buradaki Indication
uygulaması önceki örneğe çok benzerdir —
bazı parametreler içeren bir düğüm oluşturur. Animasyonlu kenarlık
Indication
öğesinin kullanıldığı bileşenin şekline ve kenarlığına
Indication
uygulaması için şekil ve kenarlık genişliğinin de sağlanması gerekir
kullanabilirsiniz:
data class NeonIndication(private val shape: Shape, private val borderWidth: Dp) : IndicationNodeFactory { override fun create(interactionSource: InteractionSource): DelegatableNode { return NeonNode( shape, // Double the border size for a stronger press effect borderWidth * 2, interactionSource ) } }
Modifier.Node
uygulaması,
daha karmaşıktır. Daha önce olduğu gibi InteractionSource
gözlemliyor
eklendiğinde, animasyonları başlatır ve çizim için DrawModifierNode
uygular
aşağıdaki etkiyi yaratır:
private class NeonNode( private val shape: Shape, private val borderWidth: Dp, private val interactionSource: InteractionSource ) : Modifier.Node(), DrawModifierNode { var currentPressPosition: Offset = Offset.Zero val animatedProgress = Animatable(0f) val animatedPressAlpha = Animatable(1f) var pressedAnimation: Job? = null var restingAnimation: Job? = null private suspend fun animateToPressed(pressPosition: Offset) { // Finish any existing animations, in case of a new press while we are still showing // an animation for a previous one restingAnimation?.cancel() pressedAnimation?.cancel() pressedAnimation = coroutineScope.launch { currentPressPosition = pressPosition animatedPressAlpha.snapTo(1f) animatedProgress.snapTo(0f) animatedProgress.animateTo(1f, tween(450)) } } private fun animateToResting() { restingAnimation = coroutineScope.launch { // Wait for the existing press animation to finish if it is still ongoing pressedAnimation?.join() animatedPressAlpha.animateTo(0f, tween(250)) animatedProgress.snapTo(0f) } } override fun onAttach() { coroutineScope.launch { interactionSource.interactions.collect { interaction -> when (interaction) { is PressInteraction.Press -> animateToPressed(interaction.pressPosition) is PressInteraction.Release -> animateToResting() is PressInteraction.Cancel -> animateToResting() } } } } override fun ContentDrawScope.draw() { val (startPosition, endPosition) = calculateGradientStartAndEndFromPressPosition( currentPressPosition, size ) val brush = animateBrush( startPosition = startPosition, endPosition = endPosition, progress = animatedProgress.value ) val alpha = animatedPressAlpha.value drawContent() val outline = shape.createOutline(size, layoutDirection, this) // Draw overlay on top of content drawOutline( outline = outline, brush = brush, alpha = alpha * 0.1f ) // Draw border on top of overlay drawOutline( outline = outline, brush = brush, alpha = alpha, style = Stroke(width = borderWidth.toPx()) ) } /** * Calculates a gradient start / end where start is the point on the bounding rectangle of * size [size] that intercepts with the line drawn from the center to [pressPosition], * and end is the intercept on the opposite end of that line. */ private fun calculateGradientStartAndEndFromPressPosition( pressPosition: Offset, size: Size ): Pair<Offset, Offset> { // Convert to offset from the center val offset = pressPosition - size.center // y = mx + c, c is 0, so just test for x and y to see where the intercept is val gradient = offset.y / offset.x // We are starting from the center, so halve the width and height - convert the sign // to match the offset val width = (size.width / 2f) * sign(offset.x) val height = (size.height / 2f) * sign(offset.y) val x = height / gradient val y = gradient * width // Figure out which intercept lies within bounds val intercept = if (abs(y) <= abs(height)) { Offset(width, y) } else { Offset(x, height) } // Convert back to offsets from 0,0 val start = intercept + size.center val end = Offset(size.width - start.x, size.height - start.y) return start to end } private fun animateBrush( startPosition: Offset, endPosition: Offset, progress: Float ): Brush { if (progress == 0f) return TransparentBrush // This is *expensive* - we are doing a lot of allocations on each animation frame. To // recreate a similar effect in a performant way, it would be better to create one large // gradient and translate it on each frame, instead of creating a whole new gradient // and shader. The current approach will be janky! val colorStops = buildList { when { progress < 1 / 6f -> { val adjustedProgress = progress * 6f add(0f to Blue) add(adjustedProgress to Color.Transparent) } progress < 2 / 6f -> { val adjustedProgress = (progress - 1 / 6f) * 6f add(0f to Purple) add(adjustedProgress * MaxBlueStop to Blue) add(adjustedProgress to Blue) add(1f to Color.Transparent) } progress < 3 / 6f -> { val adjustedProgress = (progress - 2 / 6f) * 6f add(0f to Pink) add(adjustedProgress * MaxPurpleStop to Purple) add(MaxBlueStop to Blue) add(1f to Blue) } progress < 4 / 6f -> { val adjustedProgress = (progress - 3 / 6f) * 6f add(0f to Orange) add(adjustedProgress * MaxPinkStop to Pink) add(MaxPurpleStop to Purple) add(MaxBlueStop to Blue) add(1f to Blue) } progress < 5 / 6f -> { val adjustedProgress = (progress - 4 / 6f) * 6f add(0f to Yellow) add(adjustedProgress * MaxOrangeStop to Orange) add(MaxPinkStop to Pink) add(MaxPurpleStop to Purple) add(MaxBlueStop to Blue) add(1f to Blue) } else -> { val adjustedProgress = (progress - 5 / 6f) * 6f add(0f to Yellow) add(adjustedProgress * MaxYellowStop to Yellow) add(MaxOrangeStop to Orange) add(MaxPinkStop to Pink) add(MaxPurpleStop to Purple) add(MaxBlueStop to Blue) add(1f to Blue) } } } return linearGradient( colorStops = colorStops.toTypedArray(), start = startPosition, end = endPosition ) } companion object { val TransparentBrush = SolidColor(Color.Transparent) val Blue = Color(0xFF30C0D8) val Purple = Color(0xFF7848A8) val Pink = Color(0xFFF03078) val Orange = Color(0xFFF07800) val Yellow = Color(0xFFF0D800) const val MaxYellowStop = 0.16f const val MaxOrangeStop = 0.33f const val MaxPinkStop = 0.5f const val MaxPurpleStop = 0.67f const val MaxBlueStop = 0.83f } }
Buradaki temel fark şudur:
animasyonunda, animateToResting()
işleviyle birlikte çalışır.
hemen kullanılabilirse basın animasyonu devam eder. Ayrıca,
birden fazla hızlı basma için (animateToPressed
)
animasyon sırasında gerçekleştiğini varsayalım. Önceki animasyon
işlemi iptal edilir ve basın animasyonu baştan başlar. Birden fazla desteği
eşzamanlı efektler (ör. yeni bir dalga animasyonu
üstünde), animasyonları liste halinde izlemek yerine
mevcut animasyonları iptal etmek ve yenilerini başlatmak.
Sizin için önerilenler
- Not: JavaScript kapalıyken bağlantı metni gösterilir
- Hareketleri anlama
- Jetpack Compose için Kotlin
- Malzeme Bileşenleri ve düzenleri