برای بهبود عملکرد ترکیببندی عناصر تعاملی که از
Modifier.clickable استفاده میکنند، میاناهای برنامهسازی کاربردی جدیدی معرفی کردهایم. این «میاناهای برنامهسازی کاربردی» امکان پیادهسازیهای
کارآمدتر Indication، مانند موجها را فراهم میکنند.
androidx.compose.foundation:foundation:1.7.0+ و
androidx.compose.material:material-ripple:1.7.0+ شامل تغییرات زیر در
واسط برنامهسازی کاربردی میشود:
منسوخ |
جایگزین |
|---|---|
|
|
|
بهجای آن، توجه: در این زمینه، «کتابخانههای مواد» به |
|
یکی از موارد زیر را انجام دهید:
|
این صفحه تأثیر تغییر عملکرد و دستورالعملهای انتقال به میاناهای برنامهسازی کاربردی جدید را شرح میدهد.
تغییر رفتار
نسخههای کتابخانه زیر شامل تغییر رفتار موجدار است:
androidx.compose.material:material:1.7.0+androidx.compose.material3:material3:1.3.0+androidx.wear.compose:compose-material:1.4.0+
این نسخههای کتابخانههای Material دیگر از rememberRipple() استفاده نمیکنند؛ درعوض،
از میاناهای برنامهسازی کاربردی جدید موج استفاده میکنند. درنتیجه، آنها LocalRippleTheme را پُرسمان نمیکنند.
بنابراین، اگر LocalRippleTheme را در برنامهتان تنظیم کنید، عناصر
Material از این مقادیر استفاده نخواهند کرد.
بخشهای زیر نحوه انتقال به APIهای جدید را شرح میدهد.
انتقال از rememberRipple به ripple
استفاده از کتابخانه Material
اگر از کتابخانه Material استفاده میکنید، rememberRipple() را مستقیماً با فراخوانی ripple() از کتابخانه مربوطه جایگزین کنید. این API موجی ایجاد میکند
که از مقادیر برگرفته از APIهای موضوع Material استفاده میکند. سپس، شیء برگشتی را به Modifier.clickable و/یا دیگر عناصر ارسال کنید.
برای مثال، تکهکد زیر از میاناهای برنامهسازی کاربردی منسوخ استفاده میکند:
Box( Modifier.clickable( onClick = {}, interactionSource = remember { MutableInteractionSource() }, indication = rememberRipple() ) ) { // ... }
باید گلچین بالا را به این صورت اصلاح کنید:
@Composable private fun RippleExample() { Box( Modifier.clickable( onClick = {}, interactionSource = remember { MutableInteractionSource() }, indication = ripple() ) ) { // ... } }
توجه داشته باشید که ripple() دیگر تابع ترکیبی نیست و نیازی نیست آن را بهخاطر بسپارید. همچنین میتوان آن را در چندین عنصر، مشابه
اصلاحکنندهها، مجدداً استفاده کرد، بنابراین برای
ذخیره تخصیصها، ایجاد موج را به مقدار سطح بالا استخراج کنید.
درحال پیادهسازی سیستم طراحی سفارشی
اگر سیستم طراحی خودتان را پیادهسازی میکنید و قبلاً از
rememberRipple() بههمراه RippleTheme سفارشی برای پیکربندی موج استفاده میکردید،
باید بهجای آن میانای برنامهسازی کاربردی موج خودتان را ارائه دهید که به میاناهای برنامهسازی کاربردی گره موج
نمایانشده در material-ripple واگذار میکند. سپس، عناصر شما میتوانند از موج خودشان استفاده کنند
که مستقیماً مقادیر زمینهتان را مصرف میکند. برای اطلاعات بیشتر، انتقال ازRippleTheme را ببینید.
انتقال از RippleTheme
استفاده از RippleTheme برای غیرفعال کردن موج برای یک عنصر معین
کتابخانههای material و material3 RippleConfiguration و
LocalRippleConfiguration را آشکار میکنند که به شما امکان میدهد ظاهر
موجها را در یک زیردرخت پیکربندی کنید. توجه داشته باشید که RippleConfiguration و
LocalRippleConfiguration فقط برای سفارشیسازی هر عنصر درنظر گرفته شدهاند. شخصیسازی سراسری/موضوعی با این
APIها پشتیبانی نمیشود؛ برای اطلاعات بیشتر درباره این مورد استفاده، استفاده از RippleTheme برای تغییر سراسری همه موجها در
برنامه را ببینید.
برای مثال، تکهکد زیر از میاناهای برنامهسازی کاربردی منسوخ استفاده میکند:
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 فقط برای سفارشیسازی براساس هر عنصر درنظر گرفته شدهاند.
برای مثال، تکهکد زیر از میاناهای برنامهسازی کاربردی منسوخ استفاده میکند:
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 برای تعریف رفتار موج در سطح کل
پوسته استفاده کنید. این اساساً نقطه یکپارچهسازی بین محلیهای ترکیب سیستم طراحی سفارشی و موج بود. بهجای نمایان کردن یک عنصر
اولیه زمینهسازی عمومی، material-ripple اکنون یک تابع createRippleModifierNode()
را نمایان میکند. این تابع به کتابخانههای سیستم طراحی امکان میدهد تا پیادهسازی wrapper مرتبه بالاتری ایجاد کنند که مقادیر زمینهشان را پُرسمان میکند و سپس پیادهسازی موج را به گره ایجادشده توسط این تابع واگذار میکند.
این امکان به سیستمهای طراحی میدهد تا مستقیماً آنچه را نیاز دارند پُرسمان کنند و هر لایه زمینهسازی پیکربندیپذیر موردنیاز کاربر را در بالا آشکار کنند، بدون اینکه مجبور باشند با آنچه در لایه material-ripple ارائه میشود مطابقت داشته باشند. این تغییر همچنین بهطور واضحتر مشخص میکند که موج به کدام زمینه/مشخصات پایبند است، زیرا این قرارداد را خود API موج تعریف میکند، نه اینکه بهطور ضمنی از زمینه مشتق شود.
برای راهنمایی، پیادهسازی API موج را در کتابخانههای Material ببینید و فراخوانیهای محلیهای ترکیب Material را درصورت نیاز برای سیستم طراحی خودتان جایگزین کنید.
انتقال از 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.برای مثال، تکهکد زیر از میاناهای برنامهسازی کاربردی منسوخ استفاده میکند:
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() } } }
برای پیادهسازی
IndicationNodeFactory،ScaleIndicationرا انتقال دهید. ازآنجاییکه منطق مجموعه اکنون به گره منتقل شده است، این یک شیء کارخانه بسیار ساده است که تنها مسئولیت آن ایجاد نمونه گره است.برای مثال، تکهکد زیر از میاناهای برنامهسازی کاربردی منسوخ استفاده میکند:
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 برای یک
عنصر استفاده کنید. بااینحال، در موارد نادری که بهصورت دستی بااستفاده از rememberUpdatedInstance
IndicationInstance ایجاد میکنید، باید پیادهسازیتان را بهروز کنید تا بررسی کند Indication
IndicationNodeFactory است یا نه تا بتوانید از پیادهسازی سبکتری استفاده کنید. برای مثال، Modifier.indication اگر IndicationNodeFactory باشد،
بهصورت داخلی به گره ایجادشده واگذار میکند. اگر
ندارید، از Modifier.composed برای تماس با rememberUpdatedInstance استفاده خواهد شد.