Jetpack Compose中的富文本输入

0 阅读4分钟

本文译自「Rich Content in Text Input in Jetpack Compose」,原文链接medium.com/proandroidd…,由Oğuzhan Aslan发布于2026年3月23日。

引言

在当今的移动环境中,文本输入不再仅仅局限于文本。用户已经习惯了丰富且富有表现力的沟通方式,而不仅仅局限于简单的字符。无论是发送有趣的 GIF、分享贴纸,还是从其他应用拖放图片,富文本支持都已成为用户的默认期望。

大多数流行的即时通讯和社交媒体应用都能无缝支持这些功能。然而,对于 Android 开发者来说,支持这种“默认”行为需要特定的实现步骤。标准的文本字段无法自动处理从 Gboard 发送的 GIF 或从剪贴板粘贴的图片。

“默认”体验(以及它为何失败)

如果你只是将一个 TextField 拖放到 Compose UI 中,它就能完美地处理文本。但是,如果你尝试从键盘插入 GIF 或粘贴图片,却发现……没有任何反应。运气好的话,键盘可能会提交一个 URI 字符串;但更常见的情况是,输入会被直接忽略,因为文本字段只接受文本。

1.gif

为了解决这个问题,Jetpack Compose 引入了一个强大的修饰符:contentReceiver

解决方案:contentReceiver

contentReceiver 修饰符是一个统一的 API,旨在处理来自多个来源的富文本内容:

  1. 软键盘:来自 Gboard 等应用的 GIF 和贴纸。

  2. 剪贴板:粘贴的图片或富文本。

  3. 拖放:从其他应用(在分屏模式下)或同一应用内拖拽的内容。

contentReceiver 为我们提供了一个单一的入口点来处理所有 TransferableContent,而无需为键盘管理 OnCommitContentListener,也无需为拖放监听器单独管理。

逐步实现

让我们构建一个可以接收和显示图片的文本输入区域。

步骤 1:ViewModel 逻辑

首先,我们需要一种方法来处理传入的内容。我们将创建一个 RichContentViewModel 来处理 TransferableContent。核心逻辑涉及使用 .consume 函数,该函数允许我们提取可以处理的内容部分(例如图片 URI),并返回其余部分(如果已处理完所有内容,则返回 null)。

class RichContentViewModel : ViewModel() {  
    // State to hold our received images  
    var selectedImages by mutableStateOf<List<Uri>>(emptyList())  
  
    @OptIn(ExperimentalFoundationApi::class)  
    fun handleContent(  
        transferableContent: TransferableContent  
    ): TransferableContent? {  
        val newUris = mutableListOf<Uri>()  
          
        // consume returns the parts of the content we didn't use.  
        // inside the lambda, we return true if we consumed the item.  
        val remaining = transferableContent.consume { item ->  
            // If the item has a URI, we take it  
            if (item.uri != null) {  
                newUris += item.uri  
                true // Consumed  
            } else {  
                false // Not consumed, leave it for other handlers  
            }  
        }  
          
        // Update our state with the new images  
        selectedImages = newUris  
          
        return remaining  
    }  
}

步骤 2:UI 实现

现在,让我们将其应用到 UI 中。contentReceiver 的一个关键优势是灵活性。我们不必将其直接应用于 TextField。通过将其应用于父容器(例如 Column),我们可以将整个区域转换为拖放操作的“放置区”,从而改善用户体验。

以下是我们的 RichContentTextField 可组合组件:

@OptIn(ExperimentalFoundationApi::class)  
@Composable  
fun RichContentTextField(  
    richContentViewModel: RichContentViewModel = viewModel()  
) {  
    Column(  
        modifier = Modifier  
            .fillMaxWidth()  
            .border(1.dp, color = Color.Black)  
            // Attach the content receiver here!  
            // This delegates the handling logic to our ViewModel  
            .contentReceiver(richContentViewModel::handleContent)  
    ) {  
        // 1. Display received images  
        LazyRow(  
            modifier = Modifier.fillMaxWidth()  
        ) {  
            items(richContentViewModel.selectedImages) { imageUri ->  
                AsyncImage(  
                    model = imageUri,  
                    contentDescription = null,  
                    modifier = Modifier  
                        .size(64.dp)  
                        .padding(end = 4.dp)  
                        .clip(RoundedCornerShape(8.dp)),  
                    contentScale = ContentScale.Crop  
                )  
            }  
        }  
        // 2. The actual text input  
        val count = rememberTextFieldState()  
          
        // Optional: Update text state based on images (just for demo purposes)  
        LaunchedEffect(richContentViewModel.selectedImages) {  
            if (richContentViewModel.selectedImages.isNotEmpty()) {  
                 count.setTextAndPlaceCursorAtEnd(richContentViewModel.selectedImages.size.toString())  
            }  
        }  
        TextField(  
            state = count,  
            placeholder = { Text("Rich Content Field") },  
        )  
    }  
}

代码要点:

  1. **contentReceiver** 在父级上:我们将修饰符放在了 Column 上。这意味着如果用户将图像拖到此框中的任何位置,它都会被捕获。

  2. **AsyncImage**:我们使用 Coil 的 AsyncImage 来渲染提取的 URI。

  3. 关注点分离:UI 只负责显示状态(selectedImages),而 ViewModel 则处理 TransferableContent 的复杂性。

结果

只需几行代码,我们就将一个基本的文本输入框转换成了一个富媒体接收器。用户现在可以从键盘插入 GIF 动画,或者直接将照片拖到应用中。

2.gif

结论

支持富媒体内容不再是“额外”功能,而是现代 Android 应用开发的核心组成部分。Jetpack Compose 中的 contentReceiver 修饰符极大地简化了这一过程,将原本三个独立的 API 统一到一个简洁的声明式接口中。通过实现这一点,你可以确保你的应用拥有原生应用般的体验,并且能够响应用户如今的实际交互方式。

欢迎搜索并关注 公众号「稀有猿诉」 获取更多的优质文章!

保护原创,请勿转载!