برای بهبود عملکرد ترکیببندی مؤلفههای تعاملی که از
Modifier.clickable استفاده میکنند، میاناهای برنامهسازی کاربردی جدیدی معرفی کردهایم. این میاناهای برنامهسازی کاربردی امکان پیادهسازیهای Indication کارآمدتری را فراهم میکنند، مانند موجها.
androidx.compose.foundation:foundation:1.7.0+ و
androidx.compose.material:material-ripple:1.7.0+ شامل تغییرات میانای برنامهسازی کاربردی زیر
است:
منسوخ |
جایگزینی |
|---|---|
|
|
|
بهجای آن، میاناهای برنامهسازی کاربردی توجه: در این زمینه، «کتابخانههای Material» به |
|
یکی از موارد زیر را انجام دهید:
|
این صفحه تأثیر تغییر رفتار و دستورالعملهای انتقال به میاناهای برنامهسازی کاربردی جدید را شرح میدهد.
تغییر رفتار
نسخههای کتابخانه زیر شامل تغییر رفتار موجدار است:
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
از این مقادیر استفاده نخواهند کرد.
بخشهای زیر نحوه انتقال به میاناهای برنامهسازی کاربردی جدید را شرح میدهند.
انتقال از 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 فقط برای سفارشیسازی
هر مؤلفه درنظر گرفته شدهاند. سفارشیسازی سراسری/در سطح زمینه با این میاناهای برنامهسازی کاربردی پشتیبانی نمیشود؛ برای اطلاعات بیشتر درباره این مورد استفاده، استفاده از 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 ارائه میشود مطابقت داشته باشند. این تغییر همچنین بهطور صریحتر مشخص میکند که موجک از کدام زمینه/مشخصات پیروی میکند، زیرا این خود میانای برنامهسازی کاربردی موجک است که این قرارداد را تعریف میکند، نه اینکه بهطور ضمنی از زمینه مشتق شود.
برای راهنمایی، به پیادهسازی میانای برنامهسازی کاربردی موج در کتابخانههای 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انتقال دهید. سطح میانای برنامهسازی کاربردی برایDrawModifierNodeبسیار شبیه بهIndicationInstanceاست: این میانا تابع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() } } }
ScaleIndicationرا برای پیادهسازیIndicationNodeFactoryانتقال دهید. Because the collection logic is now moved into the node, this is a very simple factory object whose only responsibility is to create a node instance.برای مثال، قطعه کد زیر از میاناهای برنامهسازی کاربردی منسوخشده استفاده میکند:
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 استفاده خواهد کرد.