Untuk meningkatkan performa komposisi komponen interaktif yang menggunakan
Modifier.clickable
, kami telah memperkenalkan API baru. API ini memungkinkan lebih banyak
implementasi Indication
yang efisien, seperti ripple.
androidx.compose.foundation:foundation:1.7.0+
dan
androidx.compose.material:material-ripple:1.7.0+
menyertakan API berikut
perubahan:
Tidak digunakan lagi |
Penggantian |
---|---|
|
|
|
Catatan: Dalam konteks ini, "Library Material" merujuk ke |
|
Yakni:
|
Halaman ini menjelaskan dampak perubahan perilaku dan petunjuk untuk bermigrasi ke API yang baru.
Perubahan perilaku
Versi library berikut menyertakan perubahan perilaku ripple:
androidx.compose.material:material:1.7.0+
androidx.compose.material3:material3:1.3.0+
androidx.wear.compose:compose-material:1.4.0+
Versi library Material ini tidak lagi menggunakan rememberRipple()
; sebagai gantinya,
mereka menggunakan API ripple baru. Hasilnya, kueri tidak mengkueri LocalRippleTheme
.
Oleh karena itu, jika Anda menetapkan LocalRippleTheme
di aplikasi Anda, Material
komponen tidak akan menggunakan nilai ini.
Bagian berikut menjelaskan cara beralih kembali ke perilaku lama untuk sementara
tanpa melakukan migrasi; tetapi, sebaiknya Anda bermigrasi ke API baru. Sebagai
petunjuk migrasi, lihat Bermigrasi dari rememberRipple
ke ripple
dan bagian-bagian berikutnya.
Mengupgrade versi library Material tanpa melakukan migrasi
Untuk berhenti memblokir upgrade versi library, Anda dapat menggunakan
LocalUseFallbackRippleImplementation CompositionLocal
API untuk dikonfigurasi
Komponen Material untuk kembali ke perilaku lama:
CompositionLocalProvider(LocalUseFallbackRippleImplementation provides true) { MaterialTheme { App() } }
Pastikan untuk memberikan ini di luar MaterialTheme
sehingga ripple lama dapat
disediakan melalui LocalIndication
.
Bagian berikut menjelaskan cara bermigrasi ke API baru.
Bermigrasi dari rememberRipple
ke ripple
Menggunakan library Material
Jika Anda menggunakan library Material, langsung ganti rememberRipple()
dengan
panggilan ke ripple()
dari library yang sesuai. API ini menciptakan ripple
menggunakan nilai yang berasal dari API tema Material. Lalu, teruskan objek
objek ke Modifier.clickable
dan/atau komponen lainnya.
Misalnya, cuplikan berikut menggunakan API yang tidak digunakan lagi:
Box( Modifier.clickable( onClick = {}, interactionSource = remember { MutableInteractionSource() }, indication = rememberRipple() ) ) { // ... }
Anda harus mengubah cuplikan di atas menjadi:
@Composable private fun RippleExample() { Box( Modifier.clickable( onClick = {}, interactionSource = remember { MutableInteractionSource() }, indication = ripple() ) ) { // ... } }
Perhatikan bahwa ripple()
tidak lagi menjadi fungsi composable dan tidak perlu
diingat. Model ini juga dapat digunakan kembali di beberapa komponen, mirip dengan
pengubah, jadi pertimbangkan untuk mengekstrak pembuatan riak ke nilai tingkat teratas untuk
menyimpan alokasi.
Menerapkan sistem desain khusus
Jika Anda menerapkan sistem desain Anda sendiri, dan sebelumnya Anda menggunakan
rememberRipple()
beserta RippleTheme
kustom untuk mengonfigurasi ripple,
Anda harus menyediakan ripple API Anda sendiri yang didelegasikan ke node ripple
API ditampilkan di material-ripple
. Kemudian, komponen Anda dapat menggunakan ripple Anda sendiri
yang memakai nilai tema Anda secara langsung. Untuk informasi selengkapnya, lihat Memigrasikan
dari RippleTheme
.
Bermigrasi dari RippleTheme
Memilih tidak ikut perubahan perilaku untuk sementara
Library Material memiliki CompositionLocal
sementara,
LocalUseFallbackRippleImplementation
, yang dapat Anda gunakan untuk mengonfigurasi semua
Komponen Material untuk kembali menggunakan rememberRipple
. Dengan begini,
rememberRipple
terus mengkueri LocalRippleTheme
.
Cuplikan kode berikut menunjukkan cara menggunakan
API LocalUseFallbackRippleImplementation CompositionLocal
:
CompositionLocalProvider(LocalUseFallbackRippleImplementation provides true) { MaterialTheme { App() } }
Jika menggunakan tema aplikasi kustom yang dibuat di atas Material, Anda dapat menyediakan lokal komposisi dengan aman sebagai bagian dari tema aplikasi:
@OptIn(ExperimentalMaterialApi::class) @Composable fun MyAppTheme(content: @Composable () -> Unit) { CompositionLocalProvider(LocalUseFallbackRippleImplementation provides true) { MaterialTheme(content = content) } }
Untuk informasi selengkapnya, lihat Mengupgrade versi library Material tanpa bagian migrasi.
Menggunakan RippleTheme
guna menonaktifkan ripple untuk komponen tertentu
Library material
dan material3
mengekspos RippleConfiguration
dan
LocalRippleConfiguration
, yang memungkinkan Anda mengonfigurasi tampilan
riak dalam sebuah subpohon. Perhatikan bahwa RippleConfiguration
dan
LocalRippleConfiguration
bersifat eksperimental, dan hanya ditujukan untuk per komponen
dan penyesuaian. Penyesuaian global/seluruh tema tidak didukung oleh
API; lihat Menggunakan RippleTheme
untuk mengubah semua ripple secara global dalam
untuk informasi selengkapnya tentang kasus penggunaan tersebut.
Misalnya, cuplikan berikut menggunakan API yang tidak digunakan lagi:
private object DisabledRippleTheme : RippleTheme { @Composable override fun defaultColor(): Color = Color.Transparent @Composable override fun rippleAlpha(): RippleAlpha = RippleAlpha(0f, 0f, 0f, 0f) } // ... CompositionLocalProvider(LocalRippleTheme provides DisabledRippleTheme) { Button { // ... } }
Anda harus mengubah cuplikan di atas menjadi:
CompositionLocalProvider(LocalRippleConfiguration provides null) { Button { // ... } }
Menggunakan RippleTheme
untuk mengubah warna/alfa ripple untuk komponen tertentu
Seperti yang dijelaskan di bagian sebelumnya, RippleConfiguration
dan
LocalRippleConfiguration
adalah API eksperimental dan hanya ditujukan untuk
penyesuaian per komponen.
Misalnya, cuplikan berikut menggunakan API yang tidak digunakan lagi:
private object DisabledRippleThemeColorAndAlpha : RippleTheme { @Composable override fun defaultColor(): Color = Color.Red @Composable override fun rippleAlpha(): RippleAlpha = MyRippleAlpha } // ... CompositionLocalProvider(LocalRippleTheme provides DisabledRippleThemeColorAndAlpha) { Button { // ... } }
Anda harus mengubah cuplikan di atas menjadi:
@OptIn(ExperimentalMaterialApi::class) private val MyRippleConfiguration = RippleConfiguration(color = Color.Red, rippleAlpha = MyRippleAlpha) // ... CompositionLocalProvider(LocalRippleConfiguration provides MyRippleConfiguration) { Button { // ... } }
Menggunakan RippleTheme
untuk mengubah semua ripple dalam aplikasi secara global
Sebelumnya, Anda dapat menggunakan LocalRippleTheme
untuk menentukan perilaku ripple pada
tingkat seluruh tema. Pada dasarnya, ini adalah titik integrasi antara
lokal dan riak sistem komposisi desain. Alih-alih mengekspos
dengan tema primitif, material-ripple
kini mengekspos createRippleModifierNode()
. Fungsi ini memungkinkan {i>library<i} sistem desain untuk membuat
implementasi urutan wrapper
, yang mengkueri nilai temanya, lalu mendelegasikan
implementasi ripple ke node yang dibuat oleh fungsi ini.
Hal ini memungkinkan sistem desain untuk secara langsung mengkueri apa yang mereka butuhkan, dan mengekspos
membutuhkan lapisan tema yang dapat dikonfigurasi pengguna di atasnya tanpa harus mematuhi
apa yang disediakan di lapisan material-ripple
. Perubahan ini juga membuat lebih banyak
secara eksplisit tema/spesifikasi riak sesuai dengan, karena
ripple API itu sendiri yang mendefinisikan kontrak tersebut, bukan secara implisit
yang berasal dari tema.
Untuk panduan, lihat penerapan API ripple di Material library, dan mengganti panggilan ke lokal komposisi Material sesuai kebutuhan sistem desain Anda sendiri.
Bermigrasi dari Indication
ke IndicationNodeFactory
Melewati sekitar Indication
Jika Anda hanya membuat Indication
untuk diteruskan, seperti membuat
ripple yang akan diteruskan ke Modifier.clickable
atau Modifier.indication
, Anda tidak
perlu melakukan perubahan. IndicationNodeFactory
mewarisi dari Indication
,
sehingga semuanya akan terus
mengkompilasi dan berfungsi.
Membuat Indication
Jika Anda membuat implementasi Indication
sendiri, migrasi harus
sederhana dalam kebanyakan kasus. Misalnya, pertimbangkan Indication
yang menerapkan
efek skala pada pers:
object ScaleIndication : Indication { @Composable override fun rememberUpdatedInstance(interactionSource: InteractionSource): IndicationInstance { // key the remember against interactionSource, so if it changes we create a new instance val instance = remember(interactionSource) { ScaleIndicationInstance() } LaunchedEffect(interactionSource) { interactionSource.interactions.collectLatest { interaction -> when (interaction) { is PressInteraction.Press -> instance.animateToPressed(interaction.pressPosition) is PressInteraction.Release -> instance.animateToResting() is PressInteraction.Cancel -> instance.animateToResting() } } } return instance } } private class ScaleIndicationInstance : IndicationInstance { var currentPressPosition: Offset = Offset.Zero val animatedScalePercent = Animatable(1f) suspend fun animateToPressed(pressPosition: Offset) { currentPressPosition = pressPosition animatedScalePercent.animateTo(0.9f, spring()) } suspend fun animateToResting() { animatedScalePercent.animateTo(1f, spring()) } override fun ContentDrawScope.drawIndication() { scale( scale = animatedScalePercent.value, pivot = currentPressPosition ) { this@drawIndication.drawContent() } } }
Anda dapat memigrasikannya dalam dua langkah:
Migrasikan
ScaleIndicationInstance
menjadiDrawModifierNode
. Platform API untukDrawModifierNode
sangat mirip denganIndicationInstance
: metode ini mengekspos fungsiContentDrawScope#draw()
yang secara fungsional setara denganIndicationInstance#drawContent()
. Anda perlu mengubah fungsi tersebut, dan kemudian terapkan logikacollectLatest
di dalam node secara langsung, bukan secaraIndication
.Misalnya, cuplikan berikut menggunakan API yang tidak digunakan lagi:
private class ScaleIndicationInstance : IndicationInstance { var currentPressPosition: Offset = Offset.Zero val animatedScalePercent = Animatable(1f) suspend fun animateToPressed(pressPosition: Offset) { currentPressPosition = pressPosition animatedScalePercent.animateTo(0.9f, spring()) } suspend fun animateToResting() { animatedScalePercent.animateTo(1f, spring()) } override fun ContentDrawScope.drawIndication() { scale( scale = animatedScalePercent.value, pivot = currentPressPosition ) { this@drawIndication.drawContent() } } }
Anda harus mengubah cuplikan di atas menjadi:
private class ScaleIndicationNode( 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() } } }
Migrasikan
ScaleIndication
untuk menerapkanIndicationNodeFactory
. Karena logika koleksi kini dipindahkan ke node, ini adalah factory yang sangat sederhana yang satu-satunya tanggung jawabnya adalah membuat instance node.Misalnya, cuplikan berikut menggunakan API yang tidak digunakan lagi:
object ScaleIndication : Indication { @Composable override fun rememberUpdatedInstance(interactionSource: InteractionSource): IndicationInstance { // key the remember against interactionSource, so if it changes we create a new instance val instance = remember(interactionSource) { ScaleIndicationInstance() } LaunchedEffect(interactionSource) { interactionSource.interactions.collectLatest { interaction -> when (interaction) { is PressInteraction.Press -> instance.animateToPressed(interaction.pressPosition) is PressInteraction.Release -> instance.animateToResting() is PressInteraction.Cancel -> instance.animateToResting() } } } return instance } }
Anda harus mengubah cuplikan di atas menjadi:
object ScaleIndicationNodeFactory : IndicationNodeFactory { override fun create(interactionSource: InteractionSource): DelegatableNode { return ScaleIndicationNode(interactionSource) } override fun hashCode(): Int = -1 override fun equals(other: Any?) = other === this }
Menggunakan Indication
untuk membuat IndicationInstance
Dalam sebagian besar kasus, Anda harus menggunakan Modifier.indication
untuk menampilkan Indication
untuk
komponen. Namun, dalam kasus yang jarang terjadi, Anda membuat
IndicationInstance
menggunakan rememberUpdatedInstance
, Anda perlu mengupdate
implementasi untuk memeriksa apakah Indication
adalah IndicationNodeFactory
sehingga Anda
bisa menggunakan implementasi yang lebih ringan. Misalnya, Modifier.indication
akan
delegasikan secara internal ke node yang dibuat jika node tersebut adalah IndicationNodeFactory
. Jika
tidak, fungsi Modifier.composed
akan digunakan untuk memanggil rememberUpdatedInstance
.