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)
Related
Is it possible to make a growing Text Composable physically scrollable when the text reaches a specific length instead of this happening only when it reaches its container bounds? I want to allow the user to physically scroll the Text Composable above a specific text length before it reaches its container bounds.
Current Composable
val scrollState = rememberScrollState(0)
Text(
text = growingText,
modifier = Modifier.horizontalScroll(scrollState),
color = Color.Gray
)
You have to make sure your text content is big enough to enable the scroll and to do it you have to change the width according to the lenght.
Something like:
val maxCount = 6
var textWidth by remember { mutableStateOf<Int?>(null) }
val widthModifier = textWidth?.let { width ->
with(LocalDensity.current) {
Modifier.width( if (text.length >= maxCount)
(width-30).toDp() //reduce the width to enable the scroll
else
width.toDp())
}
} ?: Modifier
val scrollState = rememberScrollState()
Text(
text = text,
modifier = Modifier
.then(widthModifier)
.horizontalScroll(state = scrollState),
color = Color.Gray,
onTextLayout = { textWidth = it.size.width }
)
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 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.
As we know, AnnotatedString in JetpackCompose has provided some API of Android's SpannedString.
but I didn't find any way/workaround to inline ImageSpan to a Text (except using AndroidView)
Putting images inside text can be done using AnnotatedString and inlineContent parameter of Text Composable.
Inside buildAnnotatedString { ... } we need to define some id for our inline content using appendInlineContent(id = ...)
Then in Text Composable in inlineContent parameter we provide a map matching this id to InlineTextContent() object.
You can basically put any content there as long as you can define its size up-front in Placeholder.
Here is how it looks with an Image put in the middle of the text:
val annotatedString = buildAnnotatedString {
append("This is text ")
appendInlineContent(id = "imageId")
append(" with a call icon")
}
val inlineContentMap = mapOf(
"imageId" to InlineTextContent(
Placeholder(20.sp, 20.sp, PlaceholderVerticalAlign.TextCenter)
) {
Image(
imageVector = Icons.Default.Call,
modifier = Modifier.fillMaxSize(),
contentDescription = ""
)
}
)
Text(annotatedString, inlineContent = inlineContentMap)
I want to show a custom Indication when a button is clicked. The indication should be rounded in the corners and overlay a darker color. So far I was able to achieve this with the following code
Modifier.clickable(onClick = {}, indication = PressedIndication)
object PressedIndication : Indication {
private object DefaultIndicationInstance : IndicationInstance {
override fun ContentDrawScope.drawIndication(interactionState: InteractionState) {
drawContent()
if (interactionState.contains(Interaction.Pressed)) drawRoundRect(
cornerRadius = CornerRadius(4f, 4f), //<-- How to use dp values?
color = Color.Black.copy(
alpha = 0.3f
), size = size
)
}
}
override fun createInstance(): IndicationInstance {
return DefaultIndicationInstance
}
}
I can achieve rounded corners using drawRoundRect and cornerRadius but is there any way to use dp values?
Note: I cannot use clickable and clip because the clickable area doesn't exactly match the indication area
As described by 2jan222 you can convert using Dp.toPx().
In your case you could avoid to build a custom Indication.
You could use something like:
val interactionSource = remember { MutableInteractionSource() }
Modifier.clickable(
interactionSource = interactionSource,
indication = rememberRipple(
radius = 4.dp,
color=Color.Black.copy(alpha = 0.3f))
)
You can pass Dp but you have to adjust to the screen's density. The extension function Dp.toPx() inside a density scoped code block works fine.
val sizeInPx = with(LocalDensity.current) { 16.dp.toPx() }