Currently I get this:
But I want something like this:
But also the text from "50" and "min" should be aligned to the top.
My code:
Row(verticalAlignment = Alignment.Bottom) {
Text(
text = "18",
color = MaterialTheme.colors.primaryVariant,
fontSize = 60.sp,
modifier = Modifier
.weight(1f).height(62.dp),
textAlign = TextAlign.End,
)
Text(
text = "hrs",
modifier = Modifier.weight(1f).height(16.dp),
)
}
Row(verticalAlignment = Alignment.Top) {
Text(
text = "50",
color = MaterialTheme.colors.primaryVariant,
fontSize = 28.sp,
modifier = Modifier.weight(1f).height(30.dp).align(Alignment.Top),
textAlign = TextAlign.End,
)
Text("min", modifier = Modifier.weight(1f))
}
As you see in my code I solved that by using the height property currently. But it doesn't work for the align top and it feels a bit wrong. is there a better way to make it work?
You can use the AnnotatedString to display the text with multiple styles.
Something like:
Row() {
Text(
text = buildAnnotatedString {
withStyle(style = SpanStyle(
color = MaterialTheme.colors.primaryVariant,
fontSize = 60.sp)) {
append("18")
}
append(" hrs ")
})
}
For the second case you can apply a BaselineShift to the min text:
Row() {
Text(
text = buildAnnotatedString {
withStyle(style = SpanStyle(
color = MaterialTheme.colors.primaryVariant,
fontSize = 28.sp)) {
append("50")
}
withStyle(style = SpanStyle(
baselineShift = BaselineShift(+0.65f))) {
append(" min ")
}
})
}
You can use AlignmentLine.LastBaseLine to properly size and position you're Text. You can do something like this:
Modifier.layout { measurable, constraints ->
// Measure the composable
val placeable = measurable.measure(constraints)
// Check the composable has a LastBaseline
check(placeable[LastBaseline] != AlignmentLine.Unspecified)
val lastBaseline = placeable[LastBaseline]
val placeableY = placeable.height - lastBaseline
val height = placeable.height - placeableY
layout(placeable.width, height) {
placeable.placeRelative(0, -placeableY)
}
}
If you want to completely remove even the bottom FirstBaseLine just subtract it to the height and that should do it.
From version 1.2.0-alpha05, includeFontPadding is turned off as they announced: (https://developer.android.com/jetpack/androidx/releases/compose-ui#1.2.0-alpha05)
Text: includeFontPadding is now turned off by default. The clipping issues as a result of includeFontPadding=false is handled and no clipping should occur for tall scripts.
In case you haven't found the right solution. You can try this:
Text(text = buildAnnotatedString{
withStyle(style =
ParagraphStyle(
platformStyle = PlatformParagraphStyle(includeFontPadding = false),
lineHeightStyle = LineHeightStyle(
LineHeightStyle.Alignment.Bottom,
LineHeightStyle.Trim.Both
)
)
){
append("18")
}
})
Related
I am trying to create multiple items to encapsulate the specific behavior of every component but I cannot specify the dimensions for every view.
I want a Textfield with an X icon on its right
setContent {
Surface(
modifier = Modifier
.fillMaxSize()
.background(color = white)
.padding(horizontal = 15.dp)
) {
Row(horizontalArrangement = Arrangement.spacedBy(15.dp)) {
Searcher(
modifier = Modifier.weight(1f),
onTextChanged = { },
onSearchAction = { }
)
Image(
painter = painterResource(id = R.drawable.ic_close),
contentDescription = null,
colorFilter = ColorFilter.tint(blue)
)
}
}
}
The component is the following
#Composable
fun Searcher(
modifier: Modifier = Modifier,
onTextChanged: (String) -> Unit,
onSearchAction: () -> Unit
) {
Row {
SearcherField(
onTextChanged = onTextChanged,
onSearchAction = onSearchAction,
modifier = Modifier.weight(1f)
)
CircularSearch(
modifier = Modifier
.padding(horizontal = 10.dp)
.align(CenterVertically)
)
}
}
and the SearcherField:
#Composable
fun SearcherField(
modifier: Modifier = Modifier,
onTextChanged: (String) -> Unit,
onSearchAction: () -> Unit
) {
var fieldText by remember { mutableStateOf(emptyText) }
TextField(
value = fieldText,
onValueChange = { value ->
fieldText = value
if (value.length > 2)
onTextChanged(value)
},
singleLine = true,
textStyle = Typography.h5.copy(color = White),
colors = TextFieldDefaults.textFieldColors(
cursorColor = White,
focusedIndicatorColor = Transparent,
unfocusedIndicatorColor = Transparent,
backgroundColor = Transparent
),
trailingIcon = {
if (fieldText.isNotEmpty()) {
IconButton(onClick = {
fieldText = emptyText
}) {
Icon(
imageVector = Icons.Default.Close,
contentDescription = emptyText
)
}
}
},
placeholder = {
Text(
text = stringResource(id = R.string.dondebuscas),
style = Typography.h5.copy(color = White)
)
},
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search),
keyboardActions = KeyboardActions(
onSearch = {
onSearchAction()
}
),
modifier = modifier.fillMaxWidth()
)
}
But I don´t know why, but the component Searcher with the placeholder is rendered in two lines.
It´s all about the placeholder that seems to be resized for not having enough space because if I remove the placeholder, the component looks perfect.
Everything is in one line, not having a placeholder of two lines. I m trying to modify the size of every item but I am not able to get the expected result and I don´t know if the problem is just about the placeholder.
How can I solve it? UPDATE -> I found the error
Thanks in advance!
Add maxlines = 1 to the placeholder Text's parameters.
Your field is single -lune but your text is multi-line. I think it creates conflict in implementation.
Okay... I find that the problem is about the trailing icon. Is not visible when there is no text in the TextField but is still occupying some space in the view, that´s why the placeholder cannot occupy the entire space. The solution is the following.
val trailingIconView = #Composable {
IconButton(onClick = {
fieldText = emptyText
}) {
Icon(
imageVector = Icons.Default.Close,
contentDescription = emptyText
)
}
}
Create a variable with the icon and set it to the TextField only when is required
trailingIcon = if (fieldText.isNotEmpty()) trailingIconView else null,
With that, the trailing icon will be "gone" instead of "invisible" (the old way).
Still have a lot to learn.
I'm trying to make a textfield thats quite small (32.dp). When doing this, the label gets pushed to the bottom and I can't find a way to keep it centered? I've tried using TextAlign.center but no luck
TextField(
modifier = Modifier
.height(32.dp)
.padding(horizontal = 8.dp)
.border(1.dp, colorResource(id = R.color.mono4), RoundedCornerShape(32.dp))
.constrainAs(commentBox) {
top.linkTo(parent.top)
bottom.linkTo(parent.bottom)
start.linkTo(profilePhoto.end)
end.linkTo(postText.start)
width = Dimension.fillToConstraints
height = Dimension.fillToConstraints
},
label = {
Text(
textAlign = TextAlign.Center,
text = "Comment on workout...",
style = TextStyle(
fontSize = 14.sp,
color = colorResource(id = R.color.mono4),
fontFamily = FontFamily(Font(R.font.josefin_san_regular))
)
)
},
colors = TextFieldDefaults.textFieldColors(
backgroundColor = Color.Transparent,
focusedIndicatorColor = Color.Transparent,
unfocusedIndicatorColor = Color.Transparent
),
value = commentText,
onValueChange = { commentText = it }
)
This is internal behaviour, you will need to alter the source coe to fix it, or implement your own composable, something like:-
#OptIn(ExperimentalAnimationApi::class)
#Composable
fun TinyTextCapture(){
var value by remember { mutableStateOf("") }
Box(contentAlignment = Alignment.Center){
TextField(
modifier = Modifier.height(32.dp),
value = value, onValueChange = { value = it }
)
AnimatedVisibility(visible = value.isEmpty()) {
Text("Label")
}
}
}
The default label parameter has a default padding from top start, that is why it was seen as being pushed downwards. If I were you, I would not go about editing the source of something as basic as a TextField since it has so many internal dependencies that it is all a mess.
You could also do without the AnimatedVisibility, and also apply a ConstraintLayout to get it at the start of the TextField in contrast to its absolute centered position right now.
I'm trying to align the text of my button to the center vertically and horizontally, which isn't there by default.
I have tried to use 'offset' to position text in my button but the positioning isn't consistent across various device sizes.
The code for my button is:
Button(
onClick = {
navController.navigate("fourth_screen")
},
modifier = Modifier.constrainAs(buttonSave) {
top.linkTo(glButtonSaveTop)
bottom.linkTo(glButtonSaveBottom)
start.linkTo(glLeft)
end.linkTo(glRight)
width = Dimension.fillToConstraints
height = Dimension.fillToConstraints
},
enabled = !errorMsg.value,
colors = if (query.value.text != ""){
ButtonDefaults.
buttonColors(backgroundColor = colorResource(id = R.color.voodlee_red))}
else {
ButtonDefaults.
buttonColors(backgroundColor = colorResource(id = R.color.gray))}
) {
Text(
"Save", color = colorResource(id = R.color.dark_blue),
fontSize = with(LocalDensity.current) {
dimensionResource(id = R.dimen._16ssp).toSp()
},
fontFamily = FontFamily(Font(R.font.poppins_medium)),
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxSize().offset(y= (0.15).dp)) //offset for positioning
}
How do I center the text in my button vertically and horizontally that works on all device sizes.
EDIT : Any solution for this in stable Jetpack Compose?
You can just use the modifier align to center a Composable:
Button(
onClick = {
navController.navigate("fourth_screen")
},
modifier = Modifier.constrainAs(buttonSave) {
top.linkTo(glButtonSaveTop)
bottom.linkTo(glButtonSaveBottom)
start.linkTo(glLeft)
end.linkTo(glRight)
width = Dimension.fillToConstraints
height = Dimension.fillToConstraints
},
enabled = !errorMsg.value,
colors = if (query.value.text != ""){
ButtonDefaults.
buttonColors(backgroundColor = colorResource(id = R.color.voodlee_red))}
else {
ButtonDefaults.
buttonColors(backgroundColor = colorResource(id = R.color.gray))}
) {
Text(
"Save", color = colorResource(id = R.color.dark_blue),
fontSize = with(LocalDensity.current) {
dimensionResource(id = R.dimen._16ssp).toSp()
},
fontFamily = FontFamily(Font(R.font.poppins_medium)),
textAlign = TextAlign.Center, // horizontal center of the text
modifier = Modifier.align(Alignment.CenterVertically) //vertical center of the Text composable
}
When using ConstraintLayout in a LazyColumn a simple Text does not appear when we simply scroll up and down.
Changing the Item from a ConstraintLayout to a Row fixes the issue thus I conclude either my code is bugged or ConstraintLayout alpha has a bug.
You can see in the Layout inspector picture the Text is supposed to display a -6.40 euro
edit: I also posted it on the android bug tracker as I wasn't sure if it was my problem or a bug https://issuetracker.google.com/issues/188855913 - Will close this soon most probably
LazyColumn(
modifier = modifier,
verticalArrangement = Arrangement.spacedBy(smallMargin),
) {
item {
header()
}
transactionsGroupedPer.forEach { (section, transactions) ->
item {
Text(
modifier = modifier.padding(start = largeMargin, end = largeMargin, top = normalMargin),
text = section.uppercase(),
style = MyTheme.typography.label
)
}
items(items) {
MyItem(
item = it,
modifier = Modifier.fillParentMaxWidth(),
)
}
}
}
#Composable
fun MyItem(
name: String,
amount: Money,
modifier: Modifier = Modifier,
textColor: Color = Color.Unspecified,
) {
val constraints = ConstraintSet {
val titleSubTitle = createRefFor(ModifierId.TITLE_SUBTITLE)
val logo = createRefFor(ModifierId.LOGO)
val amount = createRefFor(ModifierId.AMOUNT)
constrain(amount) {
top.linkTo(parent.top, margin = xsmallMargin)
bottom.linkTo(parent.bottom, margin = xsmallMargin)
end.linkTo(parent.end, margin = smallMargin)
}
constrain(logo) {
start.linkTo(parent.start, margin = smallMargin)
bottom.linkTo(parent.bottom)
}
constrain(titleSubTitle) {
top.linkTo(logo.top)
bottom.linkTo(logo.bottom)
start.linkTo(logo.end, margin = smallMargin)
end.linkTo(amount.start, margin = smallMargin)
width = Dimension.fillToConstraints
}
}
ConstraintLayout(constraints,
modifier = modifier.fillMaxWidth().wrapContentHeight()
) {
Text(
color = textColor,
modifier = Modifier.layoutId(ModifierId.AMOUNT),
text = amount.toMoneyAnnotatedString(),
style = amountStyle,
)
ImageContainer(Modifier.layoutId(ModifierId.LOGO), image = image, size = logoSize)
Column(modifier = Modifier.layoutId(ModifierId.TITLE_SUBTITLE), verticalArrangement = Arrangement.Center) {
Text(
color = textColor,
maxLines = maxLines,
text = name,
style = MyTheme.typography.bodyBold
)
if (showSubTitle) {
Text(
color = textColor,
text = date.formatDateTime(DateFormat.LONG),
style = MyTheme.typography.meta
)
}
}
}
}
Bug fixed in beta08
References
https://issuetracker.google.com/issues/188855913
https://issuetracker.google.com/issues/188566058
In general, most components in Jetpack Compose seem to be very easy to customize.
However, the same cannot be said for the TextField. For example, say that I wanted to make something like this:
One would think that simply wrapping the BaseTextField would work. However, it appears that there has been a bug in the BaseTextField component, and I have opened an issue. This bug will not permit the user to focus the text field after focusing-away from it once already, until the component is re-rendered.
Citing this, I attempted to customize the OutlinedTextField and TextField components, but am not able to customize them to look like the image above. Were it not for the fact that the cursor color uses the activeColor property, I could make it work.
What would be a proper work-around to create a usable text field that looks like the above?
You can use the TextField:
removing the label with label = null
applying custom color with the TextFieldDefaults.textFieldColors parameter to hide the indicator.
adding in the onValueChange a function to fix the max number of characters as described here
Finally use a Column to add 2 Text composables to complete the external label and counter text.
Something like:
var text by remember { mutableStateOf("") }
val maxChar = 5
Column(){
//External label
Text(
text = "Label",
modifier = Modifier.fillMaxWidth(),
textAlign = TextAlign.Start,
color = Blue
)
TextField(
value = text,
onValueChange = {
if (it.length <= maxChar) text = it
},
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 4.dp),
shape = RoundedCornerShape(8.dp),
trailingIcon = {
Icon(Icons.Filled.Add, "", tint = Blue)
},
colors = TextFieldDefaults.textFieldColors(
backgroundColor = ....,
focusedIndicatorColor = Color.Transparent, //hide the indicator
unfocusedIndicatorColor = .....)
)
//counter message
Text(
text = "${text.length} / $maxChar",
textAlign = TextAlign.End,
color = Blue,
style = MaterialTheme.typography.caption, //use the caption text style
modifier = Modifier.fillMaxWidth()
)
By this exemple you can learn a lot. With 1.0.0 you can do like this:
Column {
var textState by remember { mutableStateOf("") }
val maxLength = 110
val lightBlue = Color(0xffd8e6ff)
val blue = Color(0xff76a9ff)
Text(
text = "Caption",
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 4.dp),
textAlign = TextAlign.Start,
color = blue
)
TextField(
modifier = Modifier.fillMaxWidth(),
value = textState,
colors = TextFieldDefaults.textFieldColors(
backgroundColor = lightBlue,
cursorColor = Color.Black,
disabledLabelColor = lightBlue,
focusedIndicatorColor = Color.Transparent,
unfocusedIndicatorColor = Color.Transparent
),
onValueChange = {
if (it.length <= maxLength) textState = it
},
shape = RoundedCornerShape(8.dp),
singleLine = true,
trailingIcon = {
if (textState.isNotEmpty()) {
IconButton(onClick = { textState = "" }) {
Icon(
imageVector = Icons.Outlined.Close,
contentDescription = null
)
}
}
}
)
Text(
text = "${textState.length} / $maxLength",
modifier = Modifier
.fillMaxWidth()
.padding(top = 4.dp),
textAlign = TextAlign.End,
color = blue
)
}
Well, until the issue I mentioned is resolved, the choices are:
Roll back to Compose version 1.0.0-alpha04 (issue was introduced in alpha05)
Add a border to a TextField or OutlinedTextField as so:
TextField(
value = myValue,
onValueChange = myOnChange,
modifier = Modifier.clip(myShape).border(5.dp, myColor)
)
In addition to Gabriele Mariotti's Answer
If you like to have custom color for your cursor, you can achieve it using:
Column(){
//External label
Text(
...
...
)
TextField(
...
...
colors = TextFieldDefaults.textFieldColors(
backgroundColor = ...,
focusedIndicatorColor = ...,
unfocusedIndicatorColor = ...,
cursorColor = Color.Black)
)
//counter message
Text(
...
...
)