النقل إلى واجهات برمجة تطبيقات المؤشرات والتموج

لتحسين أداء إنشاء المكوّنات التفاعلية التي تستخدم Modifier.clickable، أطلقنا واجهات برمجة تطبيقات جديدة. تتيح واجهات برمجة التطبيقات هذه عمليات تنفيذ Indication أكثر فعالية، مثل التموجات.

تتضمّن الإصدارات androidx.compose.foundation:foundation:1.7.0+ وandroidx.compose.material:material-ripple:1.7.0+ تغييرات واجهة برمجة التطبيقات التالية:

تم إيقافها نهائيًا

الاستبدال

Indication#rememberUpdatedInstance

IndicationNodeFactory

rememberRipple()

بدلاً من ذلك، يتم توفير واجهات برمجة تطبيقات ripple() جديدة في مكتبات Material.

ملاحظة: في هذا السياق، تشير "مكتبات المواد" إلى androidx.compose.material:material وandroidx.compose.material3:material3 وandroidx.wear.compose:compose-material وandroidx.wear.compose:compose-material3..

RippleTheme

يمكنك إجراء ذلك بإحدى طريقتين:

  • استخدام واجهات برمجة التطبيقات في مكتبة Material RippleConfiguration
  • إنشاء تنفيذ مخصص لنظام التصميم

توضّح هذه الصفحة تأثير التغيير في السلوك وتعليمات نقل البيانات إلى واجهات برمجة التطبيقات الجديدة.

تغيير السلوك

تتضمّن إصدارات المكتبة التالية تغييرًا في سلوك التموج:

  • 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() من المكتبة المعنية. تنشئ واجهة برمجة التطبيقات هذه تأثيرًا تموجيًا باستخدام قيم مشتقة من واجهات برمجة التطبيقات الخاصة بمظهر 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 لتحديد سلوك التموج على مستوى المظهر. كانت هذه النقطة في الأساس نقطة تكامل بين العناصر المحلية المخصّصة لتكوين نظام التصميم وRipple. بدلاً من عرض عنصر أساسي عام لتطبيق السمات، يعرض material-ripple الآن دالة createRippleModifierNode(). تتيح هذه الدالة لمكتبات نظام التصميم إنشاء تنفيذ wrapper ذي ترتيب أعلى، والذي يستعلم عن قيم المظهر ثم يفوّض تنفيذ التموج إلى العقدة التي أنشأتها هذه الدالة.

يتيح ذلك لأنظمة التصميم الاستعلام مباشرةً عن ما تحتاج إليه، وعرض أي طبقات سمات قابلة للضبط من قِبل المستخدمين في الأعلى بدون الحاجة إلى الالتزام بما يتم توفيره في طبقة material-ripple. يؤدي هذا التغيير أيضًا إلى توضيح المظهر/المواصفات التي يتوافق معها التأثير بشكل أكبر، لأنّ واجهة برمجة التطبيقات الخاصة بالتأثير هي التي تحدّد هذا العقد، بدلاً من أن يتم استنتاجه ضمنيًا من المظهر.

للحصول على إرشادات، راجِع تنفيذ واجهة برمجة التطبيقات 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()
        }
    }
}

يمكنك نقل هذه البيانات في خطوتَين:

  1. نقل 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()
            }
        }
    }

  2. نقل البيانات من ScaleIndication لتنفيذ IndicationNodeFactory بما أنّ منطق التجميع قد تم نقله إلى العقدة، فإنّ هذا العنصر هو عنصر مصنع بسيط جدًا، ومسؤوليته الوحيدة هي إنشاء مثيل للعقدة.

    على سبيل المثال، يستخدم المقتطف التالي واجهات برمجة التطبيقات المتوقّفة نهائيًا:

    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.