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?
Related
I am trying to make the ImageComposable wrap its height and width according to its content, along with the two Text composable, align to the bottom of Assemble composable. Following is the code for that:
#Composable
fun ImageComposable(url:String){
val painter = rememberAsyncImagePainter(
model = ImageRequest.Builder(LocalContext.current).data(url).apply{
placeholder(drawableResId = R.drawable.ic_broken_pic)
}.build()
)
Image(painter = painter, contentDescription = null, Modifier.padding(2.dp).border(width = 2.dp, shape = CircleShape, color = MaterialTheme.colors.onPrimary)
}
#Composable
fun Assemble(url:String){
Column (modifier = Modifier.fillMaxWidth().height(400.dp).background(MaterialTheme.colors.primary)
.padding(16.dp), verticalArrangement = Arrangement.Bottom) {
ImageComposable(url)
Text(text = "title")
Text(text = "Body")
}
}
but the ImageComposable ends up taking all the height and width of the Assemble composable and I am not able to see the two Text composables that I added in the column. So I am confused as to what is the exact problem here. I thought at least it should show the ImageComposable along with the two Text composable but it is not happening.
I am using coil image loading library here for parsing the image from url. For now in testing, I am passing url as an Empty String. Hence I am calling the composable as:
Assemble("")
I didn't find any document that would help me understand this behavior. So I wanted to know the reason to this problem and possible solutions to overcome it.
You can explicitly specify the height of each component:
fun ImageComposable(modifier: Modifier = Modifier, url: String){
//...
Image(modifier = modifier, //...
}
Column(//..
ImageComposable(modifier = Modifier.height(200.dp)//...
Text(modifier = Modifier.height(50.dp)//...
Text(modifier = Modifier.height(150.dp)//...
}
Or you can specify a fraction of the maximum height it will take up:
fun ImageComposable(modifier: Modifier = Modifier, url: String){
//...
Image(modifier = modifier, //...
}
Column(//..
ImageComposable(modifier = Modifier.fillMaxHeight(0.75f)//...
Text(modifier = Modifier.fillMaxHeight(0.1f)//...
Text(modifier = Modifier.fillMaxHeight(0.15f)//...
}
You can also try playing with the weight modifier:
fun ImageComposable(modifier: Modifier = Modifier, url: String){
//...
Image(modifier = modifier, //...
}
Column(//..
ImageComposable(modifier = Modifier.weight(1f)//...
Text(modifier = Modifier.weight(1f, fill = false)//...
Text(modifier = Modifier.weight(1f, fill = false)//...
}
It would be easier to solve your problem if there would be a sketch of what you want to achieve.
Nevertheless, I hope I can help:
It looks like the issue you are facing can be handled by Intrinsic measurements in Compose layouts.
The column measures each child individually without the dimension of your text constraining the image size. For this Intrinsics can be used.
Intrinsics lets you query children before they're actually measured.
For example, if you ask the minIntrinsicHeight of a Text with infinite width, it'll return the height of the Text as if the text was drawn in a single line.
By using IntrinsicSize.Max for the width of the Assemble composable like this:
#Composable
fun Assemble(url: String) {
Column(
modifier = Modifier
.width(IntrinsicSize.Max)
.background(MaterialTheme.colors.primary)
.padding(16.dp), verticalArrangement = Arrangement.Bottom
) {
ImageComposable(url)
Text(text = "title")
Text(text = "Body")
}
}
you can can create a layout like this:
(Please note that I am using a local drawable here)
You can now see the 2 texts and the width of the image is adjusted to the width of the texts.
Using Intrinsics to measure children in dependance to each other should help you to achieve what you wanted.
Please let me know if this layout does not meet your expectations.
I'm having problems with Japanese font types. Specifically with the font Noto Sans JP. When I apply the font to Text, I see that the vertical padding of the font seems too wide.
Here is my text display code:
Text(
text = "地域のお得は\nすべてここに",
style = Typography.h4,
)
// Typography
val Typography = Typography(
...
h4 = TextStyle(
fontFamily = NotoSans,
fontWeight = FontWeight.Normal,
fontSize = 34.sp,
letterSpacing = 0.25.sp
),
...
)
// Import font
private val NotoSans = FontFamily(
Font(R.font.noto_sans_black, FontWeight.Black),
Font(R.font.noto_sans_light, FontWeight.Light),
Font(R.font.noto_sans_bold, FontWeight.Bold),
Font(R.font.noto_sans_thin, FontWeight.Thin),
Font(R.font.noto_sans_medium, FontWeight.Medium),
Font(R.font.noto_sans_regular, FontWeight.Normal),
)
Link font: Noto Sans JP
I want to remove vertical padding of Text. With Android Baseview, there is includeFontPadding = false attribute to remove font padding. But with Android Compose I can't find any properties with similar functionality.
So in Android Compose, is there a way to remove the vertical padding of the font?
I found the solution for my question. By using Compose 1.2.0-alpha07 and above, you can use PlatformTextStyle api to set includeFontPadding.
Try to the below code:
private val NotoSans = FontFamily(
Font(R.font.noto_san_jp_black, FontWeight.Black),
Font(R.font.noto_san_jp_light, FontWeight.Light),
Font(R.font.noto_san_jp_bold, FontWeight.Bold),
Font(R.font.noto_san_jp_thin, FontWeight.Thin),
Font(R.font.noto_san_jp_medium, FontWeight.Medium),
Font(R.font.noto_san_jp_regular, FontWeight.Normal),
)
val Typography = Typography(
headlineLarge = Typography().headlineLarge.copy(
fontFamily = NotoSans,
)
)
#OptIn(ExperimentalTextApi::class)
/* ... */
Text(
text = "地域のお得は\nすべてここに",
style = MaterialTheme.typography.headlineLarge.copy(
platformStyle = PlatformTextStyle(
includeFontPadding = false
)
/* ... */
)
)
The result when includeFontPadding = false:
The result when includeFontPadding = true or no using it:
More information:
Fixing Font Padding in Compose Text - Medium
The Japanese font Noto Sans JP does show an unusual amount of extra white-space or gap above the first line of text when displayed on a screen:
However, this gap can be reduced by including a negative padding offset in the Text() composable - see 'modifier' parameter below:
#Composable
fun Greeting() {
Text(
text = "地域のお得は\nすべてここに",
fontFamily = NotoSans,
style = Typography.h4,
textAlign = TextAlign.Center,
modifier = Modifier
.padding(top = 0.dp).offset(y = (-30).dp)
)
}
This results in the same text being displayed, but without the gap:
If you would like to adjust the line spacing between the lines then you can add the "lineHeight" parameter to either the TextStyle() composable in the 'Type.kt' file or the Text() composable in the 'MainActivity.kt' file, as required; e.g. in this instance "lineHeight = 80.sp":
Hope this is helpful to someone!
I want to display a dynamic multiple lines text and an icon at the end of the last line. This icon can be animate. I try some ways but not success yet. How should I do?
Example view which had the same idea with my layout
In the Text composable you can use the inlineContent to define a map of tags that replaces certain ranges of the text. It's used to insert composables into text layout.
Then using a Placeholder you can reserve space in text layout.
Something like:
val myId = "inlineContent"
val text = buildAnnotatedString {
append("Where do you like to go?")
// Append a placeholder string "[icon]" and attach an annotation "inlineContent" on it.
appendInlineContent(myId, "[icon]")
}
val inlineContent = mapOf(
Pair(
// This tells the [CoreText] to replace the placeholder string "[icon]" by
// the composable given in the [InlineTextContent] object.
myId,
InlineTextContent(
// Placeholder tells text layout the expected size and vertical alignment of
// children composable.
Placeholder(
width = 12.sp,
height = 12.sp,
placeholderVerticalAlign = PlaceholderVerticalAlign.AboveBaseline
)
) {
// This Icon will fill maximum size, which is specified by the [Placeholder]
// above. Notice the width and height in [Placeholder] are specified in TextUnit,
// and are converted into pixel by text layout.
Icon(Icons.Filled.Face,"",tint = Color.Red)
}
)
)
Text(text = text,
modifier = Modifier.width(100.dp),
inlineContent = inlineContent)
It is a composable so you can use your favorite animation.
Just an example:
var blue by remember { mutableStateOf(false) }
val color by animateColorAsState(if (blue) Blue else Red,
animationSpec = tween(
durationMillis = 3000
))
and change the Icon to
Icon(Icons.Filled.Face,"", tint = color)
Coming from SwiftUI, I wanted to create a view of a Text where it has a background of a Circle, where the circle's width/height grow as the text inside Text gets longer.
Since there's no Circle() in Compose like there is in SwifUI, I created just a Spacer instead and clipped it. The code below is using ConstraintLayout because I don't know how I would get the width of the Text in order to set the size of my Circle composable to be the same:
#Composable
fun CircleDemo() {
ConstraintLayout {
val (circle, text) = createRefs()
Spacer(
modifier = Modifier
.background(Color.Black)
.constrainAs(circle) {
centerTo(text)
}
)
Text(
text = "Hello",
color = Color.White,
modifier = Modifier
.constrainAs(text) {
centerTo(parent)
}
)
}
}
I can use a mutableStateOf { 0 } where I update that in an onGloballyPositioned modifier attached to the Text and then set that as the requiredSize for the Spacer, but 1. that seems stupid and 2. the Spacer now draws outside the boundaries of the ConstraintLayout.
Visually I want to achieve this:
How would I go about doing this? Thank you :)
It is also possible to use drawBehind from the modifier of the textView itself such as below:
Text(
modifier = Modifier
.padding(16.dp)
.drawBehind {
drawCircle(
color = red,
radius = this.size.maxDimension
)
},
text = "Hello",
)
of course, you can further customize the radius and other properties as you wish!
You have to calculate the dimension of the background circle depending on the dimension of the text.
You can use a custom modifier based on Modifier.layout:
fun Modifier.circleLayout() =
layout { measurable, constraints ->
// Measure the composable
val placeable = measurable.measure(constraints)
//get the current max dimension to assign width=height
val currentHeight = placeable.height
val currentWidth = placeable.width
val newDiameter = maxOf(currentHeight, currentWidth)
//assign the dimension and the center position
layout(newDiameter, newDiameter) {
// Where the composable gets placed
placeable.placeRelative((newDiameter-currentWidth)/2, (newDiameter-currentHeight)/2)
}
}
Then just just apply it the Text with a background with a CircleShape:
Text(
text = "Hello World",
textAlign = TextAlign.Center,
color = Color.White,
modifier = Modifier
.background(Color.Black, shape = CircleShape)
.circleLayout()
.padding(8.dp)
)
#Composable
fun Avatar(color: Color) {
Box(
modifier = Modifier
.size(size.Dp)
.clip(CircleShape)
.background(color = color),
contentAlignment = Alignment.Center
) {
Text(text = "Hello World")
}
}
Use a background drawable of a black circle inside a transparent color. The background drawable will stretch to fill the view, and circles should stretch well without artifacting.
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,
)