Files
aiagent/docs/Android键盘遮挡修复文档.md
renjianbo 61fd7168f3 feat: Android commercialization finish (30/30), TTS SSML fix, DB restore sync
- android: remaining commercialization tasks, WeChat-style timestamp divider
  (no rules), send auto-scroll, failed-message retry, FeatureGate, avatar
- backend: fix Edge TTS reading SSML tags aloud — edge_tts.Communicate escapes
  input, so custom SSML was spoken as text; now plain text + rate param
  (_build_ssml/_VOICE_STYLE_MAP removed); add custom_presets & push_greetings
  APIs/models; alembic 029-032 + post-restore sync migrations
  1ab200d2d94d/a51ee94d762d with all DROP ops disabled
- frontend: AgentChat/company view updates, TTS markdown stripping
- docs: TTS bugfix record, Android fix docs
- repo hygiene: remove 25 committed tts_outputs mp3 artifacts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-08-09 18:26:02 +08:00

4.2 KiB
Raw Permalink Blame History

Android 软键盘遮挡输入框 — 修复执行文档

日期:2026-08-05 ・ 现象:聊天页弹起软键盘后,底部发送栏被键盘顶边裁掉约 1/3(见需求方截图:chips 条完整露出,输入胶囊下半截压在键盘下面) 执行者:deepseek ・ 预计改动:3 处,共约 15 行


一、根因(已查证,不是猜测)

三处配置叠加导致:

  1. AndroidManifest.xml 没有配 windowSoftInputMode MainActivity 未声明,系统默认行为是 adjustPan(平移而非缩放布局)。

  2. MainActivity.kt:39 用了 enableEdgeToEdge() 边到边模式下系统不再帮你 resize,必须由 Compose 代码自己消费 IME insets。

  3. ui/chat/ChatScreen.kt 全文件没有任何 imePadding()(grep 实证) 新做的底部发送栏(胶囊输入条 + chips 条)没有消费键盘 insets → 键盘弹出时栏体只被 adjustPan 平移了一部分,下半截被键盘盖住。

二、修复点

修复 1:Manifest 声明 adjustResize(必须)

android/app/src/main/AndroidManifest.xml,MainActivity 节点加:

<activity
    android:name=".MainActivity"
    android:windowSoftInputMode="adjustResize"
    ... />

这是 IME insets 能正确分发到 Compose 的前提,缺了它修复 2 在某些机型上仍不生效。

修复 2:底部容器整体加 imePadding(核心)

ChatScreen.kt 的 bottomBar(约 428 行起):把 chips 条 + 停止生成胶囊 + 排队提示 + 输入栏 视为一个整体,在其最外层容器(Column 或 Surface)上加:

Modifier
    .navigationBarsPadding()   // 先吃手势导航条
    .imePadding()              // 再吃键盘;键盘弹出时它会覆盖/包含导航条高度

注意:

  • 顺序不能反(先 navigationBars 后 ime);
  • 加在整个 bottomBar 内容的最外层,不要只加在输入胶囊上——否则键盘弹起时 chips 条会被输入栏顶出重叠;
  • 如果 Scaffold 的 contentWindowInsets 被改过,恢复默认,避免 insets 被 Scaffold 提前消费导致 imePadding 拿不到。

修复 3:键盘弹起时消息列表联动(体验收尾)

bottomBar 被键盘顶高后,Scaffold 的 paddingValues 会变大,LazyColumn 底部留白随之增大——需要让列表保持"贴着最新消息":

// ChatScreen 内,listState 已存在
val imeBottom = WindowInsets.ime.getBottom(LocalDensity.current)
LaunchedEffect(imeBottom) {
    if (imeBottom > 0 && uiState.messages.isNotEmpty()) {
        listState.animateScrollToItem(listState.layoutInfo.totalItemsCount) // 滚到底
    }
}

效果对齐微信/豆包:键盘起来 → 输入栏完整贴键盘上沿 → 消息列表自动滚到最后一条。

不要做的事

  • 不要给 LazyColumn 再加一份 imePadding(bottomBar 顶上来后 contentPadding 已经变大,双份会留白过多);
  • 不要改用 adjustPan 方案糊弄——平移方案在 edge-to-edge 下表现不一致;
  • chips 条的显示/隐藏逻辑(流式时隐藏)不要动。

三、验收清单(真机逐项过)

# 操作 通过标准
1 点输入框弹键盘 输入胶囊完整露出,下沿贴键盘顶边,无裁切
2 键盘弹起瞬间 chips 条随底栏整体上移,不被输入栏覆盖、不与消息区重叠
3 键盘弹起后 消息列表自动滚到最新一条;列表不被键盘遮挡可读
4 收起键盘 底栏回落到导航条上方,无残留空白、无跳动
5 输入 5 行文字 输入框自增高,栏体始终完整在键盘上方
6 手势导航 vs 三键导航两种模式 键盘收起时底栏不被导航条压住
7 流式回复中弹键盘 "停止生成"胶囊与输入栏布局不错乱
8 横屏(如支持) 不崩、不全屏输入态遮挡即可

四、回归风险

  • 改动只涉及 Manifest 一行 + ChatScreen 底部容器修饰符 + 一个 LaunchedEffect,不影响消息流、SSE、语音逻辑;
  • 重点回归:语音"按住 说话"模式(overlay 与键盘互斥——录音 overlay 弹出时若键盘还开着要先收键盘)、图片选择返回后的布局、深色模式。