¿Cuándo debería usar Android Jetpack Compose Surface componible?

3 minutos de lectura

hay Superficie componible en Jetpack Compose que representa un superficie material. Una superficie le permite configurar cosas como el color de fondo o el borde, pero parece que se podría hacer lo mismo usando modificadores. ¿Cuándo debo usar el componible de Surface y qué beneficios me brinda?

Avatar de usuario de Valeriy Katkov
valeriy katkov

Superficie composable hace que el código sea más fácil e indica explícitamente que el código usa un superficie material. Veamos un ejemplo:

Surface(
    color = MaterialTheme.colors.primarySurface,
    border = BorderStroke(1.dp, MaterialTheme.colors.secondary),
    shape = RoundedCornerShape(8.dp),
    elevation = 8.dp
) {
    Text(
        text = "example",
        modifier = Modifier.padding(8.dp)
    )
}

y el resultado:

ingrese la descripción de la imagen aquí

El mismo resultado se puede lograr sin Surface:

val shape = RoundedCornerShape(8.dp)
val shadowElevationPx = with(LocalDensity.current) { 2.dp.toPx() }
val backgroundColor = MaterialTheme.colors.primarySurface

Text(
    text = "example",
    color = contentColorFor(backgroundColor),
    modifier = Modifier
        .graphicsLayer(shape = shape, shadowElevation = shadowElevationPx)
        .background(backgroundColor, shape)
        .border(1.dp, MaterialTheme.colors.secondary, shape)
        .padding(8.dp)
)

pero tiene algunos inconvenientes:

  • La cadena de modificadores es bastante grande y no es obvio que implemente una superficie de material
  • Debo declarar una variable para la forma y pasarla a tres modificadores diferentes
  • Usa contenidoColorPara para averiguar el color del contenido mientras que Surface lo hace bajo el capó. como resultado el backgroundColor se utiliza en dos lugares también.
  • tengo que calcular la elevacion en pixeles
  • Surface ajusta los colores para la elevación (en caso de tema oscuro) de acuerdo con el diseño de materiales. Si desea el mismo comportamiento, debe manejarlo manualmente.

Para ver la lista completa de funciones de Surface, es mejor echar un vistazo a la documentación.

  • El bloqueo de la propagación táctil podría ser quizás la función más importante de Surface, en comparación con otras vistas de contenido/componibles.

    – Anm

    13 de septiembre de 2021 a las 14:44

La superficie es el equivalente de CardView en el sistema de vista.
Por Surfacepuede establecer la elevación para la vista (tenga en cuenta que esto no es lo mismo con Modifier.shadow)

Surface es un Box con un Modifier.surface() y los colores y la elevación del material, verifica que la elevación de los ancestros esté siempre encima de ellos, y solo se sobrecarga por debajo bloqueando la propagación del toque detrás de la superficie con pointerInput(Unit) {}.

@Composable
fun Surface(
    modifier: Modifier = Modifier,
    shape: Shape = RectangleShape,
    color: Color = MaterialTheme.colors.surface,
    contentColor: Color = contentColorFor(color),
    border: BorderStroke? = null,
    elevation: Dp = 0.dp,
    content: @Composable () -> Unit
) {
    val absoluteElevation = LocalAbsoluteElevation.current + elevation
    CompositionLocalProvider(
        LocalContentColor provides contentColor,
        LocalAbsoluteElevation provides absoluteElevation
    ) {
        Box(
            modifier = modifier
                .surface(
                    shape = shape,
                    backgroundColor = surfaceColorAtElevation(
                        color = color,
                        elevationOverlay = LocalElevationOverlay.current,
                        absoluteElevation = absoluteElevation
                    ),
                    border = border,
                    elevation = elevation
                )
                .semantics(mergeDescendants = false) {}
                .pointerInput(Unit) {},
            propagateMinConstraints = true
        ) {
            content()
        }
    }
}

Y Modifier.surface()

private fun Modifier.surface(
    shape: Shape,
    backgroundColor: Color,
    border: BorderStroke?,
    elevation: Dp
) = this.shadow(elevation, shape, clip = false)
    .then(if (border != null) Modifier.border(border, shape) else Modifier)
    .background(color = backgroundColor, shape = shape)
    .clip(shape)

Otra cosa interesante es que es Box con propagateMinConstraints = true parámetro que obliga al primer descendiente a tener las mismas restricciones o dimensiones mínimas

Surface(
    modifier = Modifier.size(200.dp),
    onClick = {}) {
    Column(
        modifier = Modifier
            .size(50.dp)
            .background(Color.Red, RoundedCornerShape(6.dp))
    ) {}
}

Spacer(modifier = Modifier.height(20.dp))

Surface(
    modifier = Modifier.size(200.dp),
    onClick = {}) {
    Column(
        modifier = Modifier
            .size(50.dp)
            .background(Color.Red, RoundedCornerShape(6.dp))
    ) {
        Column(
            modifier = Modifier
                .size(50.dp)
                .background(Color.Green, RoundedCornerShape(6.dp))
        ) {}

    }
}

Spacer(modifier = Modifier.height(20.dp))

Box(
    modifier = Modifier.size(200.dp)
) {
    Column(
        modifier = Modifier
            .size(50.dp)
            .background(Color.Red, RoundedCornerShape(6.dp))
    ) {
        Column(
            modifier = Modifier
                .size(50.dp)
                .background(Color.Green, RoundedCornerShape(6.dp))
        ) {}

    }
}

En el primer ejemplo en Surface efectivo Column tener 200.dp tamaño a pesar de que tiene Modifier.size(50.dp).

En segundo ejemplo Box en el interior Column tiene un tamaño de 50.dp porque no es un descendiente directo de Surface.

En el tercer ejemplo si reemplazamos Surface(Caja con propagateMinConstraints true) con Box permite que el descendiente directo use sus propias restricciones o dimensiones.

ingrese la descripción de la imagen aquí

¿Ha sido útil esta solución?