Android Compose聊天应用添加表情面板的实现问题
在Android Compose聊天应用中集成表情面板
1. 依赖配置
首先在模块级build.gradle(或build.gradle.kts)中添加vanniktech.emoji库依赖:
// 替换为最新稳定版本 implementation "com.vanniktech:emoji-google:0.16.0"
2. 改造输入框:嵌入原生EditText
由于vanniktech.emoji依赖View系统的EditText,我们需要在Compose中用AndroidView包裹原生EditText,同时对齐原Compose TextField的样式:
@Composable fun ChatEditText( text: String, onValueChange: (String) -> Unit, onEmojiIconClick: () -> Unit ) { Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier.width(248.dp) ) { AndroidView( factory = { context -> EditText(context).apply { tag = "chat_edit_text" // 匹配原Compose TextField样式 setBackgroundColor(Color(0xFFF2F2F2).toArgb()) hint = "Type a message" setHintTextColor(Color.Gray.toArgb()) maxLines = 10 // 设置圆角背景(可通过drawable或代码实现) background = GradientDrawable().apply { shape = GradientDrawable.RECTANGLE cornerRadius = 40.dp.toPx(context).toFloat() setColor(Color(0xFFF2F2F2).toArgb()) } // 同步输入内容到Compose状态 addTextChangedListener(object : TextWatcher { override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {} override fun afterTextChanged(s: Editable?) { s?.toString()?.let(onValueChange) } }) } }, update = { editText -> if (editText.text.toString() != text) { editText.setText(text) } }, modifier = Modifier.weight(1f) ) // 表情切换图标 Icon( painter = painterResource(id = R.drawable.emoji), contentDescription = "Insert emoticon", tint = Color(0xff808080), modifier = Modifier .padding(start = 8.dp) .clickable { onEmojiIconClick() } ) } } // 扩展函数:dp转px fun Dp.toPx(context: Context): Float = this.value * context.resources.displayMetrics.density
3. 创建并管理EmojiPopup
在ChatScreen中,通过LocalView.current获取根View,创建EmojiPopup实例并处理面板显示/隐藏、表情插入逻辑:
@Composable fun ChatScreen() { var messageText by remember { mutableStateOf("") } val context = LocalContext.current val rootView = LocalView.current var emojiPanelHeight by remember { mutableStateOf(0.dp) } val emojiPopup = remember { EmojiPopup.Builder.fromRootView(rootView) .setOnEmojiClickListener { emoji -> val editText = rootView.findViewWithTag<EditText>("chat_edit_text") editText?.let { et -> val cursorPos = et.selectionStart et.text.insert(cursorPos, emoji.unicode) et.setSelection(cursorPos + emoji.unicode.length) } } .setOnEmojiPopupShownListener { height -> emojiPanelHeight = height.dp } .setOnEmojiPopupDismissListener { emojiPanelHeight = 0.dp // 面板关闭后恢复软键盘(可选) val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager rootView.findViewWithTag<EditText>("chat_edit_text")?.let { imm.showSoftInput(it, 0) } } .build() } Column( modifier = Modifier.fillMaxSize(), verticalArrangement = Arrangement.Bottom ) { // 聊天消息列表 LazyColumn( modifier = Modifier.weight(1f), contentPadding = PaddingValues( top = 16.dp, start = 16.dp, end = 16.dp, bottom = 16.dp + emojiPanelHeight ) ) { // 替换为你的消息列表逻辑 items(emptyList<ChatMessage>()) { message -> if (message.isSentByMe) { SentMessageItem(message) } else { ReceivedMessageItem(message) } } } // 输入区域 Row( modifier = Modifier.padding(8.dp), verticalAlignment = Alignment.CenterVertically ) { ChatEditText( text = messageText, onValueChange = { messageText = it }, onEmojiIconClick = { val editText = rootView.findViewWithTag<EditText>("chat_edit_text") editText?.let { et -> val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager if (emojiPopup.isShowing) { emojiPopup.dismiss() imm.showSoftInput(et, 0) } else { imm.hideSoftInputFromWindow(et.windowToken, 0) emojiPopup.show() } } } ) } } } // 示例消息数据类 data class ChatMessage(val content: String, val isSentByMe: Boolean) @Composable fun SentMessageItem(message: ChatMessage) {} @Composable fun ReceivedMessageItem(message: ChatMessage) {}
4. 核心逻辑说明
- 通过
AndroidView桥接原生EditText,解决Compose TextField无法直接适配View系表情库的问题 - 利用
tag快速定位EditText实例,实现表情插入和软键盘切换 - 监听EmojiPopup的显示高度,动态调整LazyColumn的底部padding,避免消息列表被面板遮挡
内容的提问来源于stack exchange,提问作者Olaleye Paul Tobi
相关产品推荐
相关产品推荐

