استخدام الخطوط

توضّح هذه الصفحة كيفية ضبط الخطوط في تطبيق Compose.

ضبط الخط

تحتوي Text على المَعلمة fontFamily التي تتيح ضبط الخط المستخدَم في العنصر القابل للإنشاء. يتم تضمين مجموعات الخطوط serif وsans-serif وmonospace وcursive تلقائيًا:

@Composable
fun DifferentFonts() {
    Column {
        Text("Hello World", fontFamily = FontFamily.Serif)
        Text("Hello World", fontFamily = FontFamily.SansSerif)
    }
}

الكلمات

يمكنك استخدام السمة fontFamily للعمل مع الخطوط المخصّصة وأنواع الخطوط المحدّدة في المجلد res/font:

تمثيل رسومي للمجلد res > font في بيئة التطوير

يوضّح هذا المثال كيفية تحديد fontFamily استنادًا إلى ملفات الخطوط هذه وباستخدام الدالة Font:

val firaSansFamily = FontFamily(
    Font(R.font.firasans_light, FontWeight.Light),
    Font(R.font.firasans_regular, FontWeight.Normal),
    Font(R.font.firasans_italic, FontWeight.Normal, FontStyle.Italic),
    Font(R.font.firasans_medium, FontWeight.Medium),
    Font(R.font.firasans_bold, FontWeight.Bold)
)

يمكنك تمرير fontFamily هذا إلى العنصر القابل للإنشاء Text. بما أنّ fontFamily يمكن أن يتضمّن أوزانًا مختلفة، يمكنك ضبط fontWeight يدويًا لاختيار الوزن المناسب للنص:

Column {
    Text(text = "text", fontFamily = firaSansFamily, fontWeight = FontWeight.Light)
    Text(text = "text", fontFamily = firaSansFamily, fontWeight = FontWeight.Normal)
    Text(
        text = "text",
        fontFamily = firaSansFamily,
        fontWeight = FontWeight.Normal,
        fontStyle = FontStyle.Italic
    )
    Text(text = "text", fontFamily = firaSansFamily, fontWeight = FontWeight.Medium)
    Text(text = "text", fontFamily = firaSansFamily, fontWeight = FontWeight.Bold)
}

الكلمات

للتعرّف على كيفية ضبط الطباعة في تطبيقك بالكامل، اطّلِع على أنظمة التصميم المخصّصة في Compose.

الخطوط القابلة للتنزيل

بدءًا من Compose 1.2.0، يمكنك استخدام واجهة برمجة التطبيقات الخاصة بالخطوط القابلة للتنزيل في تطبيق Compose لتنزيل خطوط Google بشكل غير متزامن واستخدامها في تطبيقك.

لا تتوفّر حاليًا إمكانية استخدام الخطوط القابلة للتنزيل التي يوفّرها مقدّمو خدمات مخصّصون.

استخدام الخطوط القابلة للتنزيل آليًا

