الطبقات المعمارية في Jetpack Compose

تقدّم هذه الصفحة نظرة عامة عالية المستوى على الطبقات المعمارية التي يتكوّن منها Jetpack Compose، والمبادئ الأساسية التي يستند إليها هذا التصميم.

إنّ Jetpack Compose ليس مشروعًا واحدًا متكاملاً، بل تم إنشاؤه من عدد من الوحدات التي يتم تجميعها معًا لتشكيل حزمة كاملة. إنّ فهم الوحدات المختلفة التي يتألف منها Jetpack Compose يتيح لك ما يلي:

  • استخدام مستوى التجريد المناسب لإنشاء تطبيقك أو مكتبتك
  • التعرّف على الحالات التي يمكنك فيها الانتقال إلى مستوى أدنى للحصول على المزيد من التحكّم أو التخصيص
  • تقليل التبعيات

الطبقات

الطبقات الرئيسية في Jetpack Compose هي:

الشكل 1. الطبقات الرئيسية في Jetpack Compose

ويتم إنشاء كل طبقة استنادًا إلى المستويات الأدنى، مع دمج الوظائف لإنشاء مكونات ذات مستوى أعلى. تستند كل طبقة إلى واجهات برمجة التطبيقات العامة للطبقات الأدنى للتحقّق من حدود الوحدات وإتاحة إمكانية استبدال أي طبقة عند الحاجة. لنتعرّف على هذه الطبقات من الأسفل إلى الأعلى.

وقت التشغيل
توفّر هذه الوحدة أساسيات وقت تشغيل Compose، مثل remember و mutableStateOf والشرح التوضيحي @Composable وSideEffect. يمكنك الاستفادة من هذه الطبقة مباشرةً إذا كنت بحاجة فقط إلى إمكانات إدارة بنية Compose، وليس واجهة المستخدم.
UI
تتكوّن طبقة واجهة المستخدم من وحدات متعدّدة ( ui-text و ui-graphics و ui-tooling وما إلى ذلك). تنفِّذ هذه الوحدات أساسيات أدوات واجهة المستخدم، مثل LayoutNode، Modifier، ومعالجات الإدخال، والتنسيقات المخصّصة، والرسم. يمكنك الاستفادة من هذه الطبقة إذا كنت بحاجة إلى المفاهيم الأساسية فقط لمجموعة أدوات واجهة المستخدم.
Foundation
يوفّر هذا النموذج وحدات أساسية مستقلة عن نظام التصميم لواجهة مستخدم Compose، مثل Row وColumn وLazyColumn والتعرّف على إيماءات معيّنة وما إلى ذلك. يمكنك الاستفادة من الطبقة الأساسية لإنشاء نظام التصميم الخاص بك.
المادة الأساسية
توفّر هذه الوحدة تنفيذًا لنظام Material Design في واجهة مستخدم Compose، وتوفّر نظامًا لتطبيق السمات ومكوّنات منمّقة ومؤشرات تموّج وأيقونات. يمكنك الاستفادة من هذه الطبقة عند استخدام Material Design في تطبيقك.

مبادئ التصميم

من المبادئ التوجيهية في Jetpack Compose توفير أجزاء صغيرة ومركزة من الوظائف التي يمكن تجميعها (أو إنشاؤها) معًا، بدلاً من عدد قليل من المكوّنات المتكاملة. لهذا الأسلوب عدد من المزايا.

التحكّم

تميل المكوّنات ذات المستوى الأعلى إلى تقديم المزيد من الميزات، ولكنّها تحدّ من مقدار التحكّم المباشر المتاح لك. إذا كنت بحاجة إلى مزيد من التحكّم، يمكنك النقر على السهم المتّجه للأسفل لاستخدام مكوّن على مستوى أدنى.

على سبيل المثال، إذا أردت تحريك لون أحد المكوّنات، يمكنك استخدام واجهة برمجة التطبيقات animateColorAsState على النحو التالي:

val color = animateColorAsState(if (condition) Color.Green else Color.Red)

ومع ذلك، إذا كنت بحاجة إلى أن يبدأ المكوّن باللون الرمادي دائمًا، لا يمكنك إجراء ذلك باستخدام واجهة برمجة التطبيقات هذه. بدلاً من ذلك، يمكنك الانتقال إلى المستوى الأدنى Animatable من واجهة برمجة التطبيقات:

val color = remember { Animatable(Color.Gray) }
LaunchedEffect(condition) {
    color.animateTo(if (condition) Color.Green else Color.Red)
}

تم إنشاء واجهة برمجة التطبيقات ذات المستوى الأعلى animateColorAsState استنادًا إلى واجهة برمجة التطبيقات ذات المستوى الأدنى Animatable. إنّ استخدام واجهة برمجة التطبيقات ذات المستوى الأدنى أكثر تعقيدًا، ولكنّه يوفّر المزيد من التحكّم. اختَر مستوى التجريد الأنسب لاحتياجاتك.

التخصيص

يؤدي تجميع المكوّنات ذات المستوى الأعلى من وحدات البناء الأصغر إلى تسهيل عملية تخصيص المكوّنات بشكل كبير عند الحاجة إلى ذلك. على سبيل المثال، ضع في اعتبارك عملية التنفيذ Button التي توفّرها طبقة Material:

