I have a Row with max width, I want to place a Icon at start then a text and then another Icon at end.
I have specific sizes of icon and I want the Text to fill up the remaining space.
Row(
Modifier
.fillMaxWidth()
.height(30.dp)
){
Icon(Modifier.size(20.dp))
Text() // Fill this with remaining space available
Icon(Modifier.size(20.dp))
}
If I do fillMaxWidth in text then Icons goes out of the view.
How to do that?
You can apply Modifier.weight(1f) to the Text composable.
Something like:
Row(
Modifier
.fillMaxWidth()
.height(30.dp)
){
Icon(Icons.Filled.Add,"", Modifier.size(20.dp))
Text("Text",Modifier.weight(1f)) // Fill this with remaining space available
Icon(Icons.Filled.Add,"", Modifier.size(20.dp))
}
Related
I have a a text element with an icon to its right, both wrapped in a Row. The text has a 1.0f weight with fill = false. When a word wraps to the next line, the text has some padding at the end of it causing the icon to be too far apart from it. This is what it looks like:
App Example
Here is the code:
Row(
modifier = Modifier.width(150.dp)
) {
Text(
"John Doe John Doe John Doe",
modifier = Modifier.weight(1.0f, fill = false).background(Color.Green)
)
Icon(
imageVector = Icons.Default.Done,
contentDescription = "",
)
}
How do I make it so the text's width wraps itself without adding that extra spacing?
This is a normal behavior. It happens because you set weight for your Text and a width modifier for your Row.
It's kind of unavoidable when setting a specific width or height. It may happen even if you set weight for both Composables inside the Row (0.1f and 0.9f). Because the word can't fit in the remaining space but the Composable must fill the Row
You set the row width to 150.dp and when your Text has a weight modifier, it fills the row even if a word in your Text composable doesn't fit in the line.
I have a simple layout comprised of a Row with:
Red box on the left that should fill the height and have the same width (square)
Column with a couple of texts on the right
Row(
Modifier
.background(Color.Green)
.height(IntrinsicSize.Min)
) {
Box(
Modifier
.background(Color.Red)
.fillMaxHeight()
.aspectRatio(1f)
)
Column(
Modifier
.weight(1f)
.padding(12.dp)
) {
Text("Hello Hello Hello Hello Hello Hello Hello Hello")
Text("Welcome")
Text("Bye")
Text("Foo")
Text("Bar")
}
}
The problem is that the last text (Bar) is sometimes cut off:
If I add another text (Hi for example), Bar will become visible but this time Hi will get cut off. The issue seems to be the aspectRatio(1f) because when I use simple width(100.dp), everything works fine.
Using compose 1.0.5. Any idea what might be wrong?
I have a TextField
TextField(
keyboardOptions = KeyboardOptions.Default.copy(
imeAction = ImeAction.Search,
),
keyboardActions = KeyboardActions(
onSearch = {
onSearchTextSubmit(searchText)
},
),
modifier = Modifier.focusRequester(focusRequester),
singleLine = true
)
the I click "Enter" on keyboard the input gets cut from the bottom.
The TextField is inside TopAppBar and I don't set any height or text size explicitly.
Is decreasing text size the only way to make it look good or there is a way to force TextField to adjust its height or text size out of the box?
You need to increase the height of the field by a modifier that looks like this:
modifier = Modifier.height(56.dp), //56 or higher!
Thus, todays options are:
Increase the height of the TextField
Decrease the size of the font
Check the surrounding containers like Card Box Row Column you will find one with a fixed Height it has impact on your composable you should increase that height i was having the same problem i found a card with fixed height 150.dp i change it to 160.dp and it works hope its the same for you
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))
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.
In Android Compose resulting Image is being constructed from the outside layer toward the Composable in the center.
This means that first defined Green border is outer border and the last defined Red border is inner border .
This is very confusing since Green Modifier that is closest to Text Composable in the Code is furthest from it in the result.
This is in contrast to SwiftUI where Modifiers appear in the same order both in the Code and in the resulting Image.
Modifier that is closest to the Composable in the Code is also closest to it in the resulting Image.
If you want to imagine that resulting Image is being constructed from the center where your Composable is positioned (like in SwiftUI) then Modifiers are applied in the opposite order from which they are given (from the bottom upward).
So if you have Text Composable with two border Modifiers
border Modifier that is furthest away from the Text Composable in the Code (the bottom Red one)
will be closest to the Text Composable in the resulting Image
Modifiers are applied from outer toward inner layer
Applying .border(2.dp, Color.Green) to the outmost layer
Applying .padding(50.dp) going inward
Applying .border(2.dp, Color.Red) to the innermost layer
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)
)
}
}
}
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))
}
}
"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)
)
}
The Modifier allows us to customize the appearance of the composable. Using it, you can:
Change a Composable’s appearance, size, offset, padding or margin
Add interactions, like making an element clickable, scrollable, draggable, or zoomable
Change its scale, position in screen while its layout somewhere different entirely, or shape that changes its touch area.
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()
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)
)
Modifier.shadow()
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))
)
Modifier.clip()
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 Shapes.
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.
Modifier.offset()
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)
Modifier.pointerInput(keys)
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
How exactly can you add Margin in Jetpack Compose?
I can see that there is a Modifier for padding with Modifier.padding(...) but I can't seem to find one for margins or am I blind?
Someone guide me please.
Thank you very much.
You can consider padding and margin as the same thing (imagine it as "spacing"). A padding can be applied twice (or more) in the same composable and achieve the similar behavior you would get with margin+padding. For example:
val shape = CircleShape
Text(
text = "Text 1",
style = TextStyle(
color = Color.White,
fontWeight = FontWeight.Bold,
textAlign = TextAlign.Center),
modifier = Modifier.fillMaxWidth()
.padding(16.dp)
.border(2.dp, MaterialTheme.colors.secondary, shape)
.background(MaterialTheme.colors.primary, shape)
.padding(16.dp)
)
Will result on this:
As you can see, the first padding is adding a space between the component and its border. Then the background and border are defined. Finally, a new padding is set to add space between the border and the text.
Thinking in terms of padding and margin you refer to the so-called box model that we are used to. There's no a box model in Compose but a sequence of modifiers which is applied to a given composable. The trick is that you can apply the same modifier like padding or border multiple times and the order of these matters, for example:
#Composable
fun PaddingExample() {
Text(
text = "Hello World!",
color = Color.White,
modifier = Modifier
.padding(8.dp) // margin
.border(2.dp, Color.White) // outer border
.padding(8.dp) // space between the borders
.border(2.dp, Color.Green) // inner border
.padding(8.dp) // padding
)
}
As the result you'll get this composable:
This design is well explained in the Modifiers documentation:
Note: The explicit order helps you to reason about how different modifiers will interact. Compare this to the view-based system where you had to learn the box model, that margins applied "outside" the element but padding "inside" it, and a background element would be sized accordingly. The modifier design makes this kind of behavior explicit and predictable, and gives you more control to achieve the exact behavior you want.
You can also use Spacer:
Spacer(modifier = Modifier.width(10.dp))
It represents an empty space layout, whose size can be defined using Modifier.width, Modifier.height and Modifier.size modifiers.
Suppose you want to add margin between 2 composables, then you can achieve it as
Text(
text = stringResource(id = R.string.share_your_posters),
fontSize = 16.sp,
color = Color.Black
)
Spacer(modifier = Modifier.width(10.dp))
Image(painter = painterResource(id = R.drawable.ic_starts), contentDescription = null)
The margin is different than padding, margin is the space outside the widget, where padding is the distance inside the widget, in old XML you could have decided explicitly which one to use, however the new compose way is different.
How compose treat paddings and margins?
There is an object which can be set as Parameter to the composable called Modifier, you can use this to do both margins and paddings.
Example of Padding:
Text(
text = "Test",
modifier = Modifier
.padding(16.dp)
.clickable { }
)
Example of Margin
Text(
text = "Test",
modifier = Modifier
.clickable { }
.padding(16.dp)
)
As you can see the order makes a difference here in compose, all the modifiers are implemented by order.
So from what I can understand after reading the documentation there is no margin modifier as such as the API designer felt it is redundant to give something different name which essentially does the same thing.
So let's say you want to apply a margin of 8dp before colouring your container with yellow background and you want the container with a padding of 4dp for the content.
Column(modifier = Modifier.padding(all = 8.dp)
.background(color = Color.Yellow)
.padding(all=4.dp)) {
Text(text = "Android")
...
}
Here in the above example you can see that I have applied the padding first and after that I have added background colour to the container and finally the last padding. And here's how it looks. Just like we intended.
I was also looking for something which should give me a direct option to set margin on a View like TextView. But unfortunately we don't have margin support in Jetpack compose. But the good news is we can still achieve it by using layout container like Box, which allows us to add views like TextView, ImageView etc.
So you can add margin to any of the child(TextView) by using padding modifier to the parent(Box).
Here is the code:
Box(Modifier.padding(10.dp)) {
Surface(color = Color.LightGray) {
Text(text = "Hello $text!", color = Color.Blue,
modifier = Modifier.padding(16.dp))
}
}
And the result is:
Here I have given 10.dp padding to the box.
Hope it is useful.
You can achieve the same effect as margin with putting your content, that has padding, inside a different composable like Box and make outer composable clickable. With this approach, inner padded areas will be included in clickable content.
You can achieve a margin effect by using nested Surface elements with padding
e.g.
#Composable
fun MainScreen() {
Surface(color=Color.Yellow, modifier=Modifier.padding(10.dp)){
Surface(color=Color.Magenta, modifier=Modifier.padding(30.dp)) {
Surface(
color = Color.Green,
modifier = Modifier.padding(10.dp).wrapContentSize()) {
Text(text = "My Dummy Text", color = Color.Black)
}
}
}
}