Compose: wrap text in Row layout, instead of pushing siblings out - android

I'm dipping my toes into Jetpack Compose, but I'm stumped by the behaviour of Row. I have a text next to an icon button, and I want the icon button to be anchored to the side with a minimum width of 48dp, and have text wrap around it. Like this:
But the text does not wrap, it eats up all the space in the Row:
#Composable
fun SampleLayout(text: String) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
) {
Text(text)
IconButton(
onClick = { },
) {
Icon(
imageVector = androidx.compose.material.icons.Icons.Default.StarBorder,
null
)
}
}
}
#Preview(showBackground = true, backgroundColor = 0x006EAEA0, fontScale = 1.5F)
#Composable
fun SamplePreview1() {
Box(Modifier.padding(16.dp)) {
SampleLayout("helooooo")
}
}
#Preview(showBackground = true, backgroundColor = 0x006EAEA0, fontScale = 1.5F)
#Composable
fun SamplePreview2() {
Box(Modifier.padding(16.dp)) {
SampleLayout("helooooooooooooooooooooooooooo")
}
}
#Preview(showBackground = true, backgroundColor = 0x006EAEA0, fontScale = 1.5F)
#Composable
fun SamplePreview3() {
Box(Modifier.padding(16.dp)) {
SampleLayout("heloooooooooooooooooooooooooooooooooooooooo")
}
}
I've tried setting the minimum width of the icon 48dp, but the text then still fills until the end of the row.
How can I make sure the the Text width does not go further than the icon button?

By default Text has a higher layout priority than Icon in order to fill the necessary space. You can change this with the weight modifier.
After using this modifier, the size of Icon will be calculated before Text:
The parent will divide the vertical space remaining after measuring unweighted child elements
Also weight has a fill parameter, which is set to true by default. This is equivalent to fillMaxWidth (when weight is used inside a Row), so you can skip the fillMaxWidth modifier in your parent. When you don't need this behaviour, pass false to this parameter.
Row{
Text(text, modifier = Modifier.weight(1f))
IconButton(
onClick = { }
) {
Icon(
imageVector = Icons.Default.StarBorder,
null
)
}
}

Try this
#Composable
fun SampleLayout(text: String) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
) {
Text(text = text)
IconButton(
modifier = Modifier.weight(1f, fill = false), //You must add the false fill here to keep it from occupying all the available space
onClick = { },
) {
Icon(
imageVector = androidx.compose.material.icons.Icons.Default.StarBorder,
null
)
}
}
}

Related

How to add compose modifiers based on other surrounding views?

I am unable to get the expected results as shown in the picture below. There are 2 rules to follow
The horizontal line should not continue till the bottom text. Instead, it should just be the height of the right text (multiline).
Bottom text should align with the Right Text from the left side.
Current Incorrect Snippet
#Composable
fun Sample() {
Row(
modifier = Modifier
.height(IntrinsicSize.Min)
.padding(10.dp)
) {
Text("Left Text")
Divider(
Modifier
.padding(horizontal = 10.dp)
.fillMaxHeight()
.width(4.dp),
color = Color.Black
)
Column {
Text("Right Looooong Text")
Text("Bottom Text")
}
}
}
Visual Representation
You can achieve this in various ways including
Option 1: You can either redesign your Composable
Option 2: Apply Modifier.layoutId() your Composables then set their position relative to each other using Layout and getting measurables via this ids then placing them based on one that they depend on.
I post only the option one which is the easiest one.
#Composable
fun Sample(horizontalPadding: Dp = 10.dp, dividerWidth: Dp = 4.dp) {
Row(
modifier = Modifier.padding(10.dp)
) {
Text("Left Text")
Column {
Row(modifier = Modifier.height(IntrinsicSize.Min)) {
Divider(
Modifier
.padding(horizontal = horizontalPadding)
.fillMaxHeight()
.width(dividerWidth),
color = Color.Black
)
Text("Right Loooooooooooooooooooong Text")
}
Text(
"Bottom Text",
modifier = Modifier.offset(x = horizontalPadding * 2 + dividerWidth)
)
}
}
}
Result
You can use the modifier extension .onSizeChanged{}, which returns its composable's size in Int, to set the vertical divider's height as follows:
var divHeight: Int = 0 // Must be initialized
Row() {
Text(text = "Left Text")
Divider(
modifier = Modifier
.padding(horizontal = 10.dp)
.width(4.dp)
.height(divHeight.dp) // .dp converts measurement from Int to Dp
)
Column {
Text(
modifier = Modifier
.onSizeChanged { divHeight = it.height },
text = "Right Looooong Text"
)
Text(text = "Bottom Text")
}
}
Hope you found this helpful!