لتنزيل خط برمجيًا من داخل تطبيقك، اتّبِع الخطوات التالية:

  1. أضِف التبعية:

    Groovy

    dependencies {
        ...
        implementation "androidx.compose.ui:ui-text-google-fonts:1.8.1"
    }

    Kotlin

    dependencies {
        ...
        implementation("androidx.compose.ui:ui-text-google-fonts:1.8.1")
    }
  2. عليك تهيئة GoogleFont.Provider باستخدام بيانات اعتماد Google Fonts:
    val provider = GoogleFont.Provider(
        providerAuthority = "com.google.android.gms.fonts",
        providerPackage = "com.google.android.gms",
        certificates = R.array.com_google_android_gms_fonts_certs
    )
    المَعلمات التي يتلقّاها مقدّم الخدمة هي:
    • مرجع مقدّم الخطوط في Google Fonts
    • حزمة موفِّر الخطوط لإثبات هوية الموفِّر
    • قائمة بمجموعات التجزئة للشهادات التي سيتم استخدامها للتحقّق من هوية الموفّر. يمكنك العثور على قيم التجزئة المطلوبة لموفّر Google Fonts في ملف font_certs.xml في تطبيق Jetchat النموذجي.
  3. عرِّف FontFamily:
    // ...
     import androidx.compose.ui.text.googlefonts.GoogleFont
     import androidx.compose.ui.text.font.FontFamily
     import androidx.compose.ui.text.googlefonts.Font
     // ...
    
    val fontName = GoogleFont("Lobster Two")
    
    val fontFamily = FontFamily(
        Font(googleFont = fontName, fontProvider = provider)
    )
    يمكنك طلب البحث عن مَعلمات أخرى للخط، مثل السُمك والنمط، باستخدام FontWeight و FontStyle على التوالي:
    // ...
     import androidx.compose.ui.text.googlefonts.GoogleFont
     import androidx.compose.ui.text.font.FontFamily
     import androidx.compose.ui.text.googlefonts.Font
     // ...
    
    val fontName = GoogleFont("Lobster Two")
    
    val fontFamily = FontFamily(
        Font(
            googleFont = fontName,
            fontProvider = provider,
            weight = FontWeight.Bold,
            style = FontStyle.Italic
        )
    )
  4. اضبط FontFamily ليتم استخدامه في دالة Text القابلة للإنشاء:

Text(
    fontFamily = fontFamily, text = "Hello World!"
)

يمكنك أيضًا تحديد أسلوب الخط لاستخدام FontFamily:

val MyTypography = Typography(
    bodyMedium = TextStyle(
        fontFamily = fontFamily, fontWeight = FontWeight.Normal, fontSize = 12.sp/*...*/
    ),
    bodyLarge = TextStyle(
        fontFamily = fontFamily,
        fontWeight = FontWeight.Bold,
        letterSpacing = 2.sp,
        /*...*/
    ),
    headlineMedium = TextStyle(
        fontFamily = fontFamily, fontWeight = FontWeight.SemiBold/*...*/
    ),
    /*...*/
)

بعد ذلك، اضبط أسلوب الخط على مظهر تطبيقك:

MyAppTheme(
    typography = MyTypography
)/*...*/

للاطّلاع على مثال لتطبيق يستخدم الخطوط القابلة للتنزيل في Compose مع Material3، راجِع تطبيق Jetchat التجريبي.

إضافة خطوط احتياطية

يمكنك تحديد سلسلة من الخطوط الاحتياطية في حال تعذّر تنزيل الخط بشكل صحيح. على سبيل المثال، إذا كان خطك القابل للتنزيل معرَّفًا على النحو التالي:

// ...
 import androidx.compose.ui.text.googlefonts.Font
 // ...

val fontName = GoogleFont("Lobster Two")

val fontFamily = FontFamily(
    Font(googleFont = fontName, fontProvider = provider),
    Font(googleFont = fontName, fontProvider = provider, weight = FontWeight.Bold)
)

يمكنك تحديد الإعدادات التلقائية للخط لكلا الوزنين على النحو التالي:

// ...
 import androidx.compose.ui.text.font.Font
 import androidx.compose.ui.text.googlefonts.Font
 // ...

val fontName = GoogleFont("Lobster Two")

val fontFamily = FontFamily(
    Font(googleFont = fontName, fontProvider = provider),
    Font(resId = R.font.my_font_regular),
    Font(googleFont = fontName, fontProvider = provider, weight = FontWeight.Bold),
    Font(resId = R.font.my_font_regular_bold, weight = FontWeight.Bold)
)

تأكَّد من إضافة عمليات الاستيراد الصحيحة.

يؤدي تحديد FontFamily على هذا النحو إلى إنشاء FontFamily يحتوي على سلسلتَين، واحدة لكل وزن. سيحاول آلية التحميل أولاً حل الخط على الإنترنت، ثم الخط الموجود في مجلد موارد R.font المحلي.

تصحيح الأخطاء في عملية التنفيذ

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

ابدأ بإنشاء CoroutineExceptionHandler:

