Reputation: 1079
Documentation says that Modifiers are applied from the left. But from this example it looks like they are applied from the right: First border and then padding because there is no space between text and border
Text("Hi there!", Modifier.padding(10.dp).border(2.dp, Color.Magenta))
Upvotes: 42
Views: 25854
Reputation: 66599
The Modifier allows us to customize the appearance of the composable. Using it, you can:
Based on which order you place these modifiers your Composable's visual and behavioral structure is shaped.
Most of the modifiers are applied from from top to bottom or left to right. One exception is Modifier.pointerInput() it gets applied from right to left or bottom to top by default pass.
Modifier.padding()
in Jetpack Compose acts as padding or margin depending on order.
Modifier.padding(10.dp).size(200.dp)
adds space before setting size you have a Composable with 200.dp size
Modifier.size(200.dp).padding(10.dp)
adds padding which you have 180.dp width and height after setting 10.dp padding on each side.
Box(
Modifier
.border(2.dp, Color.Green)
.padding(10.dp)
.border(2.dp, Color.Red)
.size(200.dp)
)
Box(
Modifier
.border(2.dp, Color.Cyan)
.size(200.dp)
.padding(10.dp)
.border(2.dp, Color.Magenta)
)
And padding modifiers are cumulative. Modifier.padding(20.dp).padding(20.dp)
is summed as 40.dp.
Box(
Modifier
.border(2.dp, Color.Green)
.padding(20.dp)
.border(2.dp, Color.Red)
.size(200.dp)
)
Box(
Modifier
.border(2.dp, Color.Green)
.padding(20.dp)
.padding(20.dp)
.border(2.dp, Color.Red)
.size(200.dp)
)
Another modifier that changes appearance of Composable based on which order it's applied. For shadow to be applied as outside of Composable it should be applied before background or other modifiers. If you apply it after Modifier.background
you can have outer shadow.
Box(
Modifier
.shadow(5.dp, RoundedCornerShape(8.dp))
.background(Color.White)
.size(100.dp)
)
Box(
Modifier
.background(Color.White)
.size(100.dp)
.shadow(5.dp, RoundedCornerShape(8.dp))
)
This Modifier also clips the Modifiers depending on order it's placed. Good thing with this modifier if you place it before Modifier.clickable{}
you can change or clip clickable area of a Composable. Having a circle, triangle or diamond circle area or creating before/after layout is possible using this modifier and Shape
s.
It's Modifier.graphicsLayer{}
under the hood, you can check out my detailed answer about it here, here and here. It helps you create complex layouts using scale, shape, clip, translate, and other cool properties.
This Modifier is useful for changing position of a Composable after it's laid out unlike Modifier.padding changing value of this Modifier does not change position of a Composable relative to its sibling Composable. However depending on where you set Modifier.offset you can change touch area of a Composable and it has two variants. One that takes lambda defers state read which is advised by google over the one that takes value.
I used one with value for demonstration. You can see if offset is applied first ever modifier that follow offset is moved as Slider changes values. In second example touch area of Composable is not changed because Modifier.clickable{}
is applied before Modifier.offset{}
var offset by remember {
mutableStateOf(0f)
}
Box(
Modifier
.offset(x = offset.dp)
.clickable {}
.background(Color.Red)
.size(100.dp)
)
Box(
Modifier
.clickable {}
.offset(x = offset.dp)
.background(Color.Red)
.size(100.dp)
)
Slider(value = offset, onValueChange = { offset = it }, valueRange = 0f..200f)
This modifier is basis of gesture and touch events. Using it drag, tap, press, double tap, zoom, rotation and many gesturer can be invoked. In this answer how it's used is explained to build onTouchEvent counterpart of View system.
Unlike Modifiers above it propagates by default from bottom to top unless you consume PointerInputChange. In Compose gesture system consuming continuous events cancel next one in line to receive it. So you can prevent gestures like scroll not happening when you zoom an image for instance.
Modifier
.pointerInput() // Second one that is invoked
.pointerInput() // First one that is invoked
Upvotes: 6
Reputation: 40572
There’s Layouts in Jetpack Compose codelab containing Layout modifiers under the hood step which explains the modifier order, see "Order matters" section.
order matters when chaining modifiers as they're applied to the composable they modify from earlier to later, meaning that the measurement and layout of the modifiers on the left will affect the modifier on the right. The final size of the composable depends on all modifiers passed as a parameter. First, modifiers will update the constraints from left to right, and then, they return back the size from right to left.
To understand it better I'd recommend to figure out how layouts work in Compose. In short, padding() is a LayoutModifer, it takes in some constraints, measures its child size based on a projection of that constraints and places the child at some coordinates.
Let’s see an example:
Box(
modifier = Modifier
.border(1.dp, Color.Red)
.size(32.dp)
.padding(8.dp)
.border(1.dp, Color.Blue)
)
And the result:
But let's swap the .size()
and the .padding()
Box(
modifier = Modifier
.border(1.dp, Color.Red)
.padding(8.dp)
.size(32.dp)
.border(1.dp, Color.Blue)
)
Now we have a different result:
I hope this sample helps you to figure out how the modifiers are applied.
One can expect that the red border should be the closest to the box since it was added first, so the order might seem reversed, but such an order has pros too. Let’s take a look at this composable:
@Composable
fun MyFancyButton(modifier: Modifier = Modifier) {
Text(
text = "Ok",
modifier = modifier
.clickable(onClick = { /*do something*/ })
.background(Color.Blue, RoundedCornerShape(4.dp))
.padding(8.dp)
)
}
Just by moving the modifier
to the arguments the composable allows its parents to add additional modifiers such as extra margin. Because the lastly added modifiers are the closest to the button, the border and the inner padding won’t be affected.
Upvotes: 120
Reputation: 1079
package com.example.myapplication import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import androidx.compose.foundation.* import androidx.compose.foundation.layout.padding import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.setContent import androidx.compose.ui.unit.dp class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { Text("Hi there!", Modifier .border(2.dp, Color.Green) .padding(50.dp) .border(2.dp, Color.Red) ) } } }
Upvotes: 18
Reputation: 4376
"Modifier elements may be combined using then. Order is significant; modifier elements that appear first will be applied first." @here
It applies to the outer layer first with padding 10.dp, then the border with color.Magenta, and so on ("left to right"). The 80.dp padding applies last to the inner layer.
@Composable
fun test() {
Text("Hi there!",
Modifier.background(color = Color.Green)
.padding(10.dp)
.border(2.dp, Color.Magenta)
.padding(30.dp)
.border(2.dp, Color.Red)
.padding(80.dp)
)
}
Upvotes: 3
Reputation: 1924
The first padding is like the margin for the element in this case.
Compare these Composables and you will see the difference.
@Composable
fun Example() {
// Default
Box(modifier = Modifier.background(Color.Cyan), alignment = Alignment.Center){
Text("Hi there!", Modifier.border(2.dp, Color.Magenta))
}
Divider()
// 10dp margin
Box(modifier = Modifier.background(Color.Cyan), alignment = Alignment.Center){
Text("Hi there!", Modifier.padding(10.dp).border(2.dp, Color.Magenta))
}
Divider()
// 10dp margin and 10dp padding
Box(modifier = Modifier.background(Color.Cyan), alignment = Alignment.Center){
Text("Hi there!", Modifier.padding(10.dp).border(2.dp, Color.Magenta).padding(10.dp))
}
}
Upvotes: 4