Untuk meningkatkan performa komposisi komponen interaktif yang menggunakan
Modifier.clickable
, kami telah memperkenalkan API baru. API ini memungkinkan implementasi
Indication
yang lebih efisien, seperti ripple.
androidx.compose.foundation:foundation:1.7.0+
dan
androidx.compose.material:material-ripple:1.7.0+
mencakup perubahan API
berikut:
Tidak digunakan lagi |
Penggantian |
---|---|
|
|
|
Sebagai gantinya, Catatan: Dalam konteks ini, "Library Material" mengacu pada |
|
Yakni:
|
Halaman ini menjelaskan dampak perubahan perilaku dan petunjuk untuk bermigrasi ke API 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,
library tersebut menggunakan API ripple baru. Akibatnya, mereka tidak membuat kueri LocalRippleTheme
.
Oleh karena itu, jika Anda menetapkan LocalRippleTheme
di aplikasi, komponen
Material tidak akan menggunakan nilai ini.
Bagian berikut menjelaskan cara beralih kembali ke perilaku lama untuk sementara tanpa melakukan migrasi; namun, sebaiknya lakukan migrasi ke API baru. Untuk
petunjuk migrasi, lihat Bermigrasi dari rememberRipple
ke ripple
dan bagian berikutnya.
Mengupgrade versi library Material tanpa bermigrasi
Untuk berhenti memblokir upgrade versi library, Anda dapat menggunakan
LocalUseFallbackRippleImplementation CompositionLocal
API sementara untuk mengonfigurasi
komponen Material agar kembali ke perilaku lama:
CompositionLocalProvider(LocalUseFallbackRippleImplementation provides true) { MaterialTheme { App() } }
Pastikan untuk menyediakannya di luar MaterialTheme
sehingga ripple lama dapat
diberikan melalui LocalIndication
.
Bagian berikut menjelaskan cara melakukan migrasi ke API baru.
Bermigrasi dari rememberRipple
ke ripple
Menggunakan library Material
Jika Anda menggunakan library Material, ganti langsung rememberRipple()
dengan
panggilan ke ripple()
dari library yang sesuai. API ini membuat ripple
menggunakan nilai yang berasal dari API tema Material. Kemudian, teruskan objek yang ditampilkan
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 memodifikasi cuplikan di atas untuk:
@Composable private fun RippleExample() { Box( Modifier.clickable( onClick = {}, interactionSource = remember { MutableInteractionSource() }, indication = ripple() ) ) { // ... } }
Perhatikan bahwa ripple()
tidak lagi merupakan fungsi composable dan tidak perlu
diingat. Ini juga dapat digunakan kembali di beberapa komponen, mirip dengan
pengubah, jadi pertimbangkan untuk mengekstrak pembuatan ripple ke nilai level teratas untuk
menyimpan alokasi.
Mengimplementasikan sistem desain kustom
Jika Anda mengimplementasikan sistem desain sendiri, dan sebelumnya menggunakan
rememberRipple()
bersama dengan RippleTheme
kustom untuk mengonfigurasi ripple,
Anda harus menyediakan API ripple Anda sendiri yang didelegasikan ke API
node ripple yang diekspos dalam material-ripple
. Kemudian, komponen Anda dapat menggunakan ripple
Anda sendiri yang memakai nilai tema secara langsung. Untuk mengetahui informasi selengkapnya, lihat Bermigrasi
dariRippleTheme
.
Bermigrasi dari RippleTheme
Memilih tidak mengikuti perubahan perilaku untuk sementara
Library Material memiliki CompositionLocal
sementara,
LocalUseFallbackRippleImplementation
, yang dapat Anda gunakan untuk mengonfigurasi semua
komponen Material agar dapat kembali menggunakan rememberRipple
. Dengan cara ini,
rememberRipple
akan terus mengkueri LocalRippleTheme
.
Cuplikan kode berikut menunjukkan cara menggunakan
API LocalUseFallbackRippleImplementation CompositionLocal
:
CompositionLocalProvider(LocalUseFallbackRippleImplementation provides true) { MaterialTheme { App() } }
Jika menggunakan tema aplikasi kustom yang di-build di atas Material, Anda dapat menyediakan komposisi lokal 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 bagian Mengupgrade versi library Material tanpa memigrasikan.
Menggunakan RippleTheme
untuk menonaktifkan ripple untuk komponen tertentu
Library material
dan material3
mengekspos RippleConfiguration
dan
LocalRippleConfiguration
, yang memungkinkan Anda mengonfigurasi tampilan
ripple dalam sub-hierarki. Perlu diperhatikan bahwa RippleConfiguration
dan
LocalRippleConfiguration
bersifat eksperimental, dan hanya dimaksudkan untuk penyesuaian
per komponen. Penyesuaian tingkat global/tema tidak didukung dengan API
ini; lihat Menggunakan RippleTheme
untuk mengubah semua ripple secara global dalam
aplikasi untuk mengetahui 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 memodifikasi cuplikan di atas untuk:
@OptIn(ExperimentalMaterialApi::class) private val DisabledRippleConfiguration = RippleConfiguration(isEnabled = false) // ... CompositionLocalProvider(LocalRippleConfiguration provides DisabledRippleConfiguration) { 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 memodifikasi cuplikan di atas untuk:
@OptIn(ExperimentalMaterialApi::class) private val MyRippleConfiguration = RippleConfiguration(color = Color.Red, rippleAlpha = MyRippleAlpha) // ... CompositionLocalProvider(LocalRippleConfiguration provides MyRippleConfiguration) { Button { // ... } }
Menggunakan RippleTheme
untuk mengubah semua ripple secara global di aplikasi
Sebelumnya, Anda dapat menggunakan LocalRippleTheme
untuk menentukan perilaku ripple pada
tingkat seluruh tema. Ini pada dasarnya adalah titik integrasi antara
lokal komposisi sistem desain khusus dan ripple. Bukannya mengekspos primitif tema
generik, material-ripple
kini mengekspos fungsi
createRippleModifierNode()
. Fungsi ini memungkinkan library sistem desain membuat implementasi
wrapper
tingkat tinggi, yang mengkueri nilai temanya, lalu mendelegasikan
implementasi ripple ke node yang dibuat oleh fungsi ini.
Hal ini memungkinkan sistem desain untuk langsung membuat kueri apa yang diperlukan, dan mengekspos
lapisan tema yang dapat dikonfigurasi pengguna di bagian atas tanpa harus sesuai dengan
yang disediakan pada lapisan material-ripple
. Perubahan ini juga menjadikan tema/spesifikasi yang sesuai dengan ripple secara lebih eksplisit, karena API ripple itu sendirilah yang mendefinisikan kontrak tersebut, bukan diambil secara implisit dari tema.
Untuk panduan, lihat implementasi API ripple di library Material, dan ganti panggilan ke lokal komposisi Material sesuai kebutuhan untuk sistem desain Anda sendiri.
Bermigrasi dari Indication
ke IndicationNodeFactory
Melewati sekitar Indication
Jika hanya membuat Indication
untuk diteruskan, seperti membuat
ripple untuk diteruskan ke Modifier.clickable
atau Modifier.indication
, Anda tidak
perlu membuat perubahan apa pun. IndicationNodeFactory
mewarisi dari Indication
,
sehingga semuanya akan terus dikompilasi dan berfungsi.
Membuat Indication
Jika Anda membuat implementasi Indication
sendiri, dalam sebagian besar kasus,
migrasi akan menjadi sederhana. 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
: platform ini menampilkan fungsiContentDrawScope#draw()
yang secara fungsional setara denganIndicationInstance#drawContent()
. Anda perlu mengubah fungsi tersebut, lalu menerapkan logikacollectLatest
di dalam node secara langsung, bukanIndication
.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 memodifikasi cuplikan di atas untuk:
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 mengimplementasikanIndicationNodeFactory
. Karena logika koleksi sekarang dipindahkan ke dalam node, ini adalah objek factory sangat sederhana yang satu-satunya tanggung jawab 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 memodifikasi cuplikan di atas untuk:
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
Pada umumnya, Anda harus menggunakan Modifier.indication
untuk menampilkan Indication
untuk
komponen. Namun, dalam kasus yang jarang terjadi saat Anda membuat
IndicationInstance
secara manual menggunakan rememberUpdatedInstance
, Anda perlu mengupdate
implementasi untuk memeriksa apakah Indication
adalah IndicationNodeFactory
sehingga Anda
dapat menggunakan implementasi yang lebih ringan. Misalnya, Modifier.indication
akan
didelegasikan secara internal ke node yang dibuat jika node tersebut adalah IndicationNodeFactory
. Jika tidak, Modifier.composed
akan digunakan untuk memanggil rememberUpdatedInstance
.