val handler = CoroutineExceptionHandler { _, throwable ->
    // process the Throwable
    Log.e(TAG, "There has been an issue: ", throwable)
}

مرِّرها إلى الطريقة createFontFamilyResolver ليستخدم محلّل الاسم المعالج الجديد:

CompositionLocalProvider(
    LocalFontFamilyResolver provides createFontFamilyResolver(LocalContext.current, handler)
) {
    Column {
        Text(
            text = "Hello World!", style = MaterialTheme.typography.bodyMedium
        )
    }
}

يمكنك أيضًا استخدام واجهة برمجة التطبيقات isAvailableOnDevice الخاصة بموفّر الخدمة لاختبار ما إذا كان موفّر الخدمة متاحًا وتم ضبط الشهادات بشكل صحيح. لإجراء ذلك، يمكنك استدعاء طريقة isAvailableOnDevice التي تعرض القيمة false إذا تم ضبط الموفّر بشكل غير صحيح.

val context = LocalContext.current
LaunchedEffect(Unit) {
    if (provider.isAvailableOnDevice(context)) {
        Log.d(TAG, "Success!")
    }
}

المحاذير

تستغرق Google Fonts عدة أشهر لإتاحة الخطوط الجديدة على Android. هناك فترة زمنية بين وقت إضافة خط في fonts.google.com ووقت إتاحته من خلال واجهة برمجة التطبيقات للخطوط القابلة للتنزيل (إما في نظام View أو في Compose). قد يتعذّر تحميل الخطوط التي تمت إضافتها حديثًا في تطبيقك مع ظهور IllegalStateException. لمساعدة المطوّرين في تحديد هذا الخطأ من بين أنواع أخرى من أخطاء تحميل الخطوط، أضفنا رسائل وصفية للاستثناء في Compose مع التغييرات هنا. إذا رصدت أي مشاكل، يُرجى الإبلاغ عنها باستخدام أداة تتبُّع المشاكل.

استخدام الخطوط المتغيّرة

الخط المتغير هو تنسيق خط يتيح لملف خط واحد أن يحتوي على أنماط مختلفة. باستخدام الخطوط المتغيرة، يمكنك تعديل المحاور (أو المَعلمات) لإنشاء النمط المفضّل لديك. يمكن أن تكون هذه المحاور عادية، مثل الوزن والعرض والميلان والخط المائل، أو مخصّصة، وتختلف بين الخطوط المتغيرة.

خمسة إعدادات للخط المتغيّر نفسه بقيم مختلفة للمحور
الشكل 1. نص يستخدم خطًا متغيرًا واحدًا تم تخصيصه باستخدام قيم مختلفة للمحور.

يتيح لك استخدام الخطوط المتغيرة بدلاً من ملفات الخطوط العادية الحصول على ملف خط واحد فقط بدلاً من ملفات متعددة.

لمزيد من المعلومات الأساسية حول الخطوط المتغيرة، يُرجى الاطّلاع على قاعدة المعرفة في Google Fonts والفهرس الكامل للخطوط المتغيرة المتاحة وجدول للمحاور المتوافقة مع كل خط.

يوضّح لك هذا المستند كيفية تنفيذ خط متغير في تطبيق Compose.

