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

لتحسين أداء تركيبة المكونات التفاعلية التي تستخدم 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

ويمكنك الاختيار ممّا يلي:

  • استخدام واجهات برمجة تطبيقات 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 في تطبيقك، لن تستخدم مكونات المواد هذه القيم.

يوضِّح القسم التالي كيفية العودة مؤقتًا إلى السلوك القديم بدون نقل البيانات، إلا أنّنا ننصح بالانتقال إلى واجهات برمجة التطبيقات الجديدة. للحصول على تعليمات حول نقل البيانات، يمكنك الاطّلاع على نقل البيانات من rememberRipple إلى ripple والأقسام اللاحقة.

ترقية إصدار مكتبة Material بدون نقل البيانات

لإزالة حظر ترقية إصدارات المكتبات، يمكنك استخدام واجهة برمجة التطبيقات LocalUseFallbackRippleImplementation CompositionLocal المؤقتة لضبط مكوّنات Material من أجل العودة إلى السلوك القديم:

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

احرص على توفير هذه الخوارزمية خارج MaterialTheme حتى يمكن توفير الأمواج القديمة من خلال LocalIndication.

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

نقل البيانات من rememberRipple إلى ripple

استخدام مكتبة Material

إذا كنت تستخدم مكتبة Material، استبدِل rememberRipple() مباشرةً باستدعاء ripple() من المكتبة المقابلة. تنشئ واجهة برمجة التطبيقات هذه تمويجًا باستخدام القيم المستمدة من واجهات برمجة تطبيقات Materials. بعد ذلك، مرِّر الكائن الذي تم إرجاعه إلى 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"

إيقاف خيار تغيير السلوك مؤقتًا

وتشتمل مكتبات Material على سمة 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)
    }
}

للحصول على مزيد من المعلومات، يمكنك الاطّلاع على القسم ترقية إصدار مكتبة Material بدون نقل البيانات.

استخدام 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 {
            // ...
        }
    }

عليك تعديل المقتطف أعلاه لإجراء ما يلي:

@OptIn(ExperimentalMaterialApi::class)
private val DisabledRippleConfiguration =
    RippleConfiguration(isEnabled = false)

// ...
    CompositionLocalProvider(LocalRippleConfiguration provides DisabledRippleConfiguration) {
        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. هذا التغيير يجعل أيضًا أكثر وضوحًا الموضوع/المواصفات التي يتوافق معها التموج، لأنها واجهة برمجة التطبيقات لتموجة نفسها هي التي تحدد هذا العقد، بدلاً من اشتقاقها بشكل ضمني من الموضوع.

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

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

  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.