Table of contents
- The UI we are fixing
- Step 1: id and buildAnnotatedString
- Step 2: Create a map
- Step 3: Add InlineContent to text
- A more complicated example
The code
My app on the Google play store
Resources
Without InlineTextContent
With InlineTextContent
- Notice how the text now nicely wraps around the icons, instead looking like a separate entity
1) Create an id and buildAnnotatedString
- So we need to do two things in this step. First create an id which will represent the icon you are trying to place inside of the text. Second use
buildAnnotatedString
to create our text
val modId = "modIcon"
val text = buildAnnotatedString {
appendInlineContent(modId, "[icon]")
append("user message")
}
- We will talk more about
appendInlineContent()
in step 3
but just know that it what compose will use to swap out with our icon
2) Create a map
- Now we need to create a map. This is the map that compose will use to swap out our ids with our composables. So inside of this map the keys will be our ids(modId in my case) and the values will be the icons
val inlineContent = mapOf(
Pair(
// This tells the [CoreText] to replace the placeholder string "[icon]" by
// the composable given in the [InlineTextContent] object.
modId,
InlineTextContent(
// Placeholder tells text layout the expected size and vertical alignment of
// children composable.
Placeholder(
width = 20.sp,
height = 20.sp,
placeholderVerticalAlign = PlaceholderVerticalAlign.Center
)
) {
// 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,modifier = Modifier.fillMaxSize())
}
)
)
-a Placeholder is also needed for text layout to reserve space for our icon.
- Also, notice the modifier on the Icon,
Modifier.fillMaxSize()
, we do this so it fills up all the space reserved by the Placeholder
3) Add InlineContent to text
- when we add our inlineContent to text, Compose will try to find a InlineTextContent in the map we defined as inlineContent, whose key equals to our defined id.
Text(text = text,
inlineContent = inlineContent,
modifier = Modifier.fillMaxWidth().padding(5.dp),
)
A more complicated example
- So here is the actual code of the Composable that is running in my application. It adds AsyncImages conditionally to my text:
@Composable
fun ChatBadges(
username:String,
message: String,
isMod: Boolean,
isSub:Boolean,
color:Color,
textSize: TextUnit
){
val modBadge = "linkToTwitchsModIcon"
val subBadge = "linkToTwitchsSubIcon"
val modId = "modIcon"
val subId = "subIcon"
val text = buildAnnotatedString {
// Append a placeholder string "[icon]" and attach an annotation "inlineContent" on it.
if(isMod){
appendInlineContent(modId, "[icon]")
}
if(isSub){
appendInlineContent(subId, "[subicon]")
}
withStyle(style = SpanStyle(color = color, fontSize = textSize)) {
append(username)
}
withStyle(style = SpanStyle(color = Color.White)) {
append(message)
}
}
val inlineContent = mapOf(
Pair(
modId,
InlineTextContent(
Placeholder(
width = 20.sp,
height = 20.sp,
placeholderVerticalAlign = PlaceholderVerticalAlign.Center
)
) {
AsyncImage(
model = modBadge,
contentDescription = "Moderator badge",
modifier = Modifier.fillMaxSize().padding(2.dp)
)
}
),
Pair(
subId,
InlineTextContent(
Placeholder(
width = 20.sp,
height = 20.sp,
placeholderVerticalAlign = PlaceholderVerticalAlign.Center
)
) {
AsyncImage(
model = subBadge,
contentDescription = "Subscriber badge",
modifier = Modifier.fillMaxSize().padding(2.dp)
)
}
)
)
Text(text = text,
inlineContent = inlineContent,
modifier = Modifier.fillMaxWidth().padding(5.dp),
color = color,
fontSize = textSize
)
}
Conclusion
- Thank you for taking the time out of your day to read this blog post of mine. If you have any questions or concerns please comment below or reach out to me on Twitter
Top comments (0)