I have a problem with letterSpacing in Compose.
When I set TextAlign.End, TextOverflow.Ellipsis and style with letterSpacing text gets cut off.
How can I fix this?
code:
val textStyleWithoutLetterSpacing = TextStyle()
val textStyleWithLetterSpacing = TextStyle(letterSpacing = 1.sp)
#Composable
fun Sample() {
Column {
Text(
text = "1234567890 1234567890 1234567890",
textAlign = TextAlign.End,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
style = textStyleWithoutLetterSpacing
)
Text(
text = "1234567890 1234567890 1234567890",
textAlign = TextAlign.End,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
style = textStyleWithLetterSpacing
)
}
}
result:
I faced a similar issue which I believe has to do with combining textAlign = TextAlign.End with overflow = TextOverflow.Ellipsis. I have just had a play with you sample and came up with this (which is similar to how I solved my problem):
val textStyleWithoutLetterSpacing = TextStyle()
val textStyleWithLetterSpacing = TextStyle(letterSpacing = 1.sp)
#Composable
fun Sample() {
Column {
Text(
text = "1234567890 1234567890 1234567890",
textAlign = TextAlign.End,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
style = textStyleWithoutLetterSpacing
)
Box(contentAlignment = Alignment.CenterEnd) {
Text(
text = "1234567890 1234567890 1234567890",
overflow = TextOverflow.Ellipsis,
maxLines = 1,
style = textStyleWithLetterSpacing
)
}
}
}
In essence, I delegated the alignment of the text to the Box component and that seems to bypass the bug in the compose library (v1.2.1 at the time of writing this answer).
Related
I am working with MediumTopAppBar, and i am trying to change the textStyle of the big Title without ruining the small title.
So what i am doing is:
MediumTopAppBar(
title = { HeadlineLargeBlackText(text = "pageTitle") },
navigationIcon = {
IconButton(onClick = { onBackPressed.invoke() }) {
Icon(
imageVector = Icons.Filled.ArrowBack,
contentDescription = stringResource(id = R.string.action_back)
)
}
},
scrollBehavior = scrollBehavior
)
My problem is that when i set it to "HeadlineLargeBlackText" it automatically change the textstyle of both the big and the small text so it looks like this (image taken with both big and small text visible):
What i want is for the bottom to be big, and the top to be small.
When i go into the MediumTopAppBar i do see that the textStyle of the big and the small is set in the TwoRowsTopAppBar as:
titleTextStyle = MaterialTheme.typography.fromToken(TopAppBarMediumTokens.HeadlineFont),
smallTitleTextStyle = MaterialTheme.typography.fromToken(TopAppBarSmallTokens.HeadlineFont),
But ofc that method is private :// So i wonder, is it possible to get access to these big/small texts separately somehow?
Currently the MediumTopAppBar uses the titleLarge and headlineSmall textstyles defined with the typography attribute in your theme.
You can define them in the theme with:
val customTypography = androidx.compose.material3.Typography(
titleLarge = TextStyle(
fontWeight = FontWeight.SemiBold,
fontSize = 22. sp,
lineHeight = 28. sp,
letterSpacing = 0. sp
),
headlineSmall = TextStyle(
fontWeight = FontWeight.SemiBold,
fontSize = 16. sp,
lineHeight = 24. sp,
letterSpacing = 0.15.sp
),
)
If you don't wont to apply them to the whole app, you can apply a custom theme only to the MediumTopAppBar with something like:
Scaffold(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = {
AppTheme {
MediumTopAppBar(
title = {
androidx.compose.material3.Text(
"Medium TopAppBar",
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
},
//....
)
},
//....
}
where
fun AppTheme(
useDarkTheme: Boolean = isSystemInDarkTheme(),
content: #Composable() () -> Unit
) {
//...
aMaterialTheme(
colorScheme = colors,
typography = customTypography,
content = content
)
}
Lest simplify my situation to the current:
Row(
modifier = Modifier
.background(Color.Red),
) {
Text(
text = ELLIPSIZE_THIS_TEXT,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
Text(
text = LEAVE_THIS_TEXT_NOT_ELLIPSIZED,
maxLines = 1,
overflow = TextOverflow.Visible,
)
}
If I add weight(1f) to the first text modifier, it works as expected, but the Row took all the line. But my goal is to take only required place and if I have not enough place - ellipsize the first text.
And I need behavior from images 2 and 3.
You can use fill property for this . Setting it to false will result in wrapping the text if small and ellipsis text in case its bigger. Try this out .
Row(
modifier = modifier
.background(Color.Red).padding(10.dp),
) {
Text(
text = "Jetpack Compose Ui Tool-kit",
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier=Modifier.weight(1f, fill = false)
)
Spacer(modifier = Modifier.width(12.dp))
Text(
text = "LEAVE_THIS_TEXT",
maxLines = 1,
overflow = TextOverflow.Visible,
)
}
Previously you could define all of your style changes as a single style in XML which was really convenient especially if you had a lot of different styles. The project I'm currently working on has 50+ of these styles defined.
<!-- styles.xml -->
<style name="title_1">
<item name="android:textColor">#color/purple_500</item>
<item name="android:fontFamily">#font</item>
<item name="android:textSize">18sp</item>
<item name="android:maxLines">1</item>
<item name="android:firstBaselineToTopHeight">12sp</item>
<item name="android:lastBaselineToBottomHeight">9sp</item>
</style>
<!-- hello_world.xml -->
<TextView
android:id="#+id/hello_world"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello, World!"
style="#style/title_1" />
While looking to transition to Compose UI, I noticed that certain attributes are separated out. For example baseline can be changed as a modifier while other values can be changed as a text style or a separate parameter in the case of max lines.
// Styles.kt
#Composable
fun Title1(text: String) {
Text(
text = text,
modifier = Modifier.paddingFromBaseline(top = 12.sp, bottom = 9.sp),
maxLines = 1,
style = TextStyle(
color = colorResource(id = R.color.purple_500),
fontFamily = FontFamily(Font(R.font.podkova_semibold)),
fontSize = 18.sp
)
)
}
// MainActivity.kt
setContent {
Title1("Hello, World")
}
This makes it more error prone since they're separated into different parameters and it's easy to forget to apply one of them. I know you could lump all the text styles together, which helps, but was wondering if there was a way to replicate what we had in XML and specify all the modifications in one place.
In my initial approach above I tried creating wrappers around the Text composable with all the predefined changes, but wanted a flexible solution that can take all the Text parameters without having 50+ composables with all the same parameters as the Text composable. Wondering if there's something in the Kotlin language that can help simplify this or if this is just the wrong approach to the problem.
// Would like to avoid having to do this 50+ times
#Composable
fun Title1(
text: String,
modifier: Modifier = Modifier,
color: Color = Color.Unspecified,
fontSize: TextUnit = TextUnit.Unspecified,
fontStyle: FontStyle? = null,
fontWeight: FontWeight? = null,
fontFamily: FontFamily? = null,
letterSpacing: TextUnit = TextUnit.Unspecified,
textDecoration: TextDecoration? = null,
textAlign: TextAlign? = null,
lineHeight: TextUnit = TextUnit.Unspecified,
overflow: TextOverflow = TextOverflow.Clip,
softWrap: Boolean = true,
maxLines: Int = 1,
onTextLayout: (TextLayoutResult) -> Unit = {},
style: TextStyle = LocalTextStyle.current
) {
Text(
text = text,
modifier = modifier
.paddingFromBaseline(top = 12.sp, bottom = 9.sp),
color = color,
fontSize = fontSize,
fontStyle = fontStyle,
fontWeight = fontWeight,
fontFamily = fontFamily,
letterSpacing = letterSpacing,
textDecoration = textDecoration,
textAlign = textAlign,
lineHeight = lineHeight,
overflow = overflow,
softWrap = softWrap,
maxLines = maxLines,
onTextLayout = onTextLayout,
style = style.merge(
TextStyle(
color = colorResource(id = R.color.purple_500),
fontFamily = FontFamily(Font(R.font.podkova_semibold)),
fontSize = 18.sp
)
)
)
}
I think your initial approach is correct in the current version of Jetpack Compose (1.1.1), but they're currently working on APIs to enable setting this within a Text's TextStyle, so you can keep it all in one place, similar to XML.
See Compose UI 1.2.0-beta01: https://developer.android.com/jetpack/androidx/releases/compose-ui#version_12_2
It looks like they'll be adding a lineHeightStyle property to TextStyle, in addition to platformStyle (which allows setting includeFontPadding to false).
You Can create it by making Typography.
object MyStyle{
val myFontFamily = FontFamily(
Font(R.font.custom_font, FontWeight.Bold)
)
val myTypepo = Typography(
bodyLarge = TextStyle(
fontFamily = myFontFamily,
fontWeight = FontWeight.Bold,
fontSize = 16.sp,
lineHeight = 24.sp,
letterSpacing = 0.5.sp
),)
}
Now OnTextView you Can use it like :
Text(text = title, style = MyStyle.myTypepo.bodyLarge)
You can use this code, when you apply your theme and it will be implemented all across the application
MaterialTheme(
colorScheme = MyColorScheme,
typography = MyTypo,
content = {
ProvideTextStyle(value = defaultTextStyle, content)
}
)
I have a Row with some text and different sizes, then in the end of the Row I have a Column where I have two Text so the output is something like this :
Perhaps the image is not the best one but as you can see there's a Row where I have two Text one with different size, and in the end I have a Column with two Text the problem I'm having is that if I use Column(modifier = Modifier.alignBy(LastBaseline)) it solves one problem that I was having, but creating a new one that is that is moving all the Column to the top, so what I'm looking for is without losing this Baseline (since I have different size this command works perfect to avoid playing with margin) I want to avoid moving this column, note that the first Text of the Column the best is that should have a alignTopToTop="secondText" but I don't know if it's possible.
There's the two options, one is if I add the alignBy(LastBaseline) make the column fixed and don't move anything just align the Text from the bottom and another one is, somehow add this constraint to the second Text of the Row to don't move the Column
My code
ConstraintLayout {
val (text1, text2) = createRefs()
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.constrainAs(text1) {
top.linkTo(parent.top)
start.linkTo(parent.start)
end.linkTo(parent.end)
bottom.linkTo(parent.bottom)
}
) {
Text(
text = "Hello",
style = TextStyle(
fontWeight = FontWeight.Bold,
fontSize = 12.sp,
lineHeight = 18.sp,
color = Black,
),
)
Row(verticalAlignment = Alignment.Bottom) {
Text(
text = "World",
style = TextStyle(
fontWeight = FontWeight.Bold,
fontSize = 12.sp,
lineHeight = 18.sp,
color = Black,
),
modifier = Modifier
.alignByBaseline()
)
Text(
text = "WORLD",
style = TextStyle(
fontWeight = FontWeight.Bold,
fontSize = 24.sp,
lineHeight = 18.sp,
color = Black,
),
modifier = Modifier
.alignByBaseline()
)
Column(modifier = Modifier.alignBy(LastBaseline)) {
Text(
text = "^",
style = TextStyle(
fontWeight = FontWeight.Bold,
fontSize = 12.sp,
lineHeight = 18.sp,
color = Black,
),
)
Text(
text = "^",
style = TextStyle(
fontWeight = FontWeight.Bold,
fontSize = 12.sp,
lineHeight = 18.sp,
color = Black,
),
)
}
}
}
}
The problem as you can see here
Is that when I do the modifier = Modifier.alignBy(LastBaseline) the column goes up and it makes to make this extra space between the text 2 and text 1, is there any way to avoid this column moving up?
I want to draw a border around a Text like this,
Text("Box around text",
modifier = Modifier
.padding(top = 8.dp)
.border(width = 2.dp, color = Color.Red)
.background(Color.DarkGray))
Text("Box around text with a very very very very longlonglonglongword",
modifier = Modifier
.padding(top = 8.dp)
.border(width = 2.dp, color = Color.Red)
.background(Color.DarkGray)
)
But in the case of a multiline text, it doesn't look well.
There is a gap on the right between the border and the text.
So how to draw a border around a multiline text, so that it fits the text width?
It looks like a bug, I've created this issue.
Here's how you can restrict it manually:
#Composable
fun WrapTextContent(
text: String,
modifier: Modifier = Modifier,
color: Color = Color.Unspecified,
fontSize: TextUnit = TextUnit.Unspecified,
fontStyle: FontStyle? = null,
fontWeight: FontWeight? = null,
fontFamily: FontFamily? = null,
letterSpacing: TextUnit = TextUnit.Unspecified,
textDecoration: TextDecoration? = null,
textAlign: TextAlign? = null,
lineHeight: TextUnit = TextUnit.Unspecified,
overflow: TextOverflow = TextOverflow.Clip,
softWrap: Boolean = true,
maxLines: Int = Int.MAX_VALUE,
onTextLayout: (TextLayoutResult) -> Unit = {},
style: TextStyle = LocalTextStyle.current,
) {
SubcomposeLayout(modifier) { constraints ->
val composable = #Composable { localOnTextLayout: (TextLayoutResult) -> Unit ->
Text(
text = text,
color = color,
fontSize = fontSize,
fontStyle = fontStyle,
fontWeight = fontWeight,
fontFamily = fontFamily,
letterSpacing = letterSpacing,
textDecoration = textDecoration,
textAlign = textAlign,
lineHeight = lineHeight,
overflow = overflow,
softWrap = softWrap,
maxLines = maxLines,
onTextLayout = localOnTextLayout,
style = style,
)
}
var textWidthOpt: Int? = null
subcompose("measureView") {
composable { layoutResult ->
textWidthOpt = (0 until layoutResult.lineCount)
.maxOf { line ->
ceil(layoutResult.getLineRight(line) - layoutResult.getLineLeft(line)).toInt()
}
}
}[0].measure(constraints)
val textWidth = textWidthOpt!!
val placeable = subcompose("content") {
composable(onTextLayout)
}[0].measure(constraints.copy(minWidth = textWidth, maxWidth = textWidth))
layout(width = textWidth, height = placeable.height) {
placeable.place(0, 0)
}
}
}
Usage:
var textWidth by remember { mutableStateOf<Int?>(null) }
WrapTextContent(
"Box around text with a very very very very longlonglonglongword",
color = Color.White,
modifier = Modifier
.border(width = 2.dp, color = Color.Red)
.background(Color.DarkGray)
)
Result: