I have a Jetpack Compose composable in a column beginning with a icon, title, textbody and a pager row:
Box(modifier = Modifier
.fillMaxSize()
.background(color = TVTheme.colors.blue)
) {
val bigPadding = 334.dp
val smallPadding = 24.dp
Column(
verticalArrangement = Arrangement.Bottom,
horizontalAlignment = Alignment.CenterHorizontally
) {
Image(
modifier = Modifier
.requiredSize(128.dp, 17.dp),
icon = R.drawable.calendar_a_icon
)
// title
val title = "Lorem Ipsum Dolor Sit"
if (title.isNotEmpty()) {
Text(
text = title,
textAlign = TextAlign.Center,
modifier = Modifier
.padding(top = dimensionResource(id = R.dimen.vertical), start = bigPadding, end = bigPadding),
overflow = TextOverflow.Visible
)
}
// body
val body = "Lorem ipsum dolor sit. Lorem ipsum dolor sit. Lorem ipsum dolor sit. Lorem ipsum dolor sit."
if (body.isNotEmpty()) {
Text(
text = body,
textAlign = TextAlign.Center,
modifier = Modifier
.padding(top = dimensionResource(id = R.dimen.vertical), start = bigPadding, end = bigPadding),
overflow = TextOverflow.Visible
)
}
Row(
modifier = Modifier
.padding(top = 44.dp, bottom = smallPadding, start = smallPadding, end = smallPadding)
.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
) {
val text = AnnotatedString.Builder(item.skipButtonText).toAnnotatedString()
ClickableText(
maxLines = 1,
text = "Skip",
onClick = {
skip()
}
)
Pager(
modifier = Modifier
.align(Alignment.CenterVertically),
index = currentPageIndex,
pageNumbers = totalPages)
}
}
}
which produces this screen:
The column has verticalArrangement = Arrangement.Bottom but obviously the column is not aligned to the bottom, it starts on top.
So how can I align the column to bottom as required in verticalArrangement = Arrangement.Bottom?
The verticalArrangement = Arrangement.Bottom places children vertically such that they are as close as possible to the bottom of the main axis.
In your case you have to apply Modifier.fillMaxHeight() to the Column:
Column(
verticalArrangement = Arrangement.Bottom,
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.fillMaxHeight()
) {
If you want to apply a fix padding, you can use something like modifier = Modifier.padding(xxx).fillMaxWidth().
// title
val title = "Lorem Ipsum Dolor Sit"
Text(
text = title,
textAlign = TextAlign.Center,
modifier = Modifier,
.padding(start = bigPadding, end = bigPadding).fillMaxWidth(),
overflow = TextOverflow.Visible
)
Related
I'm using constrainAs with Jetpack Compose to constrain a list of options.
To the left of the text there is a space, I believe this is caused by the fact that I constrain the text box to the start to the parent and the end to the start of the switch, but I need the text to wrap as shown in the second option so I think I need both of those constraints. I have tried several constraints but cannot figure out how to have the text left justified and have the wrapping. The problem is depicted in red in the image.
Also, I cannot figure out how to have the same spacing between the title and the description. This is shown in blue in the picture. I have the description constrained to the bottom of the title but when it wraps the text box becomes larger and is moved up and because the text gets centered it creates different spacing.
I have attached an image and the code.
#Composable
fun SwitchRow(title: String, description: String, enabled: Boolean) {
Box(modifier = Modifier
.height(66.dp)
.fillMaxWidth()
.padding(top = 12.dp, start = 16.dp, end = 8.dp, bottom = 12.dp)
) {
ConstraintLayout(
modifier = Modifier
.fillMaxWidth()
) {
val (titleText, descriptionText, switch) = createRefs()
Text(
text = title,
modifier = Modifier
.padding(bottom = 16.dp)
.constrainAs(titleText) {
start.linkTo(parent.start)
top.linkTo(parent.top)
},
color = MyAppTheme.colors.ice,
fontSize = 18.sp,
fontFamily = FontFamily(Font(R.font.barlow_regular, FontWeight.Normal)),
textAlign = TextAlign.Start
)
Text(
text = description,
modifier = Modifier
.wrapContentSize()
.constrainAs(descriptionText) {
start.linkTo(parent.start)
end.linkTo(switch.start)
top.linkTo(titleText.bottom)
bottom.linkTo(parent.bottom)
width = Dimension.fillToConstraints
},
color = MyAppTheme.colors.chalk,
fontSize = 14.sp,
fontFamily = FontFamily(Font(R.font.barlow_regular, FontWeight.Normal)),
maxLines = 2,
textAlign = TextAlign.Start
)
val checkedState = remember { mutableStateOf(true) }
Switch(modifier = Modifier
.background(color = Color.Gray)
.constrainAs(switch) {
top.linkTo(parent.top)
end.linkTo(parent.end)
},
enabled = enabled,
checked = checkedState.value,
onCheckedChange = { checkedState.value = it },
colors = SwitchDefaults.colors(
checkedThumbColor = MyAppTheme.colors.envy,
checkedTrackColor = MyAppTheme.colors.darkerEnvy,
uncheckedThumbColor = MyAppTheme.colors.navy,
uncheckedTrackColor = MyAppTheme.colors.darkerNavy,
),
)
}
}
}
I copy and paste the code into my editor and it gave me a lot of errors I couldn't solve. so I couldn't make you a copy and past the answer ... sorry for that.
But! look carefully!
the only thing you need to think about are Row() and Columns()
Insert this mindset into your way of thinking and it will make your life easy.
like CSS if you are familiar with web development (because I saw you wrote justify content).
look at this picture down bellow.
as you can see at the picture above there are two major properties of the rows, to Justify the content as you said you want to do. and you can list all the properties with ctrl + space
the first is horizontalArrangement = , and the second is verticalAlignment = as you can see in the code bellow.
Row(
horizontalArrangement = Arrangement.Start, // the properties you are looking for in a Row()
verticalAlignment = Alignment.CenterVertically, // // the properties you are looking for in a Row()
)
this is very confusing because a Column has really similar properties to justify its content too!
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
)
which the first is verticalArrangement = and the second is horizontalAlignment = and they are different words so be in focus when dealing with this deign so you wont confuse your self!
I included the property of the border in every row and column so you can see my way of thinking.
pro tip! use a lazy column so that the window size can be dynamic to your use.
after you will play with this it will be easier for you to make.
the weight() property of the modifier solves this problem.
i gave the left column 85% and the Switch button 15% percent you will see in the code where to modify it if you need to do so.
I created for you the simplest example I can make to fit your need that you can modify for you if you are encountering any more problems feel free to comment and ask.
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Close
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.lilmokeq.ui.theme.LilMokeQTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
LilMokeQTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(), color = MaterialTheme.colors.background
) {
NotificationCenter(
)
}
}
}
}
}
#Composable
fun NotificationCenter() {
LazyColumn(
modifier = Modifier
.fillMaxWidth()
.padding(top = 12.dp, start = 16.dp, end = 8.dp, bottom = 12.dp)
.background(color = Color.White)
) {
item {
// create a row with a X icon on the left and a title
// right next to it with a little padding
Row(
horizontalArrangement = Arrangement.Start, // the properties you are looking for in a Row()
verticalAlignment = Alignment.CenterVertically, // // the properties you are looking for in a Row()
modifier = Modifier
.fillMaxWidth()
.background(color = Color.White)
.border(1.dp, Color.Black)
) {
IconButton(
onClick = { /*TODO*/ }, modifier = Modifier.clip(CircleShape)
) {
Icon(
imageVector = Icons.Default.Close,
contentDescription = "Close",
tint = Color.Black
)
}
Text(
text = "Notifications Preferences",
fontSize = 20.sp,
fontWeight = FontWeight.Bold,
// fontFamily = FontFamily(Font(R.font.roboto)), // for some reason i have a problem with this line
modifier = Modifier.padding(start = 8.dp)
)
}
Spacer(modifier = Modifier.height(8.dp))
Card(
elevation = 8.dp,
modifier = Modifier
.fillMaxWidth()
.padding(start = 8.dp, end = 8.dp)
.background(color = Color.White)
) {
// create a row that will contain two columns , the left column will contain the text "Your Account" and under it the text "important notifications about your account" and the right column will contain a switch button.
Row(
horizontalArrangement = Arrangement.SpaceAround,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.border(1.dp, Color.Black)
) {
Column(
// the left column
verticalArrangement = Arrangement.Top,
horizontalAlignment = Alignment.Start, // useful to justify content
modifier = Modifier
.background(color = Color.White)
.border(1.dp, Color.Black)
// set a width to the column
.weight(0.85f)
) {
Text(
text = "Your Account",
fontSize = 20.sp,
fontWeight = FontWeight.Bold,
modifier = Modifier.padding(start = 8.dp)
)
Text(
text = "important notifications about your account" + " and your account settings" + " and more information about" + "interesting stuff" + "lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis ",
fontSize = 14.sp,
fontWeight = FontWeight.Normal,
modifier = Modifier.padding(start = 8.dp, end = 8.dp)
)
}
Column(
// the right column
horizontalAlignment = Alignment.End,
modifier = Modifier
.background(color = Color.White)
.border(1.dp, Color.Black)
// set a width to the column
.weight(0.15f)
) {
Switch(
checked = true,
onCheckedChange = { /*TODO*/ },
modifier = Modifier
.padding(end = 8.dp)
.border(1.dp, Color.Black)
)
}
} // end of row
} // end of card
Spacer(modifier = Modifier.height(8.dp))
Card(
elevation = 8.dp,
modifier = Modifier
.fillMaxWidth()
.padding(start = 8.dp, end = 8.dp)
.background(color = Color.White)
) {
// create a row that will contain two columns , the left column will contain the text "Your Account" and under it the text "important notifications about your account" and the right column will contain a switch button.
Row(
horizontalArrangement = Arrangement.SpaceAround,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.border(1.dp, Color.Black)
) {
Column(
// the left column
verticalArrangement = Arrangement.Top,
horizontalAlignment = Alignment.Start, // useful to justify content
modifier = Modifier
.background(color = Color.White)
.border(1.dp, Color.Black)
// set a width to the column
.weight(0.85f)
) {
Text(
text = "Second notification",
fontSize = 20.sp,
fontWeight = FontWeight.Bold,
modifier = Modifier.padding(start = 8.dp)
)
Text(
text = "important notifications about your account" + " and your account settings" + " and more information about" + "interesting stuff" + "lorem ipsum dolor sit amet, consectetur adipiscing elit,",
fontSize = 14.sp,
fontWeight = FontWeight.Normal,
modifier = Modifier.padding(start = 8.dp, end = 8.dp)
)
}
Column(
// the right column
horizontalAlignment = Alignment.End,
modifier = Modifier
.background(color = Color.White)
.border(1.dp, Color.Black)
// set a width to the column
.weight(0.15f)
) {
Switch(
checked = true,
onCheckedChange = { /*TODO*/ },
modifier = Modifier
.padding(end = 8.dp)
.border(1.dp, Color.Black)
)
}
} // end of row
} // end of card
}
}
}
the final result ^
this playlist covers a lot of information. watch it to get more ideas about your design and how to implement them.
https://www.youtube.com/watch?v=cDabx3SjuOY&list=PLQkwcJG4YTCSpJ2NLhDTHhi6XBNfk9WiC&ab_channel=PhilippLackner
I think below changes in your code will solve your problem, I am not use font style and colour same as yours.
To left/start alignment you have to remove .wrapContentSize() method.
Box(modifier = Modifier
.height(66.dp)
.fillMaxWidth()
.padding(top = 12.dp, start = 16.dp, end = 8.dp, bottom = 12.dp)
) {
ConstraintLayout(
modifier = Modifier
.fillMaxWidth()
) {
val (titleText, descriptionText, switch) = createRefs()
Text(
text = title,
modifier = Modifier
.constrainAs(titleText) {
start.linkTo(parent.start)
top.linkTo(switch.top)
},
color = Color.Black,
fontSize = 18.sp,
textAlign = TextAlign.Start
)
Text(
text = description,
modifier = Modifier
.constrainAs(descriptionText) {
start.linkTo(parent.start)
end.linkTo(switch.start)
top.linkTo(titleText.bottom)
width = Dimension.fillToConstraints
},
color = Color.Red,
fontSize = 14.sp,
textAlign = TextAlign.Start
)
val checkedState = remember { mutableStateOf(true) }
Switch(modifier = Modifier
.background(color = Color.Gray)
.constrainAs(switch) {
top.linkTo(parent.top)
end.linkTo(parent.end)
bottom.linkTo(parent.bottom)
},
enabled = enabled,
checked = checkedState.value,
onCheckedChange = { checkedState.value = it },
colors = SwitchDefaults.colors(
checkedThumbColor = Color.Green,
checkedTrackColor = Color.Gray,
uncheckedThumbColor = Color.Cyan,
uncheckedTrackColor = Color.DarkGray,
),
)
}
}
But I think you have to change you function constraint structure as below code
#Composable
fun SwitchRow(title: String, description: String, enabled: Boolean) {
Box(
contentAlignment = Alignment.TopStart,
modifier = Modifier
.height(85.dp)
.padding(8.dp)
) {
ConstraintLayout(
modifier = Modifier
.fillMaxWidth()
) {
val (descriptionText, switch) = createRefs()
Column(
horizontalAlignment = Alignment.Start,
modifier = Modifier
.fillMaxWidth()
.padding(start = 25.dp)
.constrainAs(descriptionText) {
top.linkTo(parent.top)
start.linkTo(parent.start)
end.linkTo(switch.start)
},
) {
Text(
text = title,
color = Color.Black,
fontSize = 18.sp,
textAlign = TextAlign.Start
)
Spacer(modifier = Modifier.height(2.dp))
Text(
text = description,
color = Color.Red,
fontSize = 14.sp,
textAlign = TextAlign.Start,
maxLines = 2
)
}
val checkedState = remember { mutableStateOf(true) }
Switch(
modifier = Modifier
.background(color = Color.Gray)
.constrainAs(switch) {
top.linkTo(parent.top)
end.linkTo(parent.end)
},
enabled = enabled,
checked = checkedState.value,
onCheckedChange = { checkedState.value = it },
colors = SwitchDefaults.colors(
checkedThumbColor = Color.Green,
checkedTrackColor = Color.Gray,
uncheckedThumbColor = Color.Cyan,
uncheckedTrackColor = Color.DarkGray,
),
)
}
}
}
In both code you have to change padding and spacing as per your requirement.
I've been trying to build a list with a Card in it formatted like this:
The difficulty here is that the title e.g. "Bread" and ingredient name e.g. "Flour" can be very long and thus I want to have an ellipsis to keep things manageable i.e. "My Long Flour name" will be displayed as "My Long Flou..." or as much space as is allowed.
The picture size and the gram and percent widths are constant .dp values.
Ellipsis worked fine when it was in a Column but with ConstraintLayout it doesn't seem to work and I get this:
here's my code
#Composable
fun BakeItem(
modifier: Modifier = Modifier,
bake: Bake,
cardClicked: () -> Unit,
ingredeints: List<Ingredient>
) {
Card(
modifier = modifier
.padding(start = 16.dp, end = 16.dp, top = 8.dp, bottom = 8.dp)
.clickable { cardClicked() }
.fillMaxSize()
.wrapContentHeight(),
border = BorderStroke(4.dp, MaterialTheme.colorScheme.secondary),
shape = RoundedCornerShape(14.0.dp),
colors = cardColors(
containerColor = MaterialTheme.colorScheme.background
)
) {
val context = LocalContext.current
val uri = remember(bake.imagePath) { Uri.parse(bake.imagePath) }
// Card Content
ConstraintLayout(
modifier = modifier
.fillMaxSize()
.padding(start = 16.dp, top = 8.dp, end = 8.dp, bottom = 8.dp)
) {
val (titleRef, gramColRef, ingrColRef, percentColRef,
imageRef, dateRef, starsRef) = createRefs()
Text(
modifier = modifier
.padding(4.dp)
.constrainAs(titleRef) {
top.linkTo(parent.top, margin = 8.dp)
// end.linkTo(imageRef.start, margin = 8.dp)
start.linkTo(parent.start, margin = 8.dp)
}
.background(Color(0xffeeeeee)),
// textAlign = TextAlign.Left,
text = if (bake.recipeName.isEmpty()) "<Unnamed>" else bake.recipeName,
style = MaterialTheme.typography.titleLarge,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.primary,
softWrap = false,
overflow = TextOverflow.Ellipsis,
)
Column(
horizontalAlignment = Alignment.End,
modifier = modifier
.width(50.dp)
.constrainAs(gramColRef) {
top.linkTo(titleRef.bottom, margin = 8.dp)
end.linkTo(ingrColRef.start, margin = 8.dp)
start.linkTo(parent.start, margin = 8.dp)
}
) {
ingredeints.forEachIndexed { _, it ->
Text(
text = it.weightGram,
style = MaterialTheme.typography.titleSmall,
softWrap = false,
overflow = TextOverflow.Ellipsis
)
}
}
Column(
modifier = modifier
.constrainAs(ingrColRef) {
top.linkTo(titleRef.bottom, margin = 8.dp)
start.linkTo(gramColRef.end, margin = 8.dp)
end.linkTo(ingrColRef.start, margin = 8.dp)
},
) {
ingredeints.forEachIndexed { _, it ->
Text(
text = it.name,
style = MaterialTheme.typography.titleSmall,
softWrap = false,
overflow = TextOverflow.Ellipsis
)
}
}
Column(
modifier = modifier
.width(50.dp)
.constrainAs(percentColRef) {
top.linkTo(titleRef.bottom, margin = 8.dp)
end.linkTo(imageRef.start, margin = 8.dp)
start.linkTo(ingrColRef.end, margin = 8.dp)
},
horizontalAlignment = Alignment.End
) {
ingredeints.forEachIndexed { i, it ->
Text(
text = if (i == 0) "" else it.bakingPercent,
style = MaterialTheme.typography.titleSmall,
softWrap = false,
overflow = TextOverflow.Ellipsis
)
}
}
Text(
modifier = modifier.padding(
top = 8.dp,
start = 4.dp,
end = 4.dp,
bottom = 4.dp
),
text = bake.notes,
style = MaterialTheme.typography.titleSmall,
color = MaterialTheme.colorScheme.onSurface,
maxLines = 3
)
if (bake.imagePath.isNotEmpty()) {
Image(
modifier = modifier
.constrainAs(imageRef) {
top.linkTo(parent.top, margin = 8.dp)
end.linkTo(parent.end, margin = 8.dp)
}
.padding(4.dp)
.requiredSize(150.dp)
.clip(RoundedCornerShape(14.dp))
.border(
4.dp,
MaterialTheme.colorScheme.primaryContainer,
RoundedCornerShape(14.dp)
),
painter = rememberAsyncImagePainter(
remember(uri) {
ImageRequest.Builder(context)
.data(uri)
// TODO, think of caching improvements
// .diskCacheKey(uri.toString() + key.value)
// .memoryCacheKey(uri.toString() + key.value)
.diskCachePolicy(CachePolicy.DISABLED)
.memoryCachePolicy(CachePolicy.DISABLED)
.build()
}
),
contentScale = ContentScale.Crop,
contentDescription = "Image of your bake"
)
} else {
Spacer(modifier = modifier
.background(Color.Blue)
.width(150.dp)
.height(10.dp)
.constrainAs(imageRef) {
top.linkTo(parent.top, margin = 8.dp)
end.linkTo(parent.end, margin = 8.dp)
})
}
Text(
modifier = modifier
.padding(4.dp)
.constrainAs(dateRef) {
bottom.linkTo(parent.bottom, margin = 8.dp)
top.linkTo(imageRef.bottom, margin = 8.dp)
end.linkTo(parent.end, margin = 8.dp)
},
text = bake.startTime.toString(),
style = MaterialTheme.typography.titleMedium,
color = MaterialTheme.colorScheme.onSurface,
// textAlign = Layout.Alignment.ALIGN_CENTER
)
createHorizontalChain(
gramColRef, ingrColRef, percentColRef, imageRef,
chainStyle = ChainStyle.SpreadInside
)
createVerticalChain(
imageRef, dateRef,
chainStyle = ChainStyle.SpreadInside
)
createHorizontalChain(
titleRef, imageRef,
chainStyle = ChainStyle.SpreadInside
)
}
}
}
In the title you have to add as constrain width = Dimension.fillToConstraints, end.linkTo(imageRef.start, margin = 8.dp) and maxLines = 1:
Text(
modifier = Modifier
.padding(4.dp)
.constrainAs(titleRef) {
top.linkTo(parent.top, margin = 8.dp)
end.linkTo(imageRef.start, margin = 8.dp)
start.linkTo(parent.start, margin = 8.dp)
width = Dimension.fillToConstraints
}
.background(Color(0xffeeeeee)),
text = "Recipe Name",
//...
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
For the list instead of using 3 Columns you do can something different.
For the ingredient name add the weight(1f) modifier and the maxLines = 1
Column(
modifier = Modifier
.constrainAs(listRef) {
top.linkTo(titleRef.bottom, margin = 8.dp)
end.linkTo(imageRef.start, margin = 8.dp)
start.linkTo(parent.start, margin = 8.dp)
width = Dimension.fillToConstraints
}
) {
//forEach Row...
Row(Modifier.fillMaxWidth()) {
Text(text = "50g", Modifier.width(xx.dp), textAlign = TextAlign.End)
Text(
text = "Very long text long text",
Modifier
.padding(start=4.dp)
.weight(1f),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
Text(text = "75%", Modifier.width(xx.dp), textAlign = TextAlign.End)
}
}
I have some Column with Icon and Text inside. Column is wrap the size of Text but i want to Column wrap Icon and long text move to another line
How It's looks now
How I want it to look like
#Composable
fun ServiceItem(
service: Service,
onItemClick: (service: Service) -> Unit
) {
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Card(
modifier = Modifier.padding(horizontal = 16.dp),
shape = RoundedCornerShape(itemBackgroundCornerSize),
backgroundColor = colorResource(id = R.color.gray)
) {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.size(itemSize)
.clickable(onClick = {
onItemClick(service)
})
) {
Image(
painter = rememberAsyncImagePainter(service.imageUrl),
contentDescription = null,
modifier = Modifier.padding(iconPadding)
)
}
}
Text(
text = service.title,
textAlign = TextAlign.Center,
style = itemTitleTextDefaultStyle(),
modifier = Modifier.padding(top = textTopPadding)
)
}
}
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)
#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.
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:
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:
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
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.")
}
}
}
}
How can I align an Image with a Text's baseline in a Row. Modifier.alignByBaseline() works for the Texts but the Image doesn't participate in the alignment.
#Composable
fun Sample() {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.Center,
) {
Text(
text = "One",
modifier = Modifier.alignByBaseline(),
fontSize = 40.sp
)
Image(
modifier = Modifier
.padding(horizontal = 8.dp)
.size(24.dp)
.alignBy(FirstBaseline),
painter = painterResource(id = R.drawable.ic_launcher_background),
contentDescription = "",
)
Text(
text = "two",
modifier = Modifier.alignByBaseline(),
fontSize = 40.sp
)
}
}
alignByBaseline aligns item by it's own baseline, not neighbours ones.
You can use paddingFromBaseline for Text and same value for image padding with verticalAlignment = Alignment.Bottom.
And to get actual value of baseline offset to pass it to the padding modifier, you need to wait for TextLayoutResult: it gets called when text layout is calculated depending on text size, font, etc.
Row(
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.Bottom,
modifier = Modifier.fillMaxWidth()
) {
var maxBaseline by remember { mutableStateOf(0f) }
fun updateMaxBaseline(textLayoutResult: TextLayoutResult) {
maxBaseline = max(maxBaseline, textLayoutResult.size.height - textLayoutResult.lastBaseline)
}
val topBaselinePadding = with(LocalDensity.current) { maxBaseline.toDp() }
Text(
text = "One",
modifier = Modifier.paddingFromBaseline(bottom = topBaselinePadding),
fontSize = 20.sp,
onTextLayout = ::updateMaxBaseline
)
Image(
painter = painterResource(id = R.drawable.ic_launcher_background),
contentDescription = "",
modifier = Modifier
.padding(bottom = topBaselinePadding)
.size(24.dp)
)
Text(
text = "two",
modifier = Modifier.paddingFromBaseline(bottom = topBaselinePadding),
fontSize = 40.sp,
onTextLayout = ::updateMaxBaseline
)
}