Hey there. Would like some help with some layout i...
# compose
d
Hey there. Would like some help with some layout issues I'm having. I'm not sure if it's possible to do what I am trying to do in Compose šŸ§µ
I have a
TextButton
which which has a
Text
and
Icon
composable as children. I want the Text to be pinned to the start of the text button and the icon to be pinned to the left of the button. To do so, I set a
weight(1f)
modifier on the
Text
. However, doing so causes the entire button to grow to the maximum space allowed by the container of the
TextButton
. It acts as if there is a
fillMaxWidth
set as the modifier of the
TextButton
. I basically just want some type of flexible spacer in between the text and icon which will push the text and icons out to the edges of the button, but which does not cause the whole TextButton to grow. Is this possible?
The first image of previews is how things look if I don't apply the
weight(1f)
modifier to the Text. The red background is the background of the
Row
containing the
Text
and
Icon
. The top 4 previews do not have a
fillMaxWidth()
modifier applied to the
TextButton
, but the bottom one does. I want it such that the button looks like the top for unless I apply a
fillMaxWidth()
modifier to the
TextButton
, in which case, I want the red area to take up the entire space of the of the button, like in the second attached preview.
z
Does passing this to your
Row
fix it?
Copy code
horizontalArrangement = Arrangement.SpaceBetween
d
It does not šŸ˜•
e
That sounds like a spacer with weight(1f) in btw the text & icon
z
I wrote a small reproducer and adding the horizontal arrangement fixed it for me, so obviously iā€™m missing something from your actual code
Whatā€™s the parent of the
Row
? It must not be propagating its minimum constraints
d
This is a minimal reproducible example. I actually couldn't figure out how to get the Icon to accept a Material Icon:
Copy code
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.ButtonColors
import androidx.compose.material.ButtonDefaults
import androidx.compose.material.Icon
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowDropDown
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp

@Composable
fun SpinnerButtonTest(
    onClick: () -> Unit,
    modifier: Modifier = Modifier,
    enabled: Boolean = true,
) {
    val borderColor = Color.LightGray

    val backgroundColor = Color.White

    val textColor = Color.Black

    val border = BorderStroke(
        color = borderColor,
        width = 1.dp,
    )
    val colors: ButtonColors = ButtonDefaults.textButtonColors(
        backgroundColor = backgroundColor,
        contentColor = textColor,
    )

    androidx.compose.material.TextButton(
        onClick = onClick,
        modifier = modifier,
        shape = RoundedCornerShape(8.dp),
        border = border,
        colors = colors,
        contentPadding = PaddingValues(
            start = horizontalScreenSpace,
            top = xsmallSpace,
            end = horizontalScreenSpace,
            bottom = xsmallSpace,
        ),
        enabled = enabled,
    ) {
        Row(
            modifier = Modifier
                .background(Color.Red),
            verticalAlignment = Alignment.CenterVertically,
            horizontalArrangement = Arrangement.SpaceBetween,
        ) {
            Text(
                modifier = Modifier.weight(1f),
                text = "Text",
                style = TextStyles.editText,
                color = textColor,
            )

            Icon(
                icon = Icons.Filled.ArrowDropDown,
                contentDescription = null,
                tint = textColor,
            )
        }
    }
}

@Composable
@Preview
private fun SpinnerButtonTestPreview() {
    SpinnerButtonTest(
        onClick = {},
    )
}

@Composable
@Preview
private fun SpinnerButtonTestFullWidthPreview() {
    SpinnerButtonTest(
        modifier = Modifier
            .fillMaxWidth(),
        onClick = {},
    )
}
e
This is what you need
Copy code
Button(
    modifier = Modifier
      .fillMaxWidth() <-- Turn this off or on to see behavior
      .width(IntrinsicSize.Min) <-- specifically this
    ,
    onClick = { /*TODO*/ }
  ) {
    Text(text = "Lol")
    Spacer(Modifier.weight(1f, fill = true))
    Icon(Icons.Default.Add, contentDescription = null)
  }
d
The inner Row is so that I can have this:
Copy code
verticalAlignment = Alignment.CenterVertically,
e
Button already has an internal
Row
no?
Oh woops, TextButton NOT Button
z
TextButton uses Button internally, and yes it has a row
šŸ‘šŸ¾ 1
d
Yes, but I need the vertical alignment set, which the row of the button does not do
With
width(IntrinsicWidth.Min)
z
I would probably just copy `Button`ā€™s implementation at this point
e
What verdion of compose are you on? My (1.5.4) has vertical alignment of center vertically šŸ¤”
z
Imo, a generic
Button
should not be opinionated about its contentsā€™ layout, but that ship sailed long ago
d
What verdion of compose are you on? My (1.5.4) has vertical alignment of center vertically
I just checked and it is indeed setting CenterVertically. But I still have the same issue if I remove the inner row
e
This is mine, vanilla Button, hmm
d
The top preview shouldnot go full width
OK, so using @efemoneyā€™s code, I was able to get it to work. The trick is that I need to pass in those modifiers to the
SpinnerButtonTest
component rather than having them included in the component
e
You can include the intrinsic modifier one inside the component, on top of whatever is passed in from outside, that is what does the ā€œmagicā€ (basically says the ā€œcontentā€ should try to be as small as possible)
d
Yup, just did that and all is good. Thanks guys
Final code for posterity:
Copy code
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.ButtonColors
import androidx.compose.material.ButtonDefaults
import androidx.compose.material.Icon
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp

@Composable
fun SpinnerButtonTest(
    onClick: () -> Unit,
    modifier: Modifier = Modifier,
    enabled: Boolean = true,
) {
    val borderColor = Color.LightGray

    val backgroundColor = Color.White

    val textColor = Color.Black

    val border = BorderStroke(
        color = borderColor,
        width = 1.dp,
    )
    val colors: ButtonColors = ButtonDefaults.textButtonColors(
        backgroundColor = backgroundColor,
        contentColor = textColor,
    )

    androidx.compose.material.TextButton(
        onClick = onClick,
        modifier = modifier
            .width(IntrinsicSize.Min),
        shape = RoundedCornerShape(8.dp),
        border = border,
        colors = colors,
        contentPadding = PaddingValues(
            start = horizontalScreenSpace,
            top = xsmallSpace,
            end = horizontalScreenSpace,
            bottom = xsmallSpace,
        ),
        enabled = enabled,
    ) {
        Text(
            text = "Text",
            style = TextStyles.editText,
            color = textColor,
        )

        Spacer(
            modifier = Modifier
                .defaultMinSize(minWidth = 8.dp)
                .weight(1f),
            )

        Icon(
            painter = painterResource(id = com.ifttt.uicore.R.drawable.ifd_arrow_drop_down),
            contentDescription = null,
            tint = textColor,
        )
    }
}

@Composable
@Preview
private fun SpinnerButtonTestPreview() {
    SpinnerButtonTest(
        onClick = {},
    )
}

@Composable
@Preview
private fun SpinnerButtonTestFullWidthPreview() {
        SpinnerButtonTest(
        modifier = Modifier
            .fillMaxWidth(),
        onClick = {},
    )
}
šŸ¦œ 1