لتحسين أداء إنشاء المكوّنات التفاعلية التي تستخدم
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 هذه القيم.
توضّح الأقسام التالية كيفية نقل البيانات إلى واجهات برمجة التطبيقات الجديدة.
نقل البيانات من 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() } } }
يمكنك نقل هذه البيانات في خطوتَين:
نقل
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بما أنّ منطق التجميع قد تم نقله إلى العقدة، فإنّ هذا العنصر هو عنصر مصنع بسيط جدًا، ومسؤوليته الوحيدة هي إنشاء مثيل للعقدة.على سبيل المثال، يستخدم المقتطف التالي واجهات برمجة التطبيقات المتوقّفة نهائيًا:
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.