تحميل خط متغيّر

  1. نزِّل خطوط المتغيرات التي تريد استخدامها (مثل Roboto Flex) وضعها في المجلد app/res/font في تطبيقك. تأكَّد من أنّttf الملف الذي تضيفه هو إصدار الخط المتغيّر من الخط، وأنّ اسم ملف الخط يتضمّن أحرفًا صغيرة فقط ولا يحتوي على أي رموز خاصة.

  2. لتحميل خط متغير، حدِّد FontFamily باستخدام الخط الموضوع في الدليل res/font/:

    // In Typography.kt
    @OptIn(ExperimentalTextApi::class)
    val displayLargeFontFamily =
        FontFamily(
            Font(
                R.font.robotoflex_variable,
                variationSettings = FontVariation.Settings(
                    FontVariation.weight(950),
                    FontVariation.width(30f),
                    FontVariation.slant(-6f),
                )
            )
        )

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

  3. لا تتوفّر الخطوط المتغيرة إلا على إصدارات Android O والإصدارات الأحدث، لذا أضِف حاجزًا واضبط خيارًا احتياطيًا مناسبًا:

    // In Typography.kt
    val default = FontFamily(
        /*
        * This can be any font that makes sense
        */
        Font(
            R.font.robotoflex_static_regular
        )
    )
    @OptIn(ExperimentalTextApi::class)
    val displayLargeFontFamily = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        FontFamily(
            Font(
                R.font.robotoflex_variable,
                variationSettings = FontVariation.Settings(
                    FontVariation.weight(950),
                    FontVariation.width(30f),
                    FontVariation.slant(-6f),
                )
            )
        )
    } else {
        default
    }

  4. استخرِج الإعدادات إلى مجموعة من الثوابت لتسهيل إعادة استخدامها واستبدِل إعدادات الخط بهذه الثوابت:

    // VariableFontDimension.kt
    object DisplayLargeVFConfig {
        const val WEIGHT = 950
        const val WIDTH = 30f
        const val SLANT = -6f
        const val ASCENDER_HEIGHT = 800f
        const val COUNTER_WIDTH = 500
    }
    
    @OptIn(ExperimentalTextApi::class)
    val displayLargeFontFamily = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        FontFamily(
            Font(
                R.font.robotoflex_variable,
                variationSettings = FontVariation.Settings(
                    FontVariation.weight(DisplayLargeVFConfig.WEIGHT),
                    FontVariation.width(DisplayLargeVFConfig.WIDTH),
                    FontVariation.slant(DisplayLargeVFConfig.SLANT),
                )
            )
        )
    } else {
        default
    }

  5. اضبط الكتابة في Material Design 3 لاستخدام FontFamily:

    // Type.kt
    val Typography = Typography(
        displayLarge = TextStyle(
            fontFamily = displayLargeFontFamily,
            fontSize = 50.sp,
            lineHeight = 64.sp,
            letterSpacing = 0.sp,
            /***/
        )
    )

    يستخدم هذا النموذج displayLarge تصميم الخطوط في Material 3، الذي يتضمّن إعدادات مختلفة تلقائية للخطوط واستخدامات مقترَحة. على سبيل المثال، يجب استخدام displayLarge للنصوص القصيرة والمهمة، لأنّها أكبر نص على الشاشة.

    باستخدام Material 3، يمكنك تغيير القيم التلقائية TextStyle وfontFamily لتخصيص الطباعة. في المقتطف أعلاه، يمكنك ضبط مثيلات TextStyle لتخصيص إعدادات الخط لكل مجموعة خطوط.

  6. بعد تحديد الطباعة، مرِّرها إلى MaterialTheme في M3:

    MaterialTheme(
        colorScheme = MaterialTheme.colorScheme,
        typography = Typography,
        content = content
    )

  7. أخيرًا، استخدِم دالة Text قابلة للإنشاء وحدِّد النمط لأحد أنماط الطباعة المحدّدة، MaterialTheme.typography.displayLarge:

    @Composable
    @Preview
    fun CardDetails() {
        MyCustomTheme {
            Card(
                shape = RoundedCornerShape(8.dp),
                elevation = CardDefaults.cardElevation(defaultElevation = 4.dp),
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(16.dp)
            ) {
                Column(
                    modifier = Modifier.padding(16.dp)
                ) {
                    Text(
                        text = "Compose",
                        style = MaterialTheme.typography.displayLarge,
                        modifier = Modifier.padding(bottom = 8.dp),
                        maxLines = 1
                    )
                    Text(
                        text = "Beautiful UIs on Android",
                        style = MaterialTheme.typography.headlineMedium,
                        modifier = Modifier.padding(bottom = 8.dp),
                        maxLines = 2
                    )
                    Text(
                        text = "Jetpack Compose is Android’s recommended modern toolkit for building native UI. It simplifies and accelerates UI development on Android. Quickly bring your app to life with less code, powerful tools, and intuitive Kotlin APIs.",
                        style = MaterialTheme.typography.bodyLarge,
                        modifier = Modifier.padding(bottom = 8.dp),
                        maxLines = 3
                    )
                }
            }
        }
    }

    يتم ضبط كل Text عنصر قابل للإنشاء من خلال نمط Material theme الخاص به، ويحتوي على إعداد مختلف لخط متغير. يمكنك استخدام MaterialTheme.typography لاسترداد عناصر الطباعة المقدَّمة إلى العنصر القابل للإنشاء MaterialTheme M3.

