طلب معلومات عن التنسيقات التكيّفية باستخدام mediaQuery

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

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

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

يغيّر المثال التالي التنسيق إلى TabletopLayout عندما يكون وضع الجهاز القابل للطي هو "الشاشة المسطحة":

@Composable
fun VideoPlayer(
    // ...
) {
    // ...
            if (mediaQuery { windowPosture == UiMediaScope.Posture.Tabletop }) {
                TabletopLayout()
            } else {
                FlatLayout()
            }
    // ...
}

تفعيل وظيفة mediaQuery

لتفعيل الدالة mediaQuery، اضبط السمة isMediaQueryIntegrationEnabled للعنصر ComposeUiFlags على true:

class MyApplication : Application() {
    override fun onCreate() {
        ComposeUiFlags.isMediaQueryIntegrationEnabled = true
        super.onCreate()
    }
}

تحديد شرط باستخدام مَعلمات

يمكنك تعريف شرط على أنّه تعبير lambda يتم تقييمه ضمن UiMediaScope. تقيِّم الدالة mediaQuery الشرط وفقًا للحالة الحالية وإمكانات الجهاز. تعرض الدالة قيمة منطقية، وبالتالي يمكنك تحديد التنسيق باستخدام فروع شرطية مثل تعبير if. يوضّح الجدول 1 المَعلمات المتوفّرة في UiMediaScope.

المَعلمة نوع القيمة الوصف
windowWidth Dp عرض النافذة الحالية بوحدات dp
windowHeight Dp ارتفاع النافذة الحالية بوحدات dp
windowPosture UiMediaScope.Posture تمثّل هذه السمة الوضع الحالي لنافذة التطبيق.
pointerPrecision UiMediaScope.PointerPrecision تمثّل هذه السمة أعلى دقة لأجهزة التأشير المتاحة.
keyboardKind UiMediaScope.KeyboardKind نوع لوحة المفاتيح المتوفرة أو المتصلة
hasCamera Boolean تحديد ما إذا كانت الكاميرا متوافقة مع الجهاز.
hasMicrophone Boolean تُستخدَم لتحديد ما إذا كان الميكروفون متوافقًا مع الجهاز.
viewingDistance UiMediaScope.ViewingDistance تمثّل هذه السمة المسافة المعتادة بين المستخدم وشاشة الجهاز.

يحلّ كائن UiMediaScope قيم المَعلمات. تستخدم الدالة mediaQuery LocalUiMediaScope.current للوصول إلى العنصر UiMediaScope، الذي يمثّل إمكانات الجهاز الحالي وسياقه. يتم تعديل هذا العنصر ديناميكيًا عند إجراء أي تغييرات، مثلما يحدث عندما يغيّر المستخدم وضع الجهاز. بعد ذلك، تقيّم الدالة mediaQuery تعبير lambda query باستخدام الكائن UiMediaScope المعدَّل وتعرض قيمة منطقية. على سبيل المثال، يختار المقتطف التالي بين TabletopLayout وFlatLayout استنادًا إلى قيمة المَعلمة windowPosture.

@Composable
fun VideoPlayer(
    // ...
) {
    // ...
            if (mediaQuery { windowPosture == UiMediaScope.Posture.Tabletop }) {
                TabletopLayout()
            } else {
                FlatLayout()
            }
    // ...
}

اتّخاذ قرار استنادًا إلى حجم النافذة

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

تقيّم الدالة derivedMediaQuery تعبير lambda query وتضمِّن النتيجة في derivedStateOf. بما أنّ windowWidth وwindowHeight يمكن تعديلهما بشكل متكرّر، استخدِم الدالة derivedMediaQuery بدلاً من الدالة mediaQuery عند الإشارة إلى هاتين المَعلمتَين في دالة lambda query.

val narrowerThanMedium by derivedMediaQuery {
    windowWidth < WindowSizeClass.WIDTH_DP_MEDIUM_LOWER_BOUND.dp
}
val narrowerThanExpanded by derivedMediaQuery {
    windowWidth < WindowSizeClass.WIDTH_DP_EXPANDED_LOWER_BOUND.dp
}
when {
    narrowerThanMedium -> SinglePaneLayout()
    narrowerThanExpanded -> TwoPaneLayout()
    else -> ThreePaneLayout()
}

الشكل 1. يتم تعديل التنسيق وفقًا لعرض النافذة.

تعديل التصميم وفقًا لوضع النافذة

تصف المَعلمة windowPosture وضع النافذة الحالي ككائن UiMediaScope.Posture. يمكنك التحقّق من الوضع الحالي من خلال مقارنة المَعلمة بالقيم المحدّدة في الفئة UiMediaScope.Posture. يغيّر المثال التالي التصميم حسب وضع النافذة:

when {
    mediaQuery { windowPosture == UiMediaScope.Posture.Tabletop } -> TabletopLayout()
    mediaQuery { windowPosture == UiMediaScope.Posture.Book } -> BookLayout()
    mediaQuery { windowPosture == UiMediaScope.Posture.Flat } -> FlatLayout()
}

التحقّق من دقة جهاز التأشير المتاح

يساعد جهاز التأشير العالي الدقة المستخدمين في التأشير على عنصر واجهة المستخدم بدقة. تعتمد دقة جهاز التأشير على نوع الجهاز.

تصف المَعلمة pointerPrecision دقة أجهزة التأشير المتاحة، مثل الماوس والشاشة التي تعمل باللمس. هناك أربع قيم محدّدة في الفئة UiMediaScope.PointerPrecision: Fine وCoarse وBlunt وNone. يشير None إلى عدم توفّر جهاز تأشير. تتراوح الدقة من الأعلى إلى الأدنى بهذا الترتيب: Fine وCoarse وBlunt.

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

