حالات الاستخدام الشائعة للتمويه

توضّح الأقسام التالية كيفية دمج Modifier.blur وBlurRadiusSpec وBlurStop وBlurScope لأنماط واجهة المستخدم الشائعة. للحصول على مقدمة حول واجهات برمجة التطبيقات هذه، يُرجى الاطّلاع على التمويه في Compose.

تمويه حواف المحتوى القابل للتمرير أو السحب

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

تلاشي الحواف العلوية والسفلية لقائمة قابلة للتمرير

لإضفاء لمسة ناعمة على العناصر أثناء انتقالها إلى داخل وخارج إطار عرض القائمة العمودية، يمكنك تطبيق BlurRadiusSpec.verticalGradient مع إدخالات BlurStop على LazyColumn. في المثال التالي، الذي يعرض كلمات الأغنية سطرًا بسطر، تؤدي نقاط توقّف 0.dp بين 0.42f و0.58f إلى إبقاء السطر النشط في المنتصف واضحًا بينما تتلاشى الأسطر باتجاه الحواف العلوية والسفلية تدريجيًا:

@Composable
fun SongListTopBottomBlurExample(lyrics: List<String>) {
    // ...
    LazyColumn(
        state = listState,
        flingBehavior = flingBehavior,
        modifier = Modifier
            .width(330.dp)
            .height(480.dp)
            .padding(horizontal = 24.dp)
            .blur(
                blurRadiusSpec = BlurRadiusSpec.verticalGradient(
                    stops = listOf(
                        BlurStop(fraction = 0.00f, radius = 18.dp), // Heavily blurred top edge
                        BlurStop(fraction = 0.24f, radius = 8.dp), // Soft upper transition
                        BlurStop(fraction = 0.42f, radius = 0.dp), // Sharp center focus starts
                        BlurStop(fraction = 0.58f, radius = 0.dp), // Sharp center focus ends
                        BlurStop(fraction = 0.76f, radius = 8.dp), // Soft lower transition
                        BlurStop(fraction = 1.00f, radius = 18.dp), // Heavily blurred bottom edge
                    ),
                ),
                edgeTreatment = BlurredEdgeTreatment.Unbounded,
            ),
        contentPadding = PaddingValues(vertical = 36.dp),
        verticalArrangement = Arrangement.spacedBy(26.dp),
        horizontalAlignment = Alignment.CenterHorizontally,
    ) {
        // ...
    }
}

الشكل 1: قائمة عمودية لأسطر نص كلمات الأغنية حيث تتلاشى الأسطر العلوية والسفلية في ضبابية بينما يكون السطر الأوسط واضحًا.

تلاشي الحواف اليسرى واليمنى لعنصر تحكّم أفقي

وبالمثل، لجذب التركيز إلى منتصف مكوّن قابل للتمرير أو السحب أفقيًا، مثل أداة اختيار العجلة أو لوحة العرض بعناصر متغيّرة أو مقياس القياس، يمكنك تطبيق BlurRadiusSpec.horizontalGradient مع إدخالات BlurStop. في مقياس الوزن القابل للسحب التالي، تصبح الحواف اليسرى واليمنى ناعمة بينما تظل العلامات والأرقام بالقرب من إبرة المؤشر المركزية حادة تمامًا:

@Composable
fun HorizontalScaleBlurExample() {
    var selectedValue by remember { mutableFloatStateOf(65.0f) }
    val animatedValue by animateFloatAsState(
        targetValue = selectedValue,
        animationSpec = tween(durationMillis = 900, easing = FastOutSlowInEasing),
        label = "horizontalScaleSmoothScroll",
    )
    Column(
        modifier = Modifier
            .width(340.dp)
            .background(Color(0xFF0F172A), RoundedCornerShape(24.dp))
            .padding(vertical = 24.dp),
        horizontalAlignment = Alignment.CenterHorizontally,
    ) {
        Text(
            text = "${(animatedValue * 10).roundToInt() / 10.0} kg",
            color = Color.White,
            fontSize = 44.sp,
            fontWeight = FontWeight.Black,
        )
        Spacer(Modifier.height(16.dp))
        Box(
            // ...
        ) {
            // Ruler ticks blurred at the left and right edges, sharp at center
            Canvas(
                modifier = Modifier
                    .fillMaxSize()
                    .blur(
                        blurRadiusSpec = BlurRadiusSpec.horizontalGradient(
                            stops = listOf(
                                BlurStop(fraction = 0.00f, radius = 14.dp),
                                BlurStop(fraction = 0.28f, radius = 0.dp),
                                BlurStop(fraction = 0.72f, radius = 0.dp),
                                BlurStop(fraction = 1.00f, radius = 14.dp),
                            ),
                        ),
                        edgeTreatment = BlurredEdgeTreatment.Unbounded,
                    ),
            ) {
                // Draw horizontal ruler ticks offset by `animatedValue`
                // ...
            }
            // ...
        }
    }
}

الشكل 2. أداة مسطرة أفقية تعرض علامات تدريج مموّهة بالقرب من الحواف وعلامات تدريج حادة بالقرب من مؤشر الإبرة

إنشاء تمويه قطري باستخدام linearGradient

يغيّر BlurRadiusSpec.linearGradient نصف قطر التمويه على طول مقطع خطي بين قيمتَي start وend DpOffset في مساحة إحداثيات الطبقة. داخل كتلة lambda Modifier.blur، يمكنك قراءة BlurScope.size لتشغيل تمويه تدريجي قطري عبر صورة رئيسية من الزاوية العلوية اليسرى إلى الزاوية السفلية اليمنى، مع الحفاظ على وضوح الموضوع في أعلى اليسار تمامًا مع تمويه المنطقة السفلية اليمنى تدريجيًا خلف النص المتراكب:

@Composable
fun DiagonalLinearBlurCardExample() {
    Box(
        modifier = Modifier
            .wrapContentSize()
            .background(Color(0xFFF8FAFC))
            .padding(10.dp),
    ) {
        Image(
            painter = painterResource(id = R.drawable.dog),
            contentDescription = stringResource(id = R.string.dog_content_description),
            contentScale = ContentScale.Crop,
            modifier = Modifier
                .size(width = 300.dp, height = 380.dp)
                .blur {
                    blurRadiusSpec = BlurRadiusSpec.linearGradient(
                        start = DpOffset(0.dp, 0.dp),
                        end = DpOffset(size.width, size.height),
                        stops = listOf(
                            BlurStop(fraction = 0.00f, radius = 0.dp),
                            BlurStop(fraction = 0.50f, radius = 0.dp),
                            BlurStop(fraction = 0.75f, radius = 14.dp),
                            BlurStop(fraction = 1.00f, radius = 28.dp),
                        ),
                    )
                    edgeTreatment = BlurredEdgeTreatment(RoundedCornerShape(24.dp))
                },
        )
        // ...
    }
}

صورة لكلب واضحة في أعلى اليمين ومموّهة تدريجيًا باتجاه أسفل اليسار، مع عنوان رئيسي وعنوان فرعي باللون الأبيض الواضح فوق المنطقة المموّهة
الشكل 3. صورة لكلب واضحة في الزاوية العلوية اليمنى ومموّهة تدريجيًا باتجاه الزاوية السفلية اليسرى، مع تراكب نص العنوان والعنوان الفرعي باللون الأبيض الواضح على المنطقة المموّهة.

إنشاء فيديو "تركيز" باستخدام radialGradient

تنشئ دالة BlurRadiusSpec.radialGradient تأثيرًا دائريًا يشبه تسليط الضوء من خلال تغيير نصف قطر التمويه إلى الخارج من نقطة تركيز. حدِّد center على أنّه DpOffset وfallOffRadius على أنّه المسافة Dp من center التي يتم عندها الوصول إلى endRadius. إذا تركت center على DpOffset.Unspecified، يتم توسيط التدرج على الدالة المركّبة، وإذا تركت fallOffRadius على Dp.Unspecified، يتم ضبطه تلقائيًا على نصف البُعد الأصغر للطبقة.

على سبيل المثال، يؤدي تطبيق BlurRadiusSpec.radialGradient على فقرة نصية وتعديل center من أحداث سحب المؤشر إلى السماح للمستخدم بسحب دائرة تسليط الضوء على القراءة عبر الفقرة مع تعتيم النص المحيط، كما هو موضّح في المثال التالي:

@Composable
fun RadialSpotlightTextExample() {
    var touchOffset by remember { mutableStateOf(Offset.Unspecified) }
    Box(
        modifier = Modifier
            .size(width = 330.dp, height = 340.dp)
            .background(Color(0xFF0F172A), RoundedCornerShape(20.dp))
            .pointerInput(Unit) {
                detectDragGestures(
                    onDragStart = { offset -> touchOffset = offset },
                    onDrag = { change, _ -> touchOffset = change.position },
                )
            }
            .blur {
                val spotlightCenter = if (touchOffset.isSpecified) {
                    DpOffset(touchOffset.x.toDp(), touchOffset.y.toDp())
                } else {
                    DpOffset(x = size.width / 2, y = size.height / 2)
                }
                blurRadiusSpec = BlurRadiusSpec.radialGradient(
                    startRadius = 0.dp,
                    endRadius = 10.dp,
                    center = spotlightCenter,
                    fallOffRadius = 215.dp,
                )
                edgeTreatment = BlurredEdgeTreatment(RoundedCornerShape(20.dp))
            }
            .padding(22.dp),
    ) {
        // ...
    }
}

الشكل 4. فقرة نصية مع منطقة دائرية من النص الواضح حول المؤشر الذي يصبح مموّهًا تدريجيًا باتجاه حافة الدائرة.

إنشاء قناع تمويه مخصّص باستخدام برنامج تظليل

بالنسبة إلى توزيعات التمويه المكاني المعقّدة التي تتجاوز التدرجات الخطية أو الشعاعية، مثل الأنماط المتكررة أو الأشكال العشوائية أو التشويش الإجرائي، يمكنك تمرير Shader مخصّص، مثل AGSL RuntimeShader، إلى BlurRadiusSpec.shader. يتم تقييم برنامج التظليل في إحداثيات بكسل الطبقة من نقطة الأصل في أعلى اليمين، ويتم قياس قناة ألفا التي تم أخذ عيّنات منها (0..1) بمقدار maxRadius.

أنشِئ برنامج التظليل وإعدادات BlurRadiusSpec.shader مرة واحدة باستخدام الدالة المركّبة remember، بحيث تتم إعادة استخدام مثيل واحد في عمليات إعادة الإنشاء. يستدعي Compose مجموعة الإعدادات مع حجم الطبقة في كل عملية تحليل، حيث يمكنك ضبط المتغيّرات الموحّدة التي تعتمد على الحجم:

@RequiresApi(Build.VERSION_CODES.TIRAMISU)
@Composable
fun ShaderBlurExample() {
    // Creates 6 diagonal frosted-glass bands across the image.
    val maskShader = remember {
        RuntimeShader(
            """
            uniform float2 size;
            half4 main(float2 coord) {
                float2 uv = coord / size;
                float band = smoothstep(0.4, 0.6, fract((uv.x + uv.y) * 3.0));
                return half4(0.0, 0.0, 0.0, half(band));
            }
            """.trimIndent(),
        )
    }
    val maskBlurSpec = remember(maskShader) {
        BlurRadiusSpec.shader(maxRadius = 24.dp) { sizePx ->
            maskShader.setFloatUniform("size", sizePx.width, sizePx.height)
            maskShader
        }
    }
    Image(
        painter = painterResource(id = R.drawable.dog),
        contentDescription = stringResource(id = R.string.dog_content_description),
        contentScale = ContentScale.Crop,
        modifier = Modifier
            .size(150.dp)
            .blur(blurRadiusSpec = maskBlurSpec),
    )
}

صورة لكلب مع أشرطة تمويه زجاجية مائلة مدفوعة بقناع تظليل مخصّص.
الشكل 5. صورة لكلب مع أشرطة تمويه زجاجية مائلة مدفوعة بقناع تظليل مخصّص