لمحة عن WindowInsetsRulers

WindowInsets هي واجهة برمجة التطبيقات العادية في Jetpack Compose للتعامل مع مساحات الشاشة التي تحجبها واجهة مستخدم النظام جزئيًا أو كليًا. وتشمل هذه المناطق شريط الحالة وشريط التنقّل ولوحة المفاتيح على الشاشة. يمكنك بدلاً من ذلك تمرير WindowInsetsRulers محدَّدة مسبقًا مثل SafeDrawing إلى Modifier.fitInside أو Modifier.fitOutside لمحاذاة المحتوى مع أشرطة النظام والصورة المقطوعة للشاشة أو إنشاء WindowInsetsRulers مخصَّصة.

مزايا "WindowInsetsRulers"

  • تجنُّب تعقيد الاستهلاك: يعمل هذا المبدأ خلال مرحلة وضع التنسيق. وهذا يعني أنّه يتجاوز تمامًا سلسلة استهلاك العناصر المضمّنة ويمكنه دائمًا توفير المواضع الصحيحة والمطلقة لأشرطة النظام والصور المقطوعة للشاشة، بغض النظر عما فعلته تصاميم العناصر الرئيسية. يساعد استخدام الطريقتَين Modifier.fitInside أو Modifier.fitOutside في حلّ المشاكل عندما تستهلك الدوال المركّبة الرئيسية المساحات المتروكة بشكل غير صحيح.
  • تجنُّب أشرطة النظام بسهولة: يساعد هذا المكوّن محتوى تطبيقك في تجنُّب أشرطة النظام والصورة المقطوعة للشاشة، ويمكن أن يكون أكثر سهولة من استخدام WindowInsets مباشرةً.
  • إمكانية تخصيص عالية: يمكن للمطوّرين محاذاة المحتوى مع مساطر مخصّصة، والتحكّم بدقة في التنسيقات باستخدام تنسيقات مخصّصة.

عيوب WindowInsetsRulers

  • لا يمكن استخدامها للقياس: لأنّها تعمل أثناء مرحلة تحديد الموضع، فإنّ معلومات الموضع التي توفّرها غير متاحة خلال مرحلة القياس السابقة.

محاذاة المحتوى باستخدام طرق Modifier

تسمح واجهة برمجة التطبيقات Modifier.fitInside للتطبيقات بمحاذاة المحتوى مع أشرطة النظام والصور المقطوعة للشاشة. ويمكن استخدامها بدلاً من WindowInsets. Modifier.fitOutside هو عادةً عكس Modifier.fitInside.

على سبيل المثال، للتحقّق من أنّ محتوى التطبيق يتجنّب أشرطة النظام والصورة المقطوعة للشاشة، يمكنك استخدام fitInside(WindowInsetsRulers.safeDrawing.current).

@Composable
fun FitInsideDemo(modifier: Modifier) {
    Box(
        modifier = modifier
            .fillMaxSize()
            // Or DisplayCutout, Ime, NavigationBars, StatusBar, etc...
            .fitInside(WindowInsetsRulers.SafeDrawing.current)
    )
}

يوضّح الجدول التالي كيف سيبدو محتوى تطبيقك مع المساطر المحدّدة مسبقًا باستخدام Modifier.fitInside أو Modifier.fitOutside.

نوع المسطرة المحدّد مسبقًا

Modifier.fitInside

Modifier.fitOutside

DisplayCutout

Ime

لا ينطبق

NavigationBars

SafeDrawing

لا ينطبق (يُرجى استخدام StatusBar وCaptionBar وNavigationBar بدلاً من ذلك)

StatusBar

يتطلب استخدام Modifier.fitInside وModifier.fitOutside أن تكون الدوال المركّبة مقيّدة. هذا يعني أنّه عليك تحديد معدِّلات مثل Modifier.size أو Modifier.fillMaxSize.

بعض الحكّام، مثل Modifier.fitOutside في SafeDrawing وSystemBars، يعيدون حكّامًا متعددين. في هذه الحالة، يضع Android الدالة المركّبة باستخدام مسطرة واحدة من اليسار والأعلى واليمين والأسفل.

تجنُّب محرِّر أسلوب الإدخال (IME) باستخدام Modifier.fitInside

للتعامل مع العناصر السفلية باستخدام IME مع Modifier.fitInside، أدخِل RectRuler يأخذ القيمة الأعمق لـ NavigationBar وIme.

@Composable
fun FitInsideWithImeDemo(modifier: Modifier) {
    Box(
        modifier = modifier
            .fillMaxSize()
            .fitInside(
                RectRulers.innermostOf(
                    WindowInsetsRulers.NavigationBars.current,
                    WindowInsetsRulers.Ime.current
                )
            )
    ) {
        TextField(
            value = "Demo IME Insets",
            onValueChange = {},
            modifier = modifier.align(Alignment.BottomStart).fillMaxWidth()
        )
    }
}