يعرض المثال التالي زرًا أكبر عندما يستخدم المستخدم جهاز تأشير بدقة منخفضة:

if (mediaQuery { pointerPrecision == UiMediaScope.PointerPrecision.Blunt }) {
    LargeSizeButton()
} else {
    NormalSizeButton()
}

التحقّق من نوع لوحة المفاتيح المتاح

تمثّل المَعلمة keyboardKind نوع لوحات المفاتيح المتاحة: Physical وVirtual وNone. إذا تم عرض لوحة مفاتيح على الشاشة وكانت لوحة مفاتيح خارجية متاحة في الوقت نفسه، يتم تحديد قيمة المَعلمة على أنّها Physical. في حال عدم رصد أي منهما، تكون None هي قيمة المَعلمة. يعرض المثال التالي رسالة تقترح على المستخدمين توصيل لوحة مفاتيح عند عدم رصد أي لوحة مفاتيح:

if (mediaQuery { keyboardKind == UiMediaScope.KeyboardKind.None }) {
    SuggestKeyboardConnect()
}

التحقّق مما إذا كان الجهاز يتيح استخدام الكاميرا والميكروفون

لا تتوافق بعض الأجهزة مع الكاميرات أو الميكروفونات. يمكنك التحقّق مما إذا كان الجهاز يتيح استخدام كاميرا وميكروفون باستخدام المَعلمة hasCamera والمَعلمة hasMicrophone. يوضّح المثال التالي أزرارًا يمكن استخدامها مع الكاميرا والميكروفون عندما يتيح الجهاز استخدامهما:

Row {
    OutlinedTextField(state = rememberTextFieldState())
    // Show the MicButton when the device supports a microphone.
    if (mediaQuery { hasMicrophone }) {
        MicButton()
    }
    // Show the CameraButton when the device supports a camera.
    if (mediaQuery { hasCamera }) {
        CameraButton()
    }
}

تعديل واجهة المستخدم حسب مسافة المشاهدة المقدَّرة

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

تتضمّن الفئة UiMediaScope.ViewingDistance ثلاث قيم: Near وMedium وFar. يشير الرمز Near إلى أنّ الشاشة في نطاق قريب، ويشير الرمز Far إلى أنّه يتم عرض الجهاز من مسافة بعيدة. يزيد المثال التالي حجم الخط عندما تكون مسافة المشاهدة Far أو Medium:

val fontSize = when {
    mediaQuery { viewingDistance == UiMediaScope.ViewingDistance.Far } -> 20.sp
    mediaQuery { viewingDistance == UiMediaScope.ViewingDistance.Medium } -> 18.sp
    else -> 16.sp
}

معاينة أحد مكونات واجهة المستخدم

يمكنك استدعاء الدالتَين mediaQuery وderivedMediaQuery في الدوال القابلة للإنشاء لمعاينة مكوّنات واجهة المستخدم. يختار المقتطف التالي بين TabletopLayout وFlatLayout استنادًا إلى قيمة المَعلمة windowPosture. لمعاينة TabletopLayout، يجب أن تكون قيمة المَعلمة windowPosture هي UiMediaScope.Posture.Tabletop.

when {
    mediaQuery { windowPosture == UiMediaScope.Posture.Tabletop } -> TabletopLayout()
    mediaQuery { windowPosture == UiMediaScope.Posture.Book } -> BookLayout()
    mediaQuery { windowPosture == UiMediaScope.Posture.Flat } -> FlatLayout()
}

تقيّم الدالتان mediaQuery وderivedMediaQuery تعبير lambda query المحدّد ضمن عنصر UiMediaScope، والذي يتم توفيره كـ LocalUiMediaScope.current. يمكنك تجاهل هذا الإعداد من خلال اتّباع الخطوات التالية:

  1. فعِّل وظيفة mediaQuery.
  2. حدِّد عنصرًا مخصّصًا ينفّذ واجهة UiMediaScope.
  3. اضبط العنصر المخصّص على LocalUiMediaScope باستخدام الدالة CompositionLocalProvider.
  4. استدعِ الدالة المركّبة لمعاينتها في رمز lambda الخاص بالمحتوى للدالة CompositionLocalProvider.

يمكنك معاينة TabletopLayout باستخدام المثال التالي:

@Preview
@Composable
fun PreviewLayoutForTabletop() {
    // Step 1: Enable the mediaQuery function
    ComposeUiFlags.isMediaQueryIntegrationEnabled = true

    val currentUiMediaScope = LocalUiMediaScope.current
    // Step 2: Define a custom object implementing the UiMediaScope interface.
    // The object overrides the windowPosture parameter.
    // The resolution of the remaining parameters is deferred to the currentUiMediaScope object.
    val uiMediaScope = remember(currentUiMediaScope) {
        object : UiMediaScope by currentUiMediaScope {
            override val windowPosture: UiMediaScope.Posture = UiMediaScope.Posture.Tabletop
        }
    }

    // Step 3: Set the object to the LocalUiMediaScope.
    CompositionLocalProvider(LocalUiMediaScope provides uiMediaScope) {
        // Step 4: Call the composable to preview.
        when {
            mediaQuery { windowPosture == UiMediaScope.Posture.Tabletop } -> TabletopLayout()
            mediaQuery { windowPosture == UiMediaScope.Posture.Book } -> BookLayout()
            mediaQuery { windowPosture == UiMediaScope.Posture.Flat } -> FlatLayout()
        }
    }
}