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

توضّح هذه الصفحة كيفية ضبط الخطوط في تطبيق "الإنشاء".

ضبط الخط

يحتوي العنصر Text على مَعلمة fontFamily للسماح بضبط الخط المستخدَم في العنصر composable. بشكلٍ تلقائي، يتم تضمين مجموعات الخطوط 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.7.5"
    }
    

    Kotlin

    dependencies {
        ...
        implementation("androidx.compose.ui:ui-text-google-fonts:1.7.5")
    }
  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(
    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
)/*...*/

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

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

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

خمسة إعدادات للخط المتغيّر نفسه بقيم محور مختلفة
الشكل 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 10 والإصدارات الأحدث، لذا أضِف حاجزًا وضبط بديلاً مناسبًا:

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

مصادر إضافية

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