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