به API های Indication و Ripple مهاجرت کنید

برای بهبود عملکرد ترکیب اجزای تعاملی که از Modifier.clickable استفاده می کنند، API های جدیدی را معرفی کرده ایم. این APIها امکان پیاده سازی Indication کارآمدتر مانند امواج را فراهم می کنند.

androidx.compose.foundation:foundation:1.7.0+ و androidx.compose.material:material-ripple:1.7.0+ شامل تغییرات API زیر هستند:

منسوخ

جایگزینی

Indication#rememberUpdatedInstance

IndicationNodeFactory

rememberRipple()

API های جدید ripple() در کتابخانه های Material ارائه شده اند.

توجه: در این زمینه، "کتابخانه های مواد" به androidx.compose.material:material ، androidx.compose.material3:material3 ، androidx.wear.compose:compose-material و androidx.wear.compose:compose-material3.

RippleTheme

یا:

  • از کتابخانه Material RippleConfiguration APIs یا
  • پیاده سازی ریپل سیستم طراحی خود را بسازید

این صفحه تأثیر تغییر رفتار و دستورالعمل‌های مهاجرت به APIهای جدید را شرح می‌دهد.

تغییر رفتار

نسخه‌های کتابخانه زیر شامل تغییر رفتار ریپل است:

  • 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های ریپل جدید استفاده می کنند. در نتیجه، LocalRippleTheme پرس و جو نمی کنند. بنابراین، اگر LocalRippleTheme در برنامه خود تنظیم کنید، اجزای Material از این مقادیر استفاده نخواهند کرد .

بخش زیر نحوه بازگشت موقت به رفتار قبلی بدون مهاجرت را شرح می دهد. با این حال، ما مهاجرت به API های جدید را توصیه می کنیم. برای دستورالعمل‌های مهاجرت، به انتقال از rememberRipple به ripple و بخش‌های بعدی مراجعه کنید.

نسخه کتابخانه Material را بدون مهاجرت ارتقا دهید

برای رفع انسداد نسخه‌های کتابخانه در حال ارتقا، می‌توانید از LocalUseFallbackRippleImplementation CompositionLocal API موقت برای پیکربندی اجزای Material برای بازگشت به رفتار قبلی استفاده کنید:

CompositionLocalProvider(LocalUseFallbackRippleImplementation provides true) {
    MaterialTheme {
        App()
    }
}

مطمئن شوید که این را خارج از MaterialTheme ارائه می‌کنید تا ریپل‌های قدیمی از طریق LocalIndication ارائه شوند.

بخش های زیر نحوه مهاجرت به 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() دیگر یک تابع قابل ترکیب نیست و نیازی به به خاطر سپردن ندارد. همچنین می‌توان از آن در چندین مؤلفه، مشابه اصلاح‌کننده‌ها، استفاده مجدد کرد، بنابراین برای ذخیره تخصیص‌ها، ایجاد ریپل را به یک مقدار سطح بالا استخراج کنید.

پیاده سازی سیستم طراحی سفارشی

اگر سیستم طراحی خود را پیاده‌سازی می‌کنید، و قبلاً از rememberRipple() به همراه یک RippleTheme سفارشی برای پیکربندی ریپل استفاده می‌کردید، در عوض باید API ریپل خود را ارائه کنید که APIهای گره ریپل را که در material-ripple در معرض دید قرار می‌گیرند ارائه دهید. سپس، اجزای شما می توانند از ریپل خود استفاده کنند که مقادیر تم شما را مستقیما مصرف می کند. برای اطلاعات بیشتر، مهاجرت از RippleTheme را ببینید.

از RippleTheme مهاجرت کنید

انصراف موقت از تغییر رفتار

کتابخانه های متریال دارای یک CompositionLocal به نام LocalUseFallbackRippleImplementation موقت هستند که می توانید از آن برای پیکربندی همه اجزای Material استفاده کنید تا به استفاده از rememberRipple برگردید. به این ترتیب، rememberRipple به پرس و جوی LocalRippleTheme ادامه می دهد.