ثلاثة نصوص مختلفة، يعرض كل منها إعدادات مختلفة للخط.
الشكل 2. خط متغير تم تطبيقه في ثلاث عمليات ضبط مختلفة

استخدام محاور مخصّصة

يمكن أن تحتوي الخطوط أيضًا على محاور مخصّصة. يتم تحديدها داخل ملف الخط نفسه. على سبيل المثال، يحتوي خط Roboto Flex على محور ارتفاع الجزء الصاعد ("YTAS")، الذي يضبط ارتفاع الأجزاء الصاعدة من الأحرف الصغيرة، بينما يضبط محور عرض المساحة السلبية ("XTRA") عرض كل حرف.

يمكنك تغيير قيمة هذين المحورَين باستخدام إعدادات FontVariation.

لمزيد من المعلومات عن المحاور المخصّصة التي يمكنك ضبطها لأحد الخطوط، اطّلِع على جدول المحاور المتوافقة لكل خط.

  1. لاستخدام محاور مخصّصة، حدِّد دوال للمحورين المخصّصين ascenderHeight وcounterWidth:

    fun ascenderHeight(ascenderHeight: Float): FontVariation.Setting {
        require(ascenderHeight in 649f..854f) { "'Ascender Height' must be in 649f..854f" }
        return FontVariation.Setting("YTAS", ascenderHeight)
    }
    
    fun counterWidth(counterWidth: Int): FontVariation.Setting {
        require(counterWidth in 323..603) { "'Counter width' must be in 323..603" }
        return FontVariation.Setting("XTRA", counterWidth.toFloat())
    }

    تنفّذ هذه الدوالّ ما يلي:

    • تحديد ضوابط للقيم التي يمكن أن تقبلها كما هو موضّح في كتالوج الخطوط المتغيرة، تتضمّن السمة ascenderHeight (YTAS) حدًا أدنى يبلغ 649f وحدًا أقصى يبلغ 854f.
    • أرجِع إعدادات الخط، ليكون الإعداد جاهزًا للإضافة إلى الخط. في طريقة FontVariation.Setting()، يتم ترميز اسم المحور (YTAS, XTRA) بشكل ثابت، ويتم استخدام القيمة كمَعلمة.
  2. باستخدام المحاور مع إعدادات الخط، مرِّر مَعلمات إضافية إلى كل Font يتم تحميله:

    @OptIn(ExperimentalTextApi::class)
    val displayLargeFontFamily = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        FontFamily(
            Font(
                R.font.robotoflex_variable,
                variationSettings = FontVariation.Settings(
                    FontVariation.weight(DisplayLargeVFConfig.WEIGHT),
                    FontVariation.width(DisplayLargeVFConfig.WIDTH),
                    FontVariation.slant(DisplayLargeVFConfig.SLANT),
                    ascenderHeight(DisplayLargeVFConfig.ASCENDER_HEIGHT),
                    counterWidth(DisplayLargeVFConfig.COUNTER_WIDTH)
                )
            )
        )
    } else {
        default
    }

    لاحظ أنّ ارتفاع الحروف الصاعدة الصغيرة قد زاد الآن، وأنّ النص الآخر أصبح أعرض:

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

مراجع إضافية

لمزيد من المعلومات، يمكنك الاطّلاع على مشاركة المدونة التالية حول الخطوط المتغيرة: