En las siguientes secciones, se muestra cómo combinar Modifier.blur, BlurRadiusSpec, BlurStop y BlurScope para patrones comunes de la IU. Para obtener una introducción a estas APIs, consulta Desenfoque en Compose.
Desenfoca los bordes del contenido que se puede desplazar o arrastrar
Cuando se muestra una lista, un carrusel o un selector con desplazamiento, puedes usar desenfoques progresivos de varias paradas para suavizar los elementos cerca de los bordes del contenedor y, al mismo tiempo, mantener la región central activa en un enfoque nítido.
Atenúa los bordes superior e inferior de una lista con desplazamiento
Para suavizar los elementos a medida que se desplazan hacia dentro y fuera del viewport de una lista vertical,
aplica BlurRadiusSpec.verticalGradient con entradas BlurStop al
LazyColumn. En el siguiente ejemplo, en el que se desplaza la letra de una canción línea por línea, las detenciones de 0.dp entre 0.42f y 0.58f mantienen la línea activa en el centro nítida, mientras que las líneas hacia los bordes superior e inferior se desenfocan de forma progresiva:
@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, ) { // ... } }
Atenúa los bordes izquierdo y derecho de un control horizontal
Del mismo modo, para atraer la atención al centro de un componente que se puede desplazar o arrastrar de forma horizontal, como un selector de rueda, un carrusel o una escala de medición, aplica BlurRadiusSpec.horizontalGradient con entradas BlurStop. En la siguiente escala de peso arrastrable, los bordes izquierdo y derecho se suavizan, mientras que las marcas y los números cerca de la aguja del indicador central permanecen completamente nítidos:
@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` // ... } // ... } } }
Crea un desenfoque diagonal con linearGradient
BlurRadiusSpec.linearGradient varía el radio de desenfoque a lo largo de un segmento de línea entre los valores start y end DpOffset en el espacio de coordenadas de la capa.
Dentro del bloque lambda Modifier.blur, puedes leer BlurScope.size para ejecutar un desenfoque progresivo diagonal en una imagen hero desde la esquina superior izquierda hasta la esquina inferior derecha, lo que mantiene el sujeto en la parte superior izquierda completamente nítido mientras se desenfoca progresivamente la región inferior derecha detrás del texto superpuesto:
@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)) }, ) // ... } }
Cómo crear un foco de atención con radialGradient
BlurRadiusSpec.radialGradient crea un efecto de reflector circular variando
el radio de desenfoque hacia afuera desde un punto focal. Define center como un DpOffset y fallOffRadius como la distancia Dp desde center en la que se alcanza endRadius. Si dejas center como DpOffset.Unspecified, el gradiente se centra en el elemento que admite composición, y si dejas fallOffRadius como Dp.Unspecified, se establece de forma predeterminada en la mitad de la dimensión más pequeña de la capa.
Por ejemplo, aplicar BlurRadiusSpec.radialGradient sobre un pasaje de texto y actualizar center desde eventos de arrastre del puntero permite que el usuario arrastre un foco de lectura circular por el párrafo mientras se desenfoca el texto circundante, como se muestra en el siguiente ejemplo:
@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), ) { // ... } }
Crea una máscara de desenfoque personalizada con un sombreador
Para distribuciones de desenfoque espacial complejas más allá de los gradientes lineales o radiales, como patrones repetitivos, formas arbitrarias o ruido de procedimiento, pasa un Shader personalizado, como un AGSL RuntimeShader, a BlurRadiusSpec.shader. El sombreador evalúa las coordenadas de píxeles de la capa desde el origen superior izquierdo, y su canal alfa muestreado (0..1) escala maxRadius.
Crea el sombreador y la configuración de BlurRadiusSpec.shader una vez con la función de componibilidad remember para que se reutilice una sola instancia en las recomposiciones. Compose invoca el bloque de configuración con el tamaño de la capa en cada resolución, que es donde estableces los uniformes que dependen del tamaño:
@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), ) }