@Composable
fun Button(
    // …
    content: @Composable RowScope.() -> Unit
) {
    Surface(/* … */) {
        CompositionLocalProvider(/* … */) { // set LocalContentAlpha
            ProvideTextStyle(MaterialTheme.typography.button) {
                Row(
                    // …
                    content = content
                )
            }
        }
    }
}

يتم تجميع Button من 4 مكوّنات:

  1. مادة Surface توفّر الخلفية والشكل والتعامل مع النقرات وما إلى ذلك

  2. A CompositionLocalProvider الذي يغيّر قيمة ألفا للمحتوى عند تفعيل الزر أو إيقافه

  3. يضبط A ProvideTextStyle نمط النص التلقائي الذي سيتم استخدامه

  4. يوفر Row سياسة التنسيق التلقائية لمحتوى الزر

لقد حذفنا بعض المَعلمات والتعليقات لتوضيح البنية، ولكن يبلغ عدد أسطر الرمز البرمجي للمكوّن بأكمله 40 سطرًا تقريبًا لأنّه يجمع هذه المكوّنات الأربعة لتنفيذ الزر. تتضمّن المكوّنات، مثل Button، خيارات محدّدة بشأن المَعلمات التي تعرضها، مع تحقيق التوازن بين إتاحة التخصيصات الشائعة وتجنُّب كثرة المَعلمات التي قد تجعل المكوّن أكثر صعوبة في الاستخدام. على سبيل المثال، توفّر مكوّنات Material عمليات تخصيص محدّدة في نظام Material Design، ما يسهّل اتّباع مبادئ تصميم Material.

ومع ذلك، إذا كنت تريد إجراء تخصيص يتجاوز مَعلمات أحد المكوّنات، يمكنك "النزول" إلى مستوى أدنى وتفريغ مكوّن. على سبيل المثال، يحدّد Material Design أنّه يجب أن تكون خلفية الأزرار بلون خالص. إذا كنت بحاجة إلى خلفية متدرّجة الألوان، لا يتيح لك هذا الخيار استخدام المَعلمات Button. في هذه الحالة، يمكنك استخدام تنفيذ Material Button كمرجع وإنشاء المكوّن الخاص بك:

@Composable
fun GradientButton(
    // …
    background: List<Color>,
    modifier: Modifier = Modifier,
    content: @Composable RowScope.() -> Unit
) {
    Row(
        // …
        modifier = modifier
            .clickable(onClick = {})
            .background(
                Brush.horizontalGradient(background)
            )
    ) {
        CompositionLocalProvider(/* … */) { // set material LocalContentAlpha
            ProvideTextStyle(MaterialTheme.typography.button) {
                content()
            }
        }
    }
}

يستمر التنفيذ أعلاه في استخدام مكوّنات من طبقة Material، مثل مفاهيم Material الخاصة بشفافية المحتوى الحالي ونمط النص الحالي. ومع ذلك، يتم استبدال المادة Surface بالمادة Row وتنسيقها للحصول على المظهر المطلوب.

إذا كنت لا تريد استخدام مفاهيم Material مطلقًا، مثلاً إذا كنت بصدد إنشاء نظام تصميم مخصّص، يمكنك الانتقال إلى استخدام مكوّنات الطبقة الأساسية فقط:

@Composable
fun BespokeButton(
    // …
    backgroundColor: Color,
    modifier: Modifier = Modifier,
    content: @Composable RowScope.() -> Unit
) {
    Row(
        // …
        modifier = modifier
            .clickable(onClick = {})
            .background(backgroundColor)
    ) {
        // No Material components used
        content()
    }
}

تحتفظ Jetpack Compose بأبسط الأسماء للمكوّنات ذات المستوى الأعلى. على سبيل المثال، androidx.compose.material.Text مبني على androidx.compose.foundation.text.BasicText. ويتيح ذلك توفير عملية التنفيذ الخاصة بك مع الاسم الأكثر قابلية للاكتشاف إذا أردت استبدال المستويات الأعلى.

اختيار التجريد المناسب

تعتمد فلسفة Compose في إنشاء المكوّنات القابلة لإعادة الاستخدام على عدم اللجوء دائمًا إلى وحدات الإنشاء ذات المستوى الأدنى. لا توفّر العديد من المكوّنات ذات المستوى الأعلى وظائف أكثر فحسب، بل إنّها غالبًا ما تتضمّن أفضل الممارسات، مثل إتاحة استخدامها من قِبل ذوي الاحتياجات الخاصة.

على سبيل المثال، إذا أردت إضافة ميزة التعامل بالإيماءات إلى مكوّنك المخصّص، يمكنك إنشاء هذه الميزة من البداية باستخدام Modifier.pointerInput، ولكن هناك مكوّنات أخرى ذات مستوى أعلى تم إنشاؤها استنادًا إلى هذا المكوّن، وقد توفّر نقطة بداية أفضل، مثل Modifier.draggable أو Modifier.scrollable أو Modifier.swipeable.

بشكل عام، ننصحك بالاستفادة من المكوّن الأعلى مستوى الذي يوفّر الوظائف التي تحتاج إليها للاستفادة من أفضل الممارسات التي يتضمّنها.

مزيد من المعلومات

يمكنك الاطّلاع على نموذج Jetsnack للحصول على مثال حول إنشاء نظام تصميم مخصّص.