قطعه کد زیر نحوه استفاده از LocalUseFallbackRippleImplementation CompositionLocal API را نشان می دهد:

CompositionLocalProvider(LocalUseFallbackRippleImplementation provides true) {
    MaterialTheme {
        App()
    }
}

اگر از یک طرح زمینه برنامه سفارشی استفاده می‌کنید که بر روی Material ساخته شده است، می‌توانید با خیال راحت ترکیب محلی را به عنوان بخشی از طرح زمینه برنامه خود ارائه دهید:

@OptIn(ExperimentalMaterialApi::class)
@Composable
fun MyAppTheme(content: @Composable () -> Unit) {
    CompositionLocalProvider(LocalUseFallbackRippleImplementation provides true) {
        MaterialTheme(content = content)
    }
}

برای اطلاعات بیشتر، به بخش Upgrade Material Library بدون مهاجرت مراجعه کنید.

استفاده از 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 های آزمایشی هستند و فقط برای سفارشی سازی هر جزء در نظر گرفته شده اند.

به عنوان مثال، قطعه زیر از 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 برای تعریف رفتار ریپل در سطح تم استفاده کنید. این اساساً یک نقطه ادغام بین افراد محلی ترکیب سیستم طراحی سفارشی و ریپل بود. به‌جای افشای یک موضوع اولیه، material-ripple اکنون یک تابع createRippleModifierNode() را نمایش می‌دهد. این تابع به کتابخانه‌های سیستم طراحی اجازه می‌دهد تا پیاده‌سازی wrapper مرتبه بالاتر را ایجاد کنند، که مقادیر موضوع خود را پرس و جو کرده و سپس پیاده‌سازی ریپل را به گره ایجاد شده توسط این تابع واگذار کند.

این به سیستم‌های طراحی اجازه می‌دهد تا مستقیماً آنچه را که نیاز دارند پرس و جو کنند و لایه‌های موضوعی قابل تنظیم توسط کاربر مورد نیاز را بدون نیاز به مطابقت با آنچه در لایه material-ripple ارائه شده است، در معرض دید قرار دهند. این تغییر همچنین واضح‌تر می‌کند که ریپل با چه تم/مشخصاتی مطابقت دارد، زیرا این خود API ریپل است که آن قرارداد را تعریف می‌کند، نه اینکه به طور ضمنی از موضوع مشتق شود.

برای راهنمایی، اجرای Ripple API را در کتابخانه‌های 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()
        }
    }
}

شما می توانید این را در دو مرحله انتقال دهید:

  1. ScaleIndicationInstance را انتقال دهید تا DrawModifierNode باشد. سطح API برای DrawModifierNode بسیار شبیه به IndicationInstance است: تابع ContentDrawScope#draw() را نشان می دهد که از نظر عملکردی معادل IndicationInstance#drawContent() است. شما باید آن تابع را تغییر دهید، و سپس به جای Indication ، منطق collectLatest را مستقیماً در داخل گره پیاده سازی کنید.

    به عنوان مثال، قطعه زیر از 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()
            }
        }
    }

  2. ScaleIndication برای پیاده سازی IndicationNodeFactory منتقل کنید. از آنجا که منطق مجموعه اکنون به گره منتقل شده است، این یک شی کارخانه بسیار ساده است که تنها مسئولیت آن ایجاد یک نمونه گره است.

    به عنوان مثال، قطعه زیر از 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 است یا خیر تا بتوانید از پیاده‌سازی سبک‌تر استفاده کنید. به عنوان مثال، اگر یک IndicationNodeFactory باشد، Modifier.indication به صورت داخلی به گره ایجاد شده واگذار می شود. اگر نه، از Modifier.composed برای فراخوانی rememberUpdatedInstance استفاده می کند.