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

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

ضبط الخط

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

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

الكلمات

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

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

يوضّح هذا المثال كيفية تحديد 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.6.1"
    }
    

    Kotlin

    dependencies {
        ...
        implementation("androidx.compose.ui:ui-text-google-fonts:1.6.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(
    fontFamily = fontFamily, text = "Hello World!"
)

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

val MyTypography = Typography(
    labelMedium = TextStyle(
        fontFamily = fontFamily, fontWeight = FontWeight.Normal, fontSize = 12.sp/*...*/
    ),
    labelLarge = TextStyle(
        fontFamily = fontFamily,
        fontWeight = FontWeight.Bold,
        letterSpacing = 2.sp,
        /*...*/
    ),
    displayMedium = 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 التي تعرض خطأ إذا تم ضبط الموفر بشكل غير صحيح.

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

محاذير

يستغرق تطبيق Google Fonts عدة أشهر لإتاحة خطوط جديدة على Android. هناك فاصل زمني بين وقت إضافة خط في fonts.google.com ووقت توفّره من خلال واجهة Font API القابلة للتنزيل (إما في نظام العرض أو في 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. لا تتوفّر الخطوط المتغيّرة إلا للإصدار O والإصدارات الأحدث من نظام التشغيل Android، لذا عليك إضافة شريط حماية وإعداد خط احتياطي مناسب:

    // 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. ضبط أسلوب الخط في التصميم المتعدد الأبعاد لاستخدام FontFamily:

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

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

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

  6. الآن بعد أن حددت أسلوب الخط، مرِّره إلى M3 MaterialTheme:

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

مراجع إضافية

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