برای بهبود عملکرد ترکیب کامپوننتهای تعاملی که از Modifier.clickable استفاده میکنند، ما APIهای جدیدی را معرفی کردهایم. این APIها امکان پیادهسازیهای کارآمدتر Indication مانند ripples را فراهم میکنند.
androidx.compose.foundation:foundation:1.7.0+ و androidx.compose.material:material-ripple:1.7.0+ شامل تغییرات API زیر هستند:
منسوخ شده | جایگزینی |
|---|---|
| |
| در عوض، APIهای جدید توجه: در این متن، منظور از «کتابخانههای متریال» |
| یا:
|
این صفحه تأثیر تغییر رفتار و دستورالعملهای مهاجرت به APIهای جدید را شرح میدهد.
تغییر رفتار
نسخههای کتابخانه زیر شامل تغییر رفتار ripple هستند:
-
androidx.compose.material:material:1.7.0+ -
androidx.compose.material3:material3:1.3.0+ -
androidx.wear.compose:compose-material:1.4.0+
این نسخههای کتابخانههای Material دیگر از rememberRipple() استفاده نمیکنند؛ در عوض، از APIهای جدید ripple استفاده میکنند. در نتیجه، LocalRippleTheme را پرسوجو نمیکنند. بنابراین، اگر LocalRippleTheme در برنامه خود تنظیم کنید، کامپوننتهای Material از این مقادیر استفاده نخواهند کرد .
بخشهای بعدی نحوهی مهاجرت به APIهای جدید را شرح میدهند.
مهاجرت از rememberRipple به ripple
استفاده از کتابخانه متریال
اگر از کتابخانه Material استفاده میکنید، مستقیماً rememberRipple() با فراخوانی ripple() از کتابخانه مربوطه جایگزین کنید. این API با استفاده از مقادیر مشتق شده از APIهای تم Material، یک موج ایجاد میکند. سپس، شیء برگردانده شده را به Modifier.clickable و/یا سایر کامپوننتها منتقل کنید.
برای مثال، قطعه کد زیر از APIهای منسوخشده استفاده میکند:
Box( Modifier.clickable( onClick = {}, interactionSource = remember { MutableInteractionSource() }, indication = rememberRipple() ) ) { // ... }
شما باید قطعه کد بالا را به صورت زیر تغییر دهید:
@Composable private fun RippleExample() { Box( Modifier.clickable( onClick = {}, interactionSource = remember { MutableInteractionSource() }, indication = ripple() ) ) { // ... } }
توجه داشته باشید که ripple() دیگر یک تابع قابل ترکیب نیست و نیازی به حفظ کردن ندارد. همچنین میتواند در چندین کامپوننت، مشابه اصلاحکنندهها، مورد استفاده مجدد قرار گیرد، بنابراین استخراج ripple ایجاد شده را به یک مقدار سطح بالا در نظر بگیرید تا در تخصیصها صرفهجویی شود.
پیادهسازی سیستم طراحی سفارشی
اگر در حال پیادهسازی سیستم طراحی خودتان هستید و قبلاً از rememberRipple() به همراه یک RippleTheme سفارشی برای پیکربندی ripple استفاده میکردید، باید API ripple خودتان را ارائه دهید که به APIهای گره ripple که در material-ripple قرار دارند، واگذار شود. سپس، اجزای شما میتوانند از ripple خودتان استفاده کنند که مستقیماً از مقادیر تم شما استفاده میکند. برای اطلاعات بیشتر، به Migrate from RippleTheme مراجعه کنید.
مهاجرت از RippleTheme
استفاده از RippleTheme برای غیرفعال کردن موج برای یک کامپوننت مشخص
کتابخانههای material و material3 RippleConfiguration و LocalRippleConfiguration را ارائه میدهند که به شما امکان میدهند ظاهر موجها را در یک زیردرخت پیکربندی کنید. توجه داشته باشید که RippleConfiguration و LocalRippleConfiguration فقط برای سفارشیسازی هر جزء در نظر گرفته شدهاند. سفارشیسازی سراسری/در سطح قالب با این APIها پشتیبانی نمیشود؛ برای اطلاعات بیشتر در مورد این مورد استفاده، به بخش «استفاده از RippleTheme برای تغییر سراسری همه موجها در یک برنامه» مراجعه کنید.
برای مثال، قطعه کد زیر از APIهای منسوخشده استفاده میکند:
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 { // ... } }
شما باید قطعه کد بالا را به صورت زیر تغییر دهید:
CompositionLocalProvider(LocalRippleConfiguration provides null) { Button { // ... } }
استفاده از RippleTheme برای تغییر رنگ/آلفای یک موج برای یک کامپوننت مشخص
همانطور که در بخش قبلی توضیح داده شد، RippleConfiguration و LocalRippleConfiguration فقط برای سفارشیسازی هر جزء در نظر گرفته شدهاند.
برای مثال، قطعه کد زیر از APIهای منسوخشده استفاده میکند:
private object DisabledRippleThemeColorAndAlpha : RippleTheme { @Composable override fun defaultColor(): Color = Color.Red @Composable override fun rippleAlpha(): RippleAlpha = MyRippleAlpha } // ... CompositionLocalProvider(LocalRippleTheme provides DisabledRippleThemeColorAndAlpha) { Button { // ... } }
شما باید قطعه کد بالا را به صورت زیر تغییر دهید:
@OptIn(ExperimentalMaterialApi::class) private val MyRippleConfiguration = RippleConfiguration(color = Color.Red, rippleAlpha = MyRippleAlpha) // ... CompositionLocalProvider(LocalRippleConfiguration provides MyRippleConfiguration) { Button { // ... } }
استفاده از RippleTheme برای تغییر سراسری همه موجها در یک برنامه
پیش از این، میتوانستید LocalRippleTheme برای تعریف رفتار ripple در سطح کل قالب استفاده کنید. این اساساً یک نقطه ادغام بین ترکیب سیستم طراحی سفارشی محلی و ripple بود. به جای نمایش یک قالببندی اولیه عمومی، material-ripple اکنون یک تابع createRippleModifierNode() را نمایش میدهد. این تابع به کتابخانههای سیستم طراحی اجازه میدهد تا پیادهسازی wrapper مرتبه بالاتر ایجاد کنند، که مقادیر قالب خود را پرسوجو میکند و سپس پیادهسازی ripple را به گره ایجاد شده توسط این تابع واگذار میکند.
این به سیستمهای طراحی اجازه میدهد تا مستقیماً آنچه را که نیاز دارند جستجو کنند و هرگونه لایه قالببندی مورد نیاز قابل تنظیم توسط کاربر را در بالا نمایش دهند، بدون اینکه مجبور باشند با آنچه در لایه material-ripple ارائه میشود، مطابقت داشته باشند. این تغییر همچنین باعث میشود که قالب/مشخصات ripple با صراحت بیشتری مطابقت داشته باشد، زیرا خود API ripple است که آن قرارداد را تعریف میکند، نه اینکه به طور ضمنی از قالب مشتق شده باشد.
برای راهنمایی، به پیادهسازی ripple API در کتابخانههای Material مراجعه کنید و فراخوانیهای محلی Material composition را در صورت نیاز برای سیستم طراحی خود جایگزین کنید.
مهاجرت از Indication به IndicationNodeFactory
Indication عبور
اگر فقط در حال ایجاد یک Indication برای انتقال هستید، مانند ایجاد یک موج برای انتقال به Modifier.clickable یا Modifier.indication ، نیازی به ایجاد هیچ تغییری ندارید. IndicationNodeFactory از Indication ارث بری میکند، بنابراین همه چیز به کامپایل و کار خود ادامه خواهد داد.
ایجاد Indication
اگر در حال ایجاد پیادهسازی اختصاصی Indication هستید، مهاجرت در بیشتر موارد باید ساده باشد. برای مثال، یک Indication را در نظر بگیرید که یک اثر مقیاس را روی مطبوعات اعمال میکند:
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() } } }
شما میتوانید این را در دو مرحله منتقل کنید:
ScaleIndicationInstanceبه یکDrawModifierNodeتبدیل کنید. سطح API برایDrawModifierNodeبسیار شبیه بهIndicationInstanceاست: این API یک تابعContentDrawScope#draw()را ارائه میدهد که از نظر عملکردی معادلIndicationInstance#drawContent()است. شما باید آن تابع را تغییر دهید و سپس منطقcollectLatestمستقیماً درون گره، به جایIndication، پیادهسازی کنید.برای مثال، قطعه کد زیر از APIهای منسوخشده استفاده میکند:
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() } } }
شما باید قطعه کد بالا را به صورت زیر تغییر دهید:
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() } } }
برای پیادهسازی
IndicationNodeFactoryScaleIndicationمهاجرت دهید. از آنجا که منطق مجموعه اکنون به گره منتقل شده است، این یک شیء factory بسیار ساده است که تنها مسئولیت آن ایجاد یک نمونه گره است.برای مثال، قطعه کد زیر از APIهای منسوخشده استفاده میکند:
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 } }
شما باید قطعه کد بالا را به صورت زیر تغییر دهید:
object ScaleIndicationNodeFactory : IndicationNodeFactory { override fun create(interactionSource: InteractionSource): DelegatableNode { return ScaleIndicationNode(interactionSource) } override fun hashCode(): Int = -1 override fun equals(other: Any?) = other === this }
استفاده از Indication برای ایجاد یک IndicationInstance
در بیشتر موارد، شما باید Modifier.indication برای نمایش Indication برای یک کامپوننت استفاده کنید. با این حال، در موارد نادری که شما به صورت دستی یک IndicationInstance با استفاده از rememberUpdatedInstance ایجاد میکنید، باید پیادهسازی خود را بهروزرسانی کنید تا بررسی کنید که آیا Indication یک IndicationNodeFactory است یا خیر، بنابراین میتوانید از یک پیادهسازی سبکتر استفاده کنید. به عنوان مثال، Modifier.indication اگر گره ایجاد شده یک IndicationNodeFactory باشد، به صورت داخلی آن را به آن واگذار میکند. در غیر این صورت، Modifier.composed برای فراخوانی rememberUpdatedInstance استفاده خواهد کرد.