Jetpack Compose Button padding outside of border: why?

Using Button, I want to make its diminensions such that its border hugs its width, with a minimum width and height of 40dp. In the sample below, I like the looks of the BigNumber preview. It does not have any outside horizontal padding. The Default preview does have padding outside the border. How do I fix this without setting an absolute width? Consider this sample:
#Composable
fun BasketQuantityStepper(
quantityControlsViewState: QuantityControlsViewState,
onClick: () -> Unit,
modifier: Modifier = Modifier,
) {
Button(
onClick = onClick,
colors = ButtonDefaults.buttonColors(backgroundColor = colorResource(id = R.color.basketQuantityStepperBackground)),
border = BorderStroke(dimensionResource(id = R.dimen.buttonBorderWidth), colorResource(id = R.color.basketQuantityStepperBorderColor)),
modifier = modifier
.heightIn(min = 40.dp)
.widthIn(min = 40.dp),
) {
Text(
text = "${quantityControlsViewState.currentQuantity}",
)
}
}
#Preview
#Composable
private fun PreviewDefault() {
BasketQuantityStepper(quantityControlsViewState = QuantityControlsViewState(
currentQuantity = 1,
minOrderQuantity = 1,
maxOrderQuantity = 10,
stepQuantity = 1
), onClick = {})
}
#Preview
#Composable
private fun PreviewBigNumber() {
BasketQuantityStepper(quantityControlsViewState = QuantityControlsViewState(
currentQuantity = 100,
minOrderQuantity = 1,
maxOrderQuantity = 1000,
stepQuantity = 1
), onClick = {})
}
Minimum dimension of Composables' touch area is 48.dp by default for accessibility.
You can remove it by wrapping your button with
CompositionLocalProvider(LocalMinimumTouchTargetEnforcement provides false) {
}
but it's not advised to have Composable's smaller than accebility size. Icons, CheckBox, even Slider uses 48.dp by default.
CompositionLocalProvider(LocalMinimumTouchTargetEnforcement provides false) {
Button(
onClick = {},
border = BorderStroke(2.dp, Color.LightGray),
modifier = Modifier
.border(2.dp, Color.Green)
.heightIn(min = 40.dp)
.widthIn(min = 40.dp),
) {
Text(
text = "$counter",
)
}
}
https://developer.android.com/jetpack/compose/accessibility
Remove Default padding around checkboxes in Jetpack Compose new update

JetPack Compose - remove extra padding from view

