I want to set text size as "DP". so I add extension fuction like below
#Composable
fun Dp.toTextDp(): TextUnit = textSp(density = LocalDensity.current)
private fun Dp.textSp(density: Density): TextUnit = with(density) {
this#textSp.toSp()
}
However I found text line hight issue when change device font size to Huge
Here is my text test code.
there are two text view has different way to set font size
first is using TextStyle
second is just set font Size.
#Composable
fun TextLineLayout(description: String) {
Text(
modifier = Modifier,
text = description,
color = Color.Gray,
style = TextStyle(fontSize = 20.dp.toTextDp())
)
Text(
modifier = Modifier,
text = description,
color = Color.Blue,
fontSize = 20.dp.toTextDp(),
)
}
and then add preview function with fontScale is 2f
#Preview(name = "font_size", fontScale = 2f)
#Composable
fun TextLinePreview() {
MaterialTheme {
TextLineLayoutTest()
}
}
The Hight of Second Text is not my expected.
The reason I wasn't getting the results I was expecting is, I think, is the line height.
At that time, Default lineHeight is lineHeight=24.0.sp
But First TextView has lineHeight=Unspecified because, I think, TextStyle was overrided
Is my analysis correct?
So should I set everty text size with TextStyle if I want to set "DP" size?
I faced with a problem related to Text element in Jetpack Compose. I wanted to place two separated Text elements under each other but I can't remove their inner paddings to make them look like not so separated.
As you can see on pic below Text with "02" has these paddings. Is there any way to crop them?
I tried to use .offset() and .height() modifiers to change the Text box size, but it looks like a really awful solution.
Code example to reproduce:
Box(
modifier = Modifier
.height(65.dp)
.width(65.dp)
.background(Color.White)
) {
Column(
modifier = Modifier
.align(Alignment.Center)
) {
Text(
text = "00",
maxLines = 1,
overflow = TextOverflow.Clip,
style = MaterialTheme.typography.h5.copy(
fontWeight = FontWeight.SemiBold
),
color = Color.Black,
modifier = Modifier
.align(Alignment.CenterHorizontally)
)
Text(
text = "Text",
style = MaterialTheme.typography.body2,
color = Color.Gray,
modifier = Modifier.align(Alignment.CenterHorizontally)
)
}
}
Thanks in advance!
In Type.kt Typography I defined h1 TextStyle like this:
h1 = TextStyle(
fontFamily = FontFamily.SansSerif,
fontWeight = FontWeight.W500,
fontSize = 70.sp,
color = Color(0xFFF8F9FC)
),
Now how can I define the Text widget to be an h1 so these TextStyles applies automatically?
You can get the current theme h1 from any #Composable with MaterialTheme.typography.h1, so your widget might look like this:
#Composable
fun TextH1(
text: String,
modifier: Modifier = Modifier,
) {
Text(
text = text,
modifier = modifier,
style = MaterialTheme.typography.h1,
)
}
If you plan on using your style globally for all text that you want identified as h1, you should create a custom composable for that and apply your style there:
#Composable
fun TextH1(
text: String,
modifier: Modifier = Modifier
) {
Text(text = text, modifier = modifier, textStyle = h1)
}
Alternatively, you could use CompositionLocal, but that tends to become cumbersome if used extensively.
I am trying to customize the Typography in my project. But it not affected in my widgets.
My TypoGraphy customization:
Code:
subtitle1 = TextStyle(
fontFamily = FontFamily.Monospace,
fontWeight = FontWeight.Bold,
fontSize = 30.sp,
)
In my Text :
Text(text = "Homemade veg pizza", style = MaterialTheme.typography.subtitle1)
My Output :
My problem:
Fontsize and fontfamily not changed. It looks same like default text. How to set custom TypoGraphy in Jetpack Compose?
It's my mistake. I figure out.
We need to set the MaterialTheme before use custom TypoGraphy.
In my rootView I place my rootView under the MaterialTheme. Issue fixed.
setContent {
MaterialTheme { //I missed this line
ThemesRootView()
}
}
Output:
For various reasons a Text should always have at least the height equal to x lines of text, no matter if it has less than x lines of text. The Text and BasicText Composables only have a maxLines parameter but no minLines
I have tried the following (x = 3):
Text(
modifier = Modifier.sizeIn(minHeight = with(LocalDensity.current) {
(42*3).sp.toDp()
}),
color = MaterialTheme.colors.onPrimary,
text = "Sample", textAlign = TextAlign.Center,
style = MaterialTheme.typography.h2 /* fontSize = 42 */,
lineHeight = 42.sp
)
The resulting height is less than if the text would contain 3 lines
Back in View World Android, we could simply use minLines=3, how can we achieve this in Jetpack Compose?
Your code is almost correct, just set lineHeight to fontSize*4/3:
var lineHeight = MaterialTheme.typography.h2.fontSize*4/3
Text(
modifier = Modifier.sizeIn(minHeight = with(LocalDensity.current) {
(lineHeight*3).toDp()
}),
color = MaterialTheme.colors.onPrimary,
text = "Sample", textAlign = TextAlign.Center,
style = MaterialTheme.typography.h2,
lineHeight = lineHeight
)
But you can do something similar without calculations using onTextLayout callback:
fun main() = Window {
var text by remember { mutableStateOf("Hello, World!") }
var lines by remember { mutableStateOf(0) }
MaterialTheme {
Button(onClick = {
text += "\nnew line"
}) {
Column {
Text(text,
maxLines = 5,
style = MaterialTheme.typography.h2,
onTextLayout = { res -> lines = res.lineCount })
for (i in lines..2) {
Text(" ", style = MaterialTheme.typography.h2)
}
}
}
}
}
While we are waiting for Google implements this feature you can use this workaround:
#Preview
#Composable
fun MinLinesPreview() {
lateinit var textLayoutResult: TextLayoutResult
val text = "Title\ntitle\nTITLE\nTitle"
// val text = "title\ntitle\ntitle\ntitle"
// val text = "title\ntitle"
// val text = "title"
Text(
modifier = Modifier.fillMaxWidth(),
text = text.addEmptyLines(3), // ensures string has at least N lines,
textAlign = TextAlign.Center,
maxLines = 4,
)
}
fun String.addEmptyLines(lines: Int) = this + "\n".repeat(lines)
Now your Text has the same height regardless string content:
This solution is much more easier than calculate Text's bottom offset based on line height in onTextLayout (spoiler: start, center and last line have different height)
If one additional recomposition of the Text is fine for you, you can also make use of the onTextLayout callback of Text as a workaround until there is official support for minimum lines from Google:
val minLineCount = 4
var text by remember { mutableStateOf(description) }
Text(
text = text,
maxLines = minLineCount, // optional, if you want the Text to always be exactly 4 lines long
overflow = TextOverflow.Ellipsis, // optional, if you want ellipsizing
textAlign = TextAlign.Center,
onTextLayout = { textLayoutResult ->
// the following causes a recomposition if there isn't enough text to occupy the minimum number of lines!
if ((textLayoutResult.lineCount) < minLineCount) {
// don't forget the space after the line break, otherwise empty lines won't get full height!
text = description + "\n ".repeat(minLineCount - textLayoutResult.lineCount)
}
},
modifier = Modifier.fillMaxWidth()
)
This will also properly work with ellipsizing and any kind of font padding, line height style etc. settings your heart desires.
A "fake" 4-line Text (with, say, 2 empty lines at the end) will have the same height like a "real" 4 line Text with 4 fully occupied lines of text. This oftentimes can be super important when e.g. laying out multiple wrap_content-height cards horizontally next to each other and the Text (in combination with maxLines) should determine the height of the cards, while all cards should have the same height (and it should work in regular and tall languages, like Burmese).
Please note, that this will not work in Android Studio's preview. My guess is, that Studio doesn't allow recompositions in the preview for performance reasons.
Below is a solution that I came up with that will set the height to a specific number of lines (you could adapt the modifier to make it minLines) It is inspired by code found from the compose SDK
// Inspiration: https://github.com/androidx/androidx/blob/6075c715aea671a616890dd7f0fc9a50d96e75b9/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/MaxLinesHeightModifier.kt#L38
fun Modifier.minLinesHeight(
minLines: Int,
textStyle: TextStyle
) = composed {
val density = LocalDensity.current
val layoutDirection = LocalLayoutDirection.current
val resolvedStyle = remember(textStyle, layoutDirection) {
resolveDefaults(textStyle, layoutDirection)
}
val resourceLoader = LocalFontLoader.current
val heightOfTextLines = remember(
density,
textStyle,
layoutDirection
) {
val lines = (EmptyTextReplacement + "\n").repeat(minLines - 1)
computeSizeForDefaultText(
style = resolvedStyle,
density = density,
text = lines,
maxLines = minLines,
resourceLoader
).height
}
val heightInDp: Dp = with(density) { heightOfTextLines.toDp() }
val heightToSet = heightInDp + OutlinedTextBoxDecoration
Modifier.height(heightToSet)
}
// Source: https://github.com/androidx/androidx/blob/6075c715aea671a616890dd7f0fc9a50d96e75b9/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextFieldDelegate.kt#L61
fun computeSizeForDefaultText(
style: TextStyle,
density: Density,
text: String = EmptyTextReplacement,
maxLines: Int = 1,
resourceLoader: Font.ResourceLoader
): IntSize {
val paragraph = Paragraph(
paragraphIntrinsics = ParagraphIntrinsics(
text = text,
style = style,
density = density,
resourceLoader = resourceLoader
),
maxLines = maxLines,
ellipsis = false,
width = Float.POSITIVE_INFINITY
)
return IntSize(paragraph.minIntrinsicWidth.ceilToIntPx(), paragraph.height.ceilToIntPx())
}
// Source: https://github.com/androidx/androidx/blob/6075c715aea671a616890dd7f0fc9a50d96e75b9/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextFieldDelegate.kt#L47
internal const val DefaultWidthCharCount = 10
internal val EmptyTextReplacement = "H".repeat(DefaultWidthCharCount)
// Needed because paragraph only calculates the height to display the text and not the entire height
// to display the decoration of the TextField Widget
internal val OutlinedTextBoxDecoration = 40.dp
// Source: https://github.com/androidx/androidx/blob/6075c715aea671a616890dd7f0fc9a50d96e75b9/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextDelegate.kt#L296
internal fun Float.ceilToIntPx(): Int = ceil(this).roundToInt()
Additional discussion on this implementation and other options can be found here:
https://kotlinlang.slack.com/archives/CJLTWPH7S/p1621789835172600
Starting from M2 1.4.0-alpha02 and M3 1.1.0-alpha02 you can use the minLines attribute in the Text:
Text(
text = "MinLines = 3",
modifier = Modifier.fillMaxWidth().background(Yellow),
minLines = 3
)
Note that minLines is the minimum height in terms of minimum number of visible lines. It is required that 1 <= minLines <= maxLines.
You can use it with M2 and M3.
create custom Text
it doesn't work in #Preview but the runtime
#Composable
fun MinLineText(
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,
minLines: Int = 0,
onTextLayout: (TextLayoutResult) -> Unit = {},
style: TextStyle = LocalTextStyle.current
) {
var mText by remember { mutableStateOf(text) }
Text(
mText,
modifier,
color,
fontSize,
fontStyle,
fontWeight,
fontFamily,
letterSpacing,
textDecoration,
textAlign,
lineHeight,
overflow,
softWrap,
maxLines,
{
if (it.lineCount < minLines) {
mText = text + "\n".repeat(minLines - it.lineCount)
}
onTextLayout(it)
},
style,
)
}
usage
MinLineText(
text = "a sample text",
minLines = 2,
)