لمحة عن WindowInsetsRulers

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

مزايا WindowInsetsRulers

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

عيوب WindowInsetsRulers

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

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

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.fitInsider، مرِّر 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، إلى حدوث سلوك غير متوقّع لأنّ الحاوية القابلة للتمرير توفّر قيودًا غير محدودة للارتفاع، ما يتعارض مع منطق المسطرة.