I have a few Icons in a row
Row {
IconButton {
Icon(
painter = painterResource(R.drawable.im1)
)
},
IconButton {
Icon(
painter = painterResource(R.drawable.im2)
)
}
}
But when it's displayed the distance between 2 icons in the row is bigger then I expect. I feel like there is 32dp spacer between them. How can I decrease the distance between 2 icons inside a row?
The space between the 2 icons it is not a padding and depends by the default size of the IconButton.
It is due to accessibility touch targets and allows the correct minimum touch target size.
You can change it disabling the LocalMinimumTouchTargetEnforcement and applying a Modifier.size(24.dp) to the IconButton:
CompositionLocalProvider(LocalMinimumTouchTargetEnforcement provides false){
Row {
IconButton(modifier = Modifier.size(24.dp),
onClick = {}) {
Icon(
painter = painterResource(R.drawable.ic_add_24px),""
)
}
IconButton(modifier = Modifier.size(24.dp),
onClick = {}) {
Icon(
painter = painterResource(R.drawable.ic_add_24px),""
)
}
}
}
As alternative you can use an Icon with the Modifier.clickable:
Row {
Icon(
painter = painterResource(R.drawable.ic_add_24px),"",
modifier = Modifier.clickable (onClick = {} )
)
Icon(
painter = painterResource(R.drawable.ic_add_24px),"",
modifier = Modifier.clickable (onClick = {} )
)
}
If you wish to control the exact distance, it is viable to implement a Layout, giving you complete control over the offset (yes you could also just use the offset Modifier, but I find this more promising.
Layout(
content = {
IconButton {
Icon(
painter = painterResource(R.drawable.im1)
)
},
IconButton {
Icon(
painter = painterResource(R.drawable.im2)
)
}
}
){ measurables, constraints ->
val icon1 = measurables[0].measure(constraints)
val icon2 = measurables[1].measure(constraints)
layout(constraints.maxWidth, constraints.maxHeight){ //Change these to suit your requirements
//Use place(x, y) to specify exact co-ordinates within the container
icon1.place(0, 0)
icon2.place(icon1.width, 0) //Places Directly where icon1 ends
/*If padding still appears, you can subtract some function from the second icon's starting point to make it even closer than the edge of iicon1
*/
}
This should be it!

How do I create a Jetpack Compose Column where a middle child is scrollable but all of the other children are always visible?

I am creating a Jetpack Compose Dialog that contains a Column where all of the elements should always be visible, except for the third element, which is a Text that should be scrollable if the text doesn't fit the screen space. I almost achieved this with a secondary scrollable Column just for that Text element. However, this implementation pushes the bottom child (a button) out of view if there is a lot of text. Here is my code:
#Composable
fun WelcomeView(
viewModel: WelcomeViewModel,
onDismiss: () -> Unit
) {
Dialog(onDismissRequest = onDismiss) {
Box(
modifier = Modifier
.clip(RoundedCornerShape(Spacing.extraLarge))
.background(Colors.backgroundBase)
.padding(all = Spacing.medium)
) {
Column {
IconView(
name = IconViewNames.RUNNING_SHOE,
size = IconViewSizes.LARGE,
color = Colors.primaryBase
)
Text(
viewModel.title,
style = Text.themeBillboard,
modifier = Modifier.padding(bottom = Spacing.medium)
)
Column(
modifier = Modifier
.verticalScroll(rememberScrollState())
) {
Text(
viewModel.message,
style = Text.themeHeadline,
modifier = Modifier.padding(bottom = Spacing.medium)
)
}
Button(
onClick = onDismiss,
modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(Spacing.medium))
.background(Colors.primaryBase)
) {
Text(
"Continue",
style = Text.themeHeadline.copy(color = textExtraLight),
modifier = Modifier.padding(all = Spacing.extraSmall)
)
}
}
}
}
}
#Preview
#Composable
fun PreviewWelcomeView() {
WelcomeView(
viewModel = WelcomeViewModel(
firstName = "Wendy",
htmlWelcomeMessage = PreviewTextFixtures.threeParagraphs
),
onDismiss = {}
)
}
This is what it (correctly) looks like when there is only one paragraph of text:
But this is what it looks like when there are three paragraphs. The text scrolls correctly, but notice the missing "Continue" button:
Use this for your middle (scrollable) composable
Column(
modifier = Modifier
.verticalScroll(rememberScrollState())
.weight(weight =1f, fill = false)
) {
Text("Your text here")
}
The key is to use fill = false.
Just apply to the scrollable Column the weight modifier.
Something like:
Column (verticalArrangement= Arrangement.SpaceBetween) {
Text(
"viewModel.title",
)
Column(
modifier = Modifier
.verticalScroll(rememberScrollState())
.weight(1f, false)
) {
Text("...")
}
Button()
}

Android Jetpack compose IconButton padding

How to remove padding in an IconButton ? I want items in my column have the same start padding
Column(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
) {
IconButton(onClick = { }) {
Icon(asset = Icons.Filled.Search)
}
Text("Some text")
}
The space is due to accessibility touch targets and a default size of 48.dp.
Starting with 1.2.0 the best best way to change the default behaviour and remove the extra space is disabling the LocalMinimumTouchTargetEnforcement and applying a size modifier:
CompositionLocalProvider(LocalMinimumTouchTargetEnforcement provides false) {
IconButton(
modifier = Modifier.size(24.dp),
onClick = { }
) {
Icon(
Icons.Filled.Search,
"contentDescription",
)
}
}
Pay attention because in this way it is possible that if the component is placed near the edge of a layout / near to another component without any padding, there will not be enough space for an accessible touch target.
With 1.0.0 the IconButton applies a default size with the internal modifier: IconButtonSizeModifier = Modifier.size(48.dp).
You can modify it using something like:
IconButton(modifier = Modifier.
then(Modifier.size(24.dp)),
onClick = { }) {
Icon(
Icons.Filled.Search,
"contentDescription",
tint = Color.White)
}
It is important the use of .then to apply the size in the right sequence.
Wrap the IconButton with CompositionLocalProvider to override the value of LocalMinimumTouchTargetEnforcement which enforces a minimum touch target of 48.dp.
CompositionLocalProvider(
LocalMinimumTouchTargetEnforcement provides false,
) {
IconButton(onClick = { }) {
Icon(
imageVector = Icons.Filled.Search,
contentDescription = "Search",
)
}
}
If you use IconButton just for handling click listener, instead of:
IconButton(onClick = { // Todo -> handle click }) {
Icon(asset = Icons.Filled.Search)
}
You can use:
Icon(
asset = Icons.Filled.Search,
modifier = Modifier.clickable { // Todo -> handle click },
)
With this way you don't need to remove extra paddings.

Categories

Resources