Appearance
android-expandable-text-demo
1. 实验目标
围绕“第三行尾部接展开更多”这条主线,同时验证 Android 的两套完整实现:
- Compose:
TextMeasurer精确测量 +onTextLayout轻量对照 - Android View:
StaticLayout测量 +Spannable尾部点击,并覆盖四类复杂文本边界
重点不是 API 名字,而是看清:
- 这是一道排版测量题,不是字符串截断题
- 展开态属于低频状态,应由父层或列表宿主持有
- 只要需求要求“尾部紧贴第三行末尾”,默认 ellipsis 就不够
2. 原理与思路
2.1 Compose 为什么优先 TextMeasurer
因为你真正要测的是“正文 + 尾部文案”拼在一起后,第三行还能不能放下。TextMeasurer 可以在真正绘制前先离线测量,适合做二分查找边界。
2.2 Android View 为什么会落到 StaticLayout
TextView 自带 ellipsis 只负责默认省略显示,不会替你管理“展开更多”这个尾部点击区。旧 View 页面里若要精确尾部拼接,通常还是要回到 StaticLayout 做文本布局测量,再配 ClickableSpan。
2.4 Android View 复杂文本边界(四类样例)
View Tab 除主样例外,还单独列出四类边界文本,观察 StaticLayout + ClickableSpan 为何在这些场景更容易出问题:
| 样例 | 风险点 | 为什么 suffix 仍要参与测量 |
|---|---|---|
| 中英文混排 | 字形宽度不一致,不能按字符数截断 | 英文单词与中文段落会改变每行占宽 |
| emoji 混排 | emoji 占宽不等于一个 char | line break 与尾部拼接位置会偏移 |
| 超长单词 | 单词撑破行宽 | suffix 容易被整体挤到下一行 |
| 正文 link span + 尾部 suffix | URLSpan 与尾部 ClickableSpan 并存 | 测量与点击区都要分开处理 |
2.5 方案对比矩阵
| 方案 | 通俗比喻 | 底层原理 | 保证特性 | 吞吐性能 | 运行开销 | 适用场景 |
|---|---|---|---|---|---|---|
| Compose TextMeasurer | 先拿尺子量完整排版再裁 | 离线排版测量 + 二分边界 | 尾部拼接精确 | 中高 | 中 | Compose 卡片、详情页、富文本摘要 |
| Compose onTextLayout | 先画一次再看溢没溢出 | 布局回调读行数与 overflow | 轻量判断溢出 | 高 | 低 | 只决定是否显示“展开”按钮 |
| Android View StaticLayout | 旧体系里自己量版心 | StaticLayout + span 点击 | 旧页里仍可精确实现 | 中 | 中 | RecyclerView 卡片、老页面维护 |
| Flutter TextPainter | 先测候选文本再展示 | TextPainter.layout() | 尾部拼接精确 | 中高 | 中 | Flutter 列表/详情页主方案 |
3. Android / 移动端实战场景与误配事故
3.1 实战场景
- 信息流卡片简介
- 商品详情摘要
- 评论区“展开全文”
- FAQ / 富文本问答摘要
3.2 常见误配与事故注意事项
- 直接按字符数截断
- 尾部按钮事后硬拼,结果跑到第四行
- 列表项自己偷偷记
expanded,复用后串项 - 长列表中每次 rebuild 都重新测量全文
3.3 排障思路
- 先确认需求是不是“尾部要精确贴第三行末尾”
- 再确认宽度、字体、样式、suffix 是否都参与测量
- 再确认展开态是不是由父层按 itemId 管
- 最后才优化缓存与性能
4. 实验源码与运行验证
关键源码
关键参数 / 开关
collapsedMaxLines:收起时最多显示几行suffix:尾部文案,参与真实测量Compose / ViewTab:切换两种实现列表状态提升开关:演示父层 hoist 后的稳定行为- View Tab 四类边界样例:中英混排 / emoji / 超长单词 / link span
运行方式
bash
cd labs/android/host
./gradlew assembleDebug1
2
2
安装后打开 Android Host → 打开 Android 文本测量与展开更多实验室。
预期现象
- 文本不足 3 行时不出现“展开更多”
- 文本超过 3 行时,Compose 与 View 版都会把尾部文案贴在第三行末尾
- View Tab 四类边界样例中,link span 正文链接与尾部 suffix 点击区互不干扰
- 切换列表项展开状态时,父层状态提升方案不会串项
5. 对应知识库文档
- 理论主文档:Jetpack Compose 文本测量、自定义截断与“展开更多”
- 状态分层总览:状态管理、路由、架构