nuhkoca
nuhkoca

Reputation: 1943

How to centrally align Icon to first line of a Text component in Compose?

I have already built most of the component below but I am stuck on a detail. The detail is that the whole content should be vertically centered and additionally the Icon component should be centrally aligned to the first line of the Text component.(Below is the desired output)

enter image description here

@Composable
private fun MyBaseComponent(
    @DrawableRes iconResId: Int? = null,
    title: String? = null,
    subtitle: String,
    backgroundColor: Color,
    itemColor: Color
) {
    Box(
        modifier = Modifier
            .heightIn(min = 48.dp)
            .fillMaxWidth()
            .background(color = backgroundColor, shape = RoundedCornerShape(2.dp))
    ) {
        Row(
            modifier = Modifier.padding(MyTheme.spacing.double),
            verticalAlignment = Alignment.CenterVertically
        ) {
            iconResId?.let {
                MyIcon(iconResId = iconResId, tint = itemColor)
                Spacer(modifier = Modifier.padding(end = MyTheme.spacing.double))
            }

            Column(verticalArrangement = Arrangement.spacedBy(MyTheme.spacing.small)) {
                title?.let { MyTitle(text = title, color = itemColor) }
                MySubtitle(text = subtitle, color = itemColor)
            }
        }
    }
}

How it looks like at the moment. Everything seems to be alright but except the icon positioning.

enter image description here

Upvotes: 11

Views: 7689

Answers (3)

Jfreu
Jfreu

Reputation: 589

A simple way is to use the parent composable padding to align the top of the icon and the text:

@Preview
@Composable
fun IconText(
) {
    val title =
        "Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed do."
    val subtitle =
        "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
    Box(
        modifier = Modifier
            .heightIn(min = 48.dp)
            .background(color = Color.White, shape = RoundedCornerShape(2.dp))
    ) {
        Box(modifier = Modifier.padding(top = 40.dp, bottom = 40.dp)) {
            Image(
                painter = painterResource(id = R.drawable.ic_check),
                colorFilter = ColorFilter.tint(Color.Gray),
                contentDescription = "",
                modifier = Modifier.padding(start = 10.dp)
            )
            Column(
                verticalArrangement = Arrangement.spacedBy(10.dp),
                modifier = Modifier.padding(start = 40.dp, end = 40.dp)
            ) {
                Text(
                    text = title,
                    color = Color.Black,
                    fontSize = 15.sp,
                    fontWeight = FontWeight.Bold
                )
                Text(text = subtitle, color = Color.Black)
            }
        }
    }
}

Result:

padding align

A more complicated way is to use 'onTextLayout' to find the location of the line:

@Composable
fun TextLineIcon(
    text: String,
    icon: ImageVector,
    modifier: Modifier = Modifier,
    color: Color = Color.Unspecified,
    fontSize: TextUnit = TextUnit.Unspecified,
    fontWeight: FontWeight? = null,
    iconRightPadding: Dp = 0.dp,
    iconLine: Int = 0,
    iconTint: Color = Color.Black
    // etc
) {
    val painter = rememberVectorPainter(image = icon)
    var lineTop = 0f
    var lineBottom = 0f
    var lineLeft = 0f
    with(LocalDensity.current) {
        val imageSize = Size(icon.defaultWidth.toPx(), icon.defaultHeight.toPx())
        val rightPadding = iconRightPadding.toPx()
        Text(
            text = text,
            color = color,
            fontSize = fontSize,
            fontWeight = fontWeight,
            onTextLayout = { layoutResult ->
                val nbLines = layoutResult.lineCount
                if (nbLines > iconLine) {
                    lineTop = layoutResult.getLineTop(iconLine)
                    lineBottom = layoutResult.getLineBottom(iconLine)
                    lineLeft = layoutResult.getLineLeft(iconLine)
                }
            },
            modifier = modifier.drawBehind {
                with(painter) {
                    translate(
                        left = lineLeft - imageSize.width - rightPadding,
                        top = lineTop + (lineBottom - lineTop) / 2 - imageSize.height / 2,
                    ) {
                        draw(painter.intrinsicSize, colorFilter = ColorFilter.tint(iconTint))
                    }
                }
            }
        )
    }
}

Usage:

@Preview
@Composable
fun IconText2(
) {
    val title =
        "Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed do."
    val subtitle =
        "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
    Box(
        modifier = Modifier
            .heightIn(min = 48.dp)
            .background(color = Color.White, shape = RoundedCornerShape(2.dp))
    ) {
        Box {
            Column(
                verticalArrangement = Arrangement.spacedBy(10.dp),
                modifier = Modifier.padding(40.dp)
            ) {
                TextLineIcon(
                    title,
                    Icons.Filled.Check,
                    fontWeight = FontWeight.Bold,
                    fontSize = 15.sp,
                    iconRightPadding = 4.dp,
                    iconTint = Color.Gray
                )
                Text(text = subtitle, color = Color.Black)
                TextLineIcon(
                    title,
                    Icons.Filled.PlusOne,
                    fontWeight = FontWeight.Bold,
                    fontSize = 15.sp,
                    iconRightPadding = 4.dp,
                    iconLine = 1,
                    iconTint = Color.Red
                )

                Text(text = subtitle, color = Color.Black)
            }
        }
    }
}

You can even chose the line you want the icon to be aligned with.

Result: line align

Upvotes: 10

USMAN osman
USMAN osman

Reputation: 1030

verticalAlignment = Alignment.CenterVertically it will align every item inside the row vertically.
for example: your row is a size of 50.dp and column which contains the two textView are almost equal to 45.dp, alright? now that will aligned centerVertically that's fine but the problem is your icon which is size of for instance 24.dp if we look at the ratio of 50.dp(row) , 45.dp(column) then icon which is 24.dp pretty big according to 50.dp, we can't clearly see behaviour of 45.dp according to 50.dp but according to 24.dp it can clearly be seen.
You have to remove verticalAlignment = Alignment.CenterVertically from the row.

Sample:

@Composable
fun AlignmentProblem(
    modifier: Modifier = Modifier
) {
    Box(modifier = modifier) {
        Row(
            modifier = Modifier
                .fillMaxWidth()
                .height(400.dp)
                .background(Color.Cyan)
          
        ) {

            Icon(
                painter = painterResource(id = R.drawable.ic_chat),
                contentDescription = null
            )
            Column(
                modifier = Modifier.fillMaxWidth()
                    .background(Color.LightGray),
                horizontalAlignment = Alignment.CenterHorizontally
            ) {
                Text(text = "What is Lorem Ipsum?")
                Text(text = "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged.")
            }

        }
    }

}

Upvotes: -1

Róbert Nagy
Róbert Nagy

Reputation: 7602

Either use padding(top = x.dp) to offset the icon or create a different hierarchy, something like:

Column() {
  MyTitle() // Also align to end of composable
  Row {
     Icon()
     MySubtitle() // Align to end

  }
}

Might need some adjustmends but hopefully you get the idea

Upvotes: -1

Related Questions