طلب معلومات عن التنسيقات التكيّفية باستخدام 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()
        }
    }
}