Reputation: 40572
There's a Surface composable in Jetpack Compose which represents a material surface. A surface allows you to set up things like background color or border but it seems that the same might be done using modifiers. When should I use the Surface composable and what the benefits does it give me?
Upvotes: 105
Views: 58442
Reputation: 40572
The Surface composable makes the code easier as well as explicitly indicating that the code uses a material surface. Let's see an example:
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)
)
}
and the result:
The same result can be achieved without 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)
)
but it has a few drawbacks:
backgroundColor
is used in two places as well.Surface
adjusts colors for elevation (in case of a dark theme) according to the material design. If you want the same behavior, it should be handled manually.For the full list of Surface features it's better to take a look at the documentation.
Upvotes: 119
Reputation: 66674
Surface
is a Box
with a Modifier.surface()
and material colors and elevation, it checks elevation of ancestors to be always on top of them, and only overload below blocking touch propagation behind the surface with 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()
}
}
}
And 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)
Another interesting thing is it is Box
with propagateMinConstraints = true
parameter which forces first descendant to have same minimum constraints or dimensions
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))
) {
Box(
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))
) {
Box(
modifier = Modifier
.size(50.dp)
.background(Color.Green, RoundedCornerShape(6.dp))
)
}
}
In first example on Surface
forces Column
to have 200.dp size even though it has Modifier.size(50.dp)
.
In second example Box
inside Column
has 50.dp size because it's not a direct descendant of Surface
.
In third example if we replace Surface
(Box with propagateMinConstraints true) with Box
it allows direct descendant to use its own constraints or dimensions.
Upvotes: 28
Reputation: 649
Surface is the equivalent of CardView
in view system.
By Surface
, you can set elevation for the view (note that this is not the same with Modifier.shadow)
Upvotes: 9