تجنُّب شريط الحالة وشريط العناوين باستخدام Modifier.fitInside

وبالمثل، للتحقّق من أنّ العناصر العلوية تتجنّب شريط الحالة وشريط العناوين مع Modifier.fitInside، مرِّر RectRuler الذي يأخذ القيمة الأعمق لـ StatusBars وCaptionBar.

@Composable
fun FitInsideWithStatusAndCaptionBarDemo(modifier: Modifier) {
    Box(
        modifier = modifier
            .fillMaxSize()
            .fitInside(
                RectRulers.innermostOf(
                    WindowInsetsRulers.StatusBars.current,
                    WindowInsetsRulers.CaptionBar.current
                )
            )
    )
}

إنشاء WindowInsetsRulers مخصّص

يمكنك محاذاة المحتوى مع المساطر المخصّصة. على سبيل المثال، لنفترض أنّ هناك دالة مركّبة رئيسية تعالج المساحات المتروكة بشكل غير صحيح، ما يؤدي إلى حدوث مشاكل في المساحة المتروكة في دالة مركّبة فرعية تابعة لها. على الرغم من أنّه يمكن حلّ هذه المشكلة بطرق أخرى، بما في ذلك استخدام Modifier.fitInside، يمكنك أيضًا إنشاء مسطرة مخصّصة لمحاذاة الدالة المركّبة الفرعية بدقة بدون الحاجة إلى حلّ المشكلة في الدالة الرئيسية الأعلى في التسلسل الهرمي كما هو موضّح في المثال والفيديو التاليَين:

@Composable
fun WindowInsetsRulersDemo(modifier: Modifier) {
    Box(
        contentAlignment = BottomCenter,
        modifier = modifier
            .fillMaxSize()
            // The mistake that causes issues downstream, as .padding doesn't consume insets.
            // While it's correct to instead use .windowInsetsPadding(WindowInsets.navigationBars),
            // assume it's difficult to identify this issue to see how WindowInsetsRulers can help.
            .padding(WindowInsets.navigationBars.asPaddingValues())
    ) {
        TextField(
            value = "Demo IME Insets",
            onValueChange = {},
            modifier = modifier
                // Use alignToSafeDrawing() instead of .imePadding() to precisely place this child
                // Composable without having to fix the parent upstream.
                .alignToSafeDrawing()

            // .imePadding()
            // .fillMaxWidth()
        )
    }
}

fun Modifier.alignToSafeDrawing(): Modifier {
    return layout { measurable, constraints ->
        if (constraints.hasBoundedWidth && constraints.hasBoundedHeight) {
            val placeable = measurable.measure(constraints)
            val width = placeable.width
            val height = placeable.height
            layout(width, height) {
                val bottom = WindowInsetsRulers.SafeDrawing.current.bottom
                    .current(0f).roundToInt() - height
                val right = WindowInsetsRulers.SafeDrawing.current.right
                    .current(0f).roundToInt()
                val left = WindowInsetsRulers.SafeDrawing.current.left
                    .current(0f).roundToInt()
                measurable.measure(Constraints.fixed(right - left, height))
                    .place(left, bottom)
            }
        } else {
            val placeable = measurable.measure(constraints)
            layout(placeable.width, placeable.height) {
                placeable.place(0, 0)
            }
        }
    }
}

يعرض الفيديو التالي مثالاً على مشكلة في استهلاك إدخال IME بسبب عنصر رئيسي في الصورة على اليمين، واستخدام المساطر المخصّصة لحلّ المشكلة على اليسار. يظهر حشو إضافي أسفل الدالة المركّبة TextField لأنّ حشو شريط التنقّل لم يتم استخدامه من قِبل الدالة الرئيسية. يتم وضع العنصر الفرعي في الموقع الصحيح في الصورة على اليمين باستخدام مسطرة مخصصة كما هو موضح في عينة التعليمات البرمجية السابقة.

التأكّد من أنّ العناصر الأبوية مُقيَّدة

لاستخدام "WindowInsetsRulers" بأمان، يجب التأكّد من أنّ الوالدَين يقدّمان قيودًا صالحة. يجب أن يكون للعناصر الرئيسية حجم محدّد ولا يمكن أن تعتمد على حجم عنصر فرعي يستخدم WindowInsetsRulers. استخدِم fillMaxSize أو معدِّلات الحجم الأخرى على الدوال المركّبة الرئيسية.

وبالمثل، فإنّ وضع دالة مركّبة تستخدم WindowInsetsRulers داخل حاوية قابلة للتمرير مثل verticalScroll يمكن أن يؤدي إلى سلوك غير متوقّع لأنّ الحاوية القابلة للتمرير توفّر قيودًا غير محدودة للارتفاع، وهي غير متوافقة مع منطق المسطرة.