1. 项目概述
"Compose笔记(七十一)--InlineTextContent"这个标题揭示了这是一个关于Jetpack Compose中文本处理功能的系列教程。作为Android现代UI开发的核心框架,Compose的文本处理能力直接关系到应用界面的表现力和交互性。InlineTextContent作为Compose文本系统中的重要特性,允许开发者在文本流中嵌入自定义的可组合项,实现图文混排、特殊标记等复杂排版效果。
在实际开发中,我们经常遇到需要在文本中嵌入图标、按钮或其他交互元素的需求。传统View系统实现这类需求往往需要复杂的Span处理或自定义ViewGroup,而Compose通过InlineTextContent提供了更声明式、更符合现代UI开发理念的解决方案。掌握这个特性,能够显著提升开发者在处理复杂文本场景时的效率和质量。
2. 核心概念解析
2.1 InlineTextContent的基本原理
InlineTextContent是Compose文本系统中的一种特殊标记,它允许开发者在文本布局中预留特定位置,并用自定义的可组合项填充这些位置。其核心工作原理可以分解为以下几个步骤:
-
标记定义:首先需要创建一个AnnotatedString,在其中使用
appendInlineContent方法插入特殊标记。这些标记本质上是指向特定可组合项的引用。 -
内容映射:通过InlineTextContentProvider将标记与实际的Composable内容关联起来。这个映射关系决定了当文本系统遇到标记时应该渲染什么内容。
-
布局计算:Compose文本引擎在测量和布局阶段会为这些标记预留空间,确保文本流能够正确环绕这些嵌入式内容。
-
最终渲染:在绘制阶段,系统会根据映射关系在标记位置绘制对应的可组合项,实现无缝的图文混排效果。
2.2 关键数据结构与API
理解InlineTextContent需要掌握几个核心类和接口:
kotlin复制class InlineTextContent(
val placeholder: Placeholder,
val children: @Composable () -> Unit
)
class Placeholder(
val width: TextUnit,
val height: TextUnit,
val placeholderVerticalAlign: PlaceholderVerticalAlign = TextBaseline
)
interface InlineTextContentProvider {
fun get(inlineContentId: String): InlineTextContent?
}
其中Placeholder定义了嵌入式内容占用的空间尺寸和对齐方式,而InlineTextContent则封装了实际的Composable内容和其占位信息。开发者通过实现InlineTextContentProvider接口来提供具体的映射关系。
3. 典型应用场景与实现
3.1 基础图文混排实现
让我们从一个最简单的例子开始 - 在文本中嵌入图标。这是InlineTextContent最直观的应用场景:
kotlin复制@Composable
fun TextWithIcon() {
val iconId = "iconTag"
val annotatedString = buildAnnotatedString {
append("点击")
appendInlineContent(iconId, "[图标]")
append("查看详情")
}
val inlineContent = mapOf(
iconId to InlineTextContent(
Placeholder(
width = 16.sp,
height = 16.sp,
placeholderVerticalAlign = PlaceholderVerticalAlign.TextCenter
)
) {
Icon(
Icons.Filled.Info,
contentDescription = null,
modifier = Modifier.size(16.sp)
)
}
)
BasicText(
text = annotatedString,
inlineContent = inlineContent
)
}
这段代码展示了完整的实现流程:
- 创建带有标记的AnnotatedString
- 构建标记到InlineTextContent的映射
- 通过BasicText显示最终结果
注意:Placeholder的尺寸必须与实际内容尺寸匹配,否则会导致布局错乱。建议使用与内容相同的尺寸单位(如.sp)来确保缩放一致性。
3.2 交互式文本元素
InlineTextContent更强大的能力在于可以嵌入任意可交互的Composable。下面是一个在文本中嵌入按钮的示例:
kotlin复制@Composable
fun TextWithButton() {
val buttonId = "buttonTag"
val annotatedString = buildAnnotatedString {
append("请")
appendInlineContent(buttonId, "[按钮]")
append("确认操作")
}
var clicked by remember { mutableStateOf(false) }
val inlineContent = mapOf(
buttonId to InlineTextContent(
Placeholder(
width = 80.sp,
height = 24.sp,
placeholderVerticalAlign = PlaceholderVerticalAlign.TextBottom
)
) {
Button(
onClick = { clicked = true },
modifier = Modifier.height(24.dp),
contentPadding = PaddingValues(horizontal = 8.dp)
) {
Text("点击", fontSize = 12.sp)
}
}
)
BasicText(
text = annotatedString,
inlineContent = inlineContent,
modifier = Modifier.padding(16.dp)
)
}
这个例子展示了几个关键点:
- 嵌入式内容可以包含完整的交互逻辑
- 需要仔细调整占位尺寸与内容实际尺寸的关系
- 垂直对齐方式的选择会影响视觉效果
3.3 复杂富文本渲染
对于更复杂的场景,比如需要混合多种嵌入式内容的富文本,我们可以通过组合多个InlineTextContent来实现:
kotlin复制@Composable
fun RichTextWithMultipleElements() {
val elements = listOf(
InlineElement("icon1", Icons.Filled.Star, 20.sp),
InlineElement("text1", "重要提示", Color.Red),
InlineElement("icon2", Icons.Filled.Warning, 24.sp)
)
val annotatedString = buildAnnotatedString {
append("请注意:")
elements.forEach { element ->
appendInlineContent(element.id, element.placeholderText)
}
append("这些内容需要特别关注")
}
val inlineContent = elements.associate { element ->
element.id to element.createInlineContent()
}
BasicText(
text = annotatedString,
inlineContent = inlineContent,
style = TextStyle(fontSize = 16.sp)
)
}
data class InlineElement(
val id: String,
val content: Any,
val size: TextUnit,
val color: Color = Color.Unspecified
) {
val placeholderText = "[$id]"
@Composable
fun createInlineContent(): InlineTextContent {
return when(content) {
is ImageVector -> InlineTextContent(
Placeholder(size, size)
) {
Icon(
content,
contentDescription = null,
tint = color,
modifier = Modifier.size(size)
)
}
is String -> InlineTextContent(
Placeholder(
width = size * content.length.toFloat() * 0.6f,
height = size * 1.2f
)
) {
Text(
content,
color = color,
fontSize = size
)
}
else -> throw IllegalArgumentException("Unsupported content type")
}
}
}
这种模式特别适合需要动态生成复杂富文本的场景,通过将InlineElement抽象为数据模型,我们可以灵活地组合各种嵌入式内容。
4. 高级技巧与性能优化
4.1 动态内容更新策略
当嵌入式内容需要根据状态变化时,合理的更新策略对性能至关重要。考虑以下场景:
kotlin复制@Composable
fun DynamicInlineContent() {
val dynamicId = "dynamicContent"
var counter by remember { mutableStateOf(0) }
LaunchedEffect(Unit) {
while(true) {
delay(1000)
counter++
}
}
val annotatedString = remember {
buildAnnotatedString {
append("计数器:")
appendInlineContent(dynamicId, "[动态]")
}
}
val inlineContent = remember {
mutableStateMapOf(
dynamicId to InlineTextContent(
Placeholder(50.sp, 24.sp)
) {
Text(
text = counter.toString(),
fontSize = 20.sp,
fontWeight = FontWeight.Bold
)
}
)
}
// 优化:只在counter变化时更新inlineContent
LaunchedEffect(counter) {
inlineContent[dynamicId] = InlineTextContent(
Placeholder(50.sp, 24.sp)
) {
Text(
text = counter.toString(),
fontSize = 20.sp,
fontWeight = FontWeight.Bold
color = if(counter % 2 == 0) Color.Blue else Color.Red
)
}
}
BasicText(
text = annotatedString,
inlineContent = inlineContent
)
}
关键优化点:
- 使用remember缓存不变的annotatedString
- 将inlineContent放在mutableStateMap中,实现细粒度更新
- 通过LaunchedEffect只在counter变化时更新内容
4.2 复杂布局的对齐控制
对于需要精确控制对齐方式的场景,PlaceholderVerticalAlign提供了多种选项:
kotlin复制enum class PlaceholderVerticalAlign {
AboveBaseline,
TextTop,
TextCenter,
TextBottom,
BelowBaseline,
Top,
Center,
Bottom
}
实际使用中需要注意:
- 以Text开头的对齐方式是基于文本基线计算的
- 普通的Top/Center/Bottom是基于整个行高计算的
- 对于图标类内容,通常使用TextCenter或TextBottom效果最佳
4.3 性能敏感场景的优化
当处理大量InlineTextContent时(如聊天消息列表),需要注意以下性能优化点:
-
重用AnnotatedString:对于静态文本部分,尽可能重用构建好的AnnotatedString
-
惰性加载:使用LazyColumn等惰性布局,确保只有可见项才会实例化InlineContent
-
内容稳定性:通过remember和derivedStateOf确保InlineContent只在必要时重组
-
尺寸预计算:对于已知尺寸的内容,精确指定Placeholder尺寸避免布局传递
kotlin复制@Composable
fun HighPerformanceInlineList(items: List<RichItem>) {
LazyColumn {
items(items) { item ->
val annotatedString = remember(item.id) {
buildAnnotatedString {
append(item.text)
item.elements.forEach { element ->
appendInlineContent(element.id, element.placeholder)
}
}
}
val inlineContent = remember(item.id) {
item.elements.associate { element ->
element.id to element.createInlineContent()
}
}
BasicText(
text = annotatedString,
inlineContent = inlineContent,
modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
)
}
}
}
5. 常见问题与解决方案
5.1 布局错位问题
症状:嵌入式内容与文本基线不对齐,或与其他元素重叠
解决方案:
- 检查Placeholder的尺寸是否与实际内容匹配
- 尝试不同的PlaceholderVerticalAlign值
- 确保所有尺寸单位一致(全用sp或全用dp)
kotlin复制// 错误示例:混合使用单位和尺寸不匹配
InlineTextContent(
Placeholder(width = 16.dp, height = 16.sp) // 单位不一致
) {
Icon(Icons.Filled.Info, null, Modifier.size(20.dp)) // 尺寸不匹配
}
// 正确示例
InlineTextContent(
Placeholder(width = 20.sp, height = 20.sp)
) {
Icon(Icons.Filled.Info, null, Modifier.size(20.sp))
}
5.2 点击事件处理异常
症状:嵌入式内容的点击区域不正确或事件被错误拦截
调试步骤:
- 为可点击内容添加debug边界:
kotlin复制Modifier.border(1.dp, Color.Red) - 检查父容器是否设置了不必要的点击拦截
- 确保Placeholder尺寸足够容纳点击区域
5.3 文本测量性能问题
症状:包含大量InlineTextContent的文本测量缓慢
优化方案:
- 对静态内容使用remember缓存测量结果
- 对于列表场景,使用固定尺寸Placeholder避免动态测量
- 考虑将复杂InlineContent拆分为独立Text组件
5.4 多行文本中的布局问题
症状:嵌入式内容在多行文本中位置异常
解决方案:
- 确保Placeholder宽度不超过行宽
- 对于必须跨行的内容,考虑使用Box布局而非InlineTextContent
- 使用
SoftWrap控制文本换行行为
kotlin复制BasicText(
text = annotatedString,
inlineContent = inlineContent,
overflow = TextOverflow.Ellipsis,
softWrap = true,
maxLines = 2
)
6. 实际案例:构建一个标签系统
让我们通过一个完整的案例来展示InlineTextContent的实际应用 - 构建一个支持多种标签的文本系统:
kotlin复制// 定义标签类型
sealed class TagType {
data class IconTag(val icon: ImageVector, val size: Dp = 16.dp) : TagType()
data class BadgeTag(val text: String, val color: Color) : TagType()
data class UserTag(val userId: String) : TagType()
}
// 标签渲染器
@Composable
fun TagRenderer(tag: TagType, onClick: (() -> Unit)? = null) {
when(tag) {
is TagType.IconTag -> Icon(
tag.icon,
contentDescription = null,
modifier = Modifier
.size(tag.size)
.clickable(enabled = onClick != null) { onClick?.invoke() }
)
is TagType.BadgeTag -> Box(
contentAlignment = Center,
modifier = Modifier
.padding(horizontal = 4.dp)
.height(20.dp)
.clip(RoundedCornerShape(4.dp))
.background(tag.color.copy(alpha = 0.2f))
.clickable(enabled = onClick != null) { onClick?.invoke() }
) {
Text(
tag.text,
color = tag.color,
fontSize = 12.sp,
modifier = Modifier.padding(horizontal = 4.dp)
)
}
is TagType.UserTag -> {
val user by rememberUser(tag.userId)
UserAvatar(user, onClick = { onClick?.invoke() })
}
}
}
// 带标签的文本组件
@Composable
fun TaggedText(
text: String,
tags: Map<String, TagType>,
modifier: Modifier = Modifier,
style: TextStyle = TextStyle.Default,
onClickTag: (String) -> Unit = {}
) {
val tagRegex = Regex("""\[(\w+)\]""")
val tagPositions = mutableListOf<Pair<IntRange, String>>()
val annotatedString = buildAnnotatedString {
var lastIndex = 0
tagRegex.findAll(text).forEach { match ->
val tagId = match.groupValues[1]
if(tags.containsKey(tagId)) {
append(text.substring(lastIndex, match.range.first))
tagPositions.add(match.range to tagId)
appendInlineContent(tagId, match.value)
lastIndex = match.range.last + 1
}
}
append(text.substring(lastIndex))
}
val inlineContent = tags.mapValues { (tagId, tagType) ->
InlineTextContent(
Placeholder(
width = when(tagType) {
is TagType.IconTag -> with(LocalDensity.current) { tagType.size.toSp() }
is TagType.BadgeTag -> 12.sp * tagType.text.length.toFloat()
is TagType.UserTag -> 24.sp
},
height = when(tagType) {
is TagType.IconTag -> with(LocalDensity.current) { tagType.size.toSp() }
is TagType.BadgeTag -> 20.sp
is TagType.UserTag -> 24.sp
},
placeholderVerticalAlign = PlaceholderVerticalAlign.TextCenter
)
) {
TagRenderer(tagType) { onClickTag(tagId) }
}
}
BasicText(
text = annotatedString,
inlineContent = inlineContent,
modifier = modifier,
style = style
)
}
// 使用示例
@Composable
fun TaggedTextExample() {
val tags = mapOf(
"star" to TagType.IconTag(Icons.Filled.Star, 20.dp),
"warning" to TagType.IconTag(Icons.Filled.Warning, 24.dp),
"premium" to TagType.BadgeTag("PREMIUM", Color(0xFFFFD700)),
"user1" to TagType.UserTag("user-123")
)
TaggedText(
text = "这是一个[star]示例文本,包含[warning]警告和[premium]高级标签,以及用户[user1]",
tags = tags,
modifier = Modifier.padding(16.dp),
onClickTag = { tagId ->
println("点击了标签: $tagId")
}
)
}
这个案例展示了如何构建一个完整的标签系统:
- 定义可扩展的标签类型体系
- 实现灵活的标签渲染逻辑
- 处理文本解析和标记替换
- 支持标签点击交互
- 自动处理各种标签的尺寸和对齐
7. 与其他文本特性的结合使用
InlineTextContent可以与其他Compose文本特性结合使用,实现更复杂的效果:
7.1 与ClickableText结合
kotlin复制@Composable
fun ClickableInlineText() {
val linkId = "linkTag"
val annotatedString = buildAnnotatedString {
append("访问我们的")
appendInlineContent(linkId, "[网站]")
append("获取更多信息")
}
val inlineContent = mapOf(
linkId to InlineTextContent(
Placeholder(60.sp, 24.sp)
) {
Text(
"官网",
color = Color.Blue,
modifier = Modifier
.border(1.dp, Color.Blue, RoundedCornerShape(4.dp))
.padding(horizontal = 4.dp)
)
}
)
ClickableText(
text = annotatedString,
inlineContent = inlineContent,
onClick = { offset ->
println("点击位置: $offset")
}
)
}
7.2 与SelectionContainer结合
kotlin复制@Composable
fun SelectableInlineText() {
val codeId = "codeTag"
val annotatedString = buildAnnotatedString {
append("示例代码:")
appendInlineContent(codeId, "[代码]")
append("可以复制")
}
val inlineContent = mapOf(
codeId to InlineTextContent(
Placeholder(80.sp, 24.sp)
) {
Box(
Modifier
.background(Color.LightGray.copy(alpha = 0.3f))
.border(1.dp, Color.Gray)
.padding(horizontal = 4.dp)
) {
Text("val x = 1", fontSize = 14.sp)
}
}
)
SelectionContainer {
BasicText(
text = annotatedString,
inlineContent = inlineContent,
modifier = Modifier.padding(16.dp)
)
}
}
7.3 与TextStyle结合实现复杂样式
kotlin复制@Composable
fun StyledInlineText() {
val emojiId = "emojiTag"
val annotatedString = buildAnnotatedString {
withStyle(SpanStyle(fontWeight = FontWeight.Bold)) {
append("重要通知")
}
append(":请查看")
appendInlineContent(emojiId, "[表情]")
withStyle(SpanStyle(color = Color.Red)) {
append("紧急内容")
}
}
val inlineContent = mapOf(
emojiId to InlineTextContent(
Placeholder(24.sp, 24.sp)
) {
Text(
text = "⚠️",
fontSize = 20.sp
)
}
)
BasicText(
text = annotatedString,
inlineContent = inlineContent,
style = TextStyle(fontSize = 18.sp)
)
}
8. 测试与调试技巧
8.1 可视化调试工具
在开发过程中,可以使用以下Modifier帮助调试InlineTextContent布局:
kotlin复制Modifier
.background(Color.LightGray.copy(alpha = 0.2f)) // 显示文本容器边界
.border(1.dp, Color.Red) // 显示组件边界
8.2 单元测试策略
对于包含InlineTextContent的组件,建议采用分层测试策略:
-
模型层测试:验证AnnotatedString构建逻辑
kotlin复制@Test fun testAnnotatedStringBuilder() { val result = buildAnnotatedString { append("Hello") appendInlineContent("icon", "[icon]") } assertEquals(2, result.inlineContent.size) assertEquals("[icon]", result.inlineContent[0].second) } -
UI快照测试:验证整体渲染效果
kotlin复制@Test fun testInlineTextRendering() { composeTestRule.setContent { InlineTextExample() } composeTestRule.onRoot().captureToImage().assertAgainstGolden() } -
交互测试:验证嵌入式内容的交互行为
kotlin复制@Test fun testInlineButtonClick() { var clicked = false composeTestRule.setContent { TextWithButton(onClick = { clicked = true }) } composeTestRule.onNodeWithText("点击").performClick() assertTrue(clicked) }
8.3 性能测试要点
当评估InlineTextContent的性能影响时,需要关注以下指标:
- 首次布局时间:测量从Composition到首次布局完成的时间
- 重组频率:使用Layout Inspector检查不必要的重组
- 内存占用:监控嵌入式内容的内存使用情况
可以通过以下方式收集这些指标:
kotlin复制@Composable
fun PerformanceTestWrapper() {
val benchmark = remember { PerformanceBenchmark() }
DisposableEffect(Unit) {
benchmark.start()
onDispose {
benchmark.end()
println("Duration: ${benchmark.durationMs}ms")
}
}
InlineTextComplexExample()
}
9. 设计模式与最佳实践
9.1 工厂模式管理InlineContent
对于大型项目,建议使用工厂模式统一管理各类InlineContent:
kotlin复制object InlineContentFactory {
private val registry = mutableMapOf<String, InlineContentProvider>()
fun register(type: String, provider: InlineContentProvider) {
registry[type] = provider
}
fun create(
type: String,
id: String,
data: Any
): Pair<String, InlineTextContent>? {
return registry[type]?.provide(id, data)
}
}
interface InlineContentProvider {
fun provide(id: String, data: Any): Pair<String, InlineTextContent>
}
// 注册示例
InlineContentFactory.register("icon", object : InlineContentProvider {
override fun provide(id: String, data: Any): Pair<String, InlineTextContent> {
val icon = data as ImageVector
return id to InlineTextContent(
Placeholder(24.sp, 24.sp)
) {
Icon(icon, null)
}
}
})
// 使用示例
@Composable
fun FactoredInlineText() {
val text = "点击[icon:info]查看详情"
val regex = Regex("""\[(\w+):([^\]]+)\]""")
val annotatedString = buildAnnotatedString {
var lastIndex = 0
regex.findAll(text).forEach { match ->
val type = match.groupValues[1]
val data = match.groupValues[2]
append(text.substring(lastIndex, match.range.first))
InlineContentFactory.create(type, match.value, data)?.let { (id, content) ->
appendInlineContent(id, match.value)
}
lastIndex = match.range.last + 1
}
append(text.substring(lastIndex))
}
// ... 剩余实现
}
9.2 状态管理策略
对于需要维护状态的InlineContent,推荐采用以下模式:
kotlin复制@Composable
fun StatefulInlineContent() {
val toggleId = "toggle"
val annotatedString = buildAnnotatedString {
append("当前状态:")
appendInlineContent(toggleId, "[开关]")
}
var toggled by remember { mutableStateOf(false) }
val inlineContent = mapOf(
toggleId to InlineTextContent(
Placeholder(48.sp, 24.sp)
) {
Switch(
checked = toggled,
onCheckedChange = { toggled = it }
)
}
)
BasicText(
text = annotatedString,
inlineContent = inlineContent
)
}
关键原则:
- 将状态提升到足够高的层级
- 使用remember保持状态稳定性
- 避免在InlineContent内部直接修改外部状态
9.3 主题与样式继承
InlineContent默认不会自动继承外部文本样式,需要显式处理:
kotlin复制@Composable
fun ThemedInlineText() {
val textStyle = LocalTextStyle.current
val colorScheme = MaterialTheme.colorScheme
val emojiId = "emoji"
val annotatedString = buildAnnotatedString {
append("当前主题:")
appendInlineContent(emojiId, "[表情]")
}
val inlineContent = mapOf(
emojiId to InlineTextContent(
Placeholder(24.sp, 24.sp)
) {
CompositionLocalProvider(
LocalTextStyle provides textStyle.copy(fontSize = 20.sp)
) {
Text(
text = "🎨",
color = colorScheme.primary
)
}
}
)
BasicText(
text = annotatedString,
inlineContent = inlineContent,
style = MaterialTheme.typography.bodyLarge
)
}
10. 平台特性与兼容性
10.1 Android特定优化
在Android平台上,可以利用以下特性优化InlineTextContent:
-
字体回退:确保嵌入式图标在缺少字体时正常显示
kotlin复制
fontFamily = FontFamily( Font(R.font.roboto_regular), Font(R.font.icon_font, FontWeight.Normal, FontStyle.Normal) ) -
RTL布局支持:测试InlineContent在RTL语言下的表现
kotlin复制
CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) { InlineTextExample() } -
无障碍支持:为嵌入式内容添加适当的contentDescription
kotlin复制Icon(Icons.Filled.Info, contentDescription = "信息图标")
10.2 多平台兼容性考虑
当目标包括iOS等其他平台时,需要注意:
- 字体一致性:确保自定义图标在所有平台可用
- 尺寸单位:使用.sp而非.dp确保文本相关尺寸正确缩放
- 性能差异:不同平台可能有不同的文本渲染性能特征
kotlin复制@Composable
expect fun platformSpecificIcon(): ImageVector
@Composable
fun MultiPlatformInlineText() {
val iconId = "icon"
val annotatedString = buildAnnotatedString {
append("跨平台图标:")
appendInlineContent(iconId, "[图标]")
}
val inlineContent = mapOf(
iconId to InlineTextContent(
Placeholder(24.sp, 24.sp)
) {
Icon(platformSpecificIcon(), null)
}
)
BasicText(
text = annotatedString,
inlineContent = inlineContent
)
}
11. 扩展与进阶应用
11.1 自定义文本布局
对于需要完全自定义布局的场景,可以基于TextMeasurer实现:
kotlin复制@Composable
fun CustomTextLayout() {
val textMeasurer = rememberTextMeasurer()
val inlineContentId = "custom"
val annotatedString = buildAnnotatedString {
append("自定义布局:")
appendInlineContent(inlineContentId, "[自定义]")
}
val inlineContent = mapOf(
inlineContentId to InlineTextContent(
Placeholder(100.sp, 50.sp)
) {
Box(
Modifier
.size(100.dp, 50.dp)
.background(Color.LightGray)
) {
Text("自定义内容", Modifier.align(Center))
}
}
)
val textLayoutResult = remember(annotatedString, inlineContent) {
textMeasurer.measure(
text = annotatedString,
inlineContent = inlineContent,
maxLines = 1
)
}
Canvas(modifier = Modifier.size(textLayoutResult.size)) {
textLayoutResult.multiParagraph.paint(this)
}
}
11.2 动画与过渡效果
InlineContent支持各种动画效果:
kotlin复制@Composable
fun AnimatedInlineContent() {
val starId = "star"
var animated by remember { mutableStateOf(false) }
LaunchedEffect(Unit) {
while(true) {
delay(1000)
animated = !animated
}
}
val annotatedString = buildAnnotatedString {
append("动画效果:")
appendInlineContent(starId, "[星星]")
}
val inlineContent = mapOf(
starId to InlineTextContent(
Placeholder(24.sp, 24.sp)
) {
val rotation by animateFloatAsState(
targetValue = if(animated) 360f else 0f,
animationSpec = tween(1000)
)
Icon(
Icons.Filled.Star,
null,
tint = Color.Yellow,
modifier = Modifier.rotate(rotation)
)
}
)
BasicText(
text = annotatedString,
inlineContent = inlineContent
)
}
11.3 与Canvas集成
可以在InlineContent中嵌入自定义Canvas绘制:
kotlin复制@Composable
fun CanvasInlineContent() {
val chartId = "chart"
val annotatedString = buildAnnotatedString {
append("数据图表:")
appendInlineContent(chartId, "[图表]")
}
val inlineContent = mapOf(
chartId to InlineTextContent(
Placeholder(120.sp, 60.sp)
) {
Canvas(modifier = Modifier.size(120.dp, 60.dp)) {
val data = listOf(0.3f, 0.6f, 0.9f, 0.5f)
val barWidth = size.width / data.size
data.forEachIndexed { i, value ->
drawRect(
color = Color.Blue,
topLeft = Offset(i * barWidth, size.height * (1 - value)),
size = Size(barWidth * 0.8f, size.height * value)
)
}
}
}
)
BasicText(
text = annotatedString,
inlineContent = inlineContent
)
}