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

توضّح هذه الصفحة كيفية ضبط الخطوط في تطبيق 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. أضِف التبعية:

    أنيق

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

    Kotlin

    dependencies {
        ...
        implementation("androidx.compose.ui:ui-text-google-fonts:1.11.0")
    }
  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 API من الموفّر لاختبار ما إذا كان الموفّر متاحًا وتم ضبط الشهادات بشكلٍ صحيح. لإجراء ذلك، يمكنك استدعاء طريقة isAvailableOnDevice التي تعرض القيمة "خطأ" إذا تم ضبط الموفّر بشكلٍ غير صحيح.

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

المحاذير

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

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

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

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

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

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

يوضّح هذا المستند كيفية تنفيذ خط متغيّر في تطبيق 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. اضبط الطباعة في التصميم المتعدد الأبعاد 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 الخاص به ويحتوي على إعداد مختلف للخط المتغيّر. يمكنك استخدام MaterialTheme.typography لاسترداد الطباعة المقدَّمة إلى العنصر المركّب في M3 MaterialTheme.

ثلاثة نصوص مختلفة، يعرض كل منها إعدادات مختلفة للخط.
الشكل 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. نص يعرض المحاور المخصّصة التي تم ضبطها على الخطوط المتغيّرة

مراجع إضافية

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