feat(compose): 文本虚线 drawBehind + PathEffect 官方写法对齐(三端通用化) - #1572
feat(compose): 文本虚线 drawBehind + PathEffect 官方写法对齐(三端通用化)#1572nikazhao wants to merge 9 commits into
Conversation
- KuiklyCanvas: dash 参数下沉 CanvasContext.setLineDash + 非法输入(空/全零区间)降级实线 + 显式复位防上一帧残留 - DrawModifier: drawBehind 用 snapshotObserver.observeReads 包裹, snapshot state 变化触发重绘 - PathEffect/DashPathEffect: dash 值对象(intervals/phase), intervals 走 contentEquals - TextLayoutResult/MultiParagraph: 行度量 getter; KRRichTextView 行度量桥接(lineMetrics/getBoundingBox) - demo: DashedUnderlineDemo 官方 5 场景对齐 + ComposeAllSample 注册入口 - test: 新增 PathEffectTest 6 个 commonTest 回归用例(全过) 验证: :core-render-android:assembleDebug / :compose:testDebugUnitTest(6 passed,0 failed) / :demo:compileDebugKotlinAndroid 均 BUILD SUCCESSFUL
drawBehind 的 DrawScope.size 现在严格等于组件自身布局尺寸,不再对 文本宿主额外加 4dp 底部留白。下划线是否落在文字下方由调用方在 lambda 内自行决定绘制 y 坐标(如 size.height),框架不再替宿主 添加偏移补丁。 经 DashedUnderlineDemo 验证,移除补丁后文本下划线仍不压字, 与各组件背景贴边表现一致。
- 新增 DrawBehindDemo.kt:覆盖 Box/Row/Column/Image/LazyColumn item 五类组件的 drawBehind 用法,
并对比实线、虚线 8-4、虚线 16-8、点线 2-4 四种 PathEffect 效果
- ComposeAllSample.kt 注册 DemoItem("DrawBehind", ...)
- 仅改动 demo 模块,未引入 core/compose 之外的依赖,符合最小改动与模块边界
- iOS KRRichTextView: hrv_callWithMethod 新增 lineMetrics/getBoundingBox, 复用 NSLayoutManager,严格解析参数、越界返回空矩形(pt 口径,与现有 spanRect 一致)。 - OHOS KRRichTextShadow: Call 新增 lineMetrics/getBoundingBox, 复用 context 线程 typography 并先拷强引用避免跨线程竞争,/dpi 转 dp。 - DashedUnderlineDemo: 场景2/4 onTextLayout 几何读取推后一帧(OHOS 首帧稳定), 加越界防御,移除本地路径注释。 三端(Android/iOS/OHOS)场景 1/3/5 + 2/4 跑通,callMethod 返回格式与 Android 对齐。
范围:drawBehind + pathEffect 二轮收敛,不涉其他能力。
## Native / render 桥接对齐(三端)
- core-render-ohos/KRRichTextShadow.cpp
* BoundingBox 补 offset 越界钳制:先扫 lineInfo 得 textLen,
safeOffset = clamp(offset, 0, textLen-1),与 Android(coerceIn)/iOS(>=textLen return)
保持一致,避免越界喂给 OH_Drawing_TypographyGetRectsForRange 引发 native crash / 脏数据。
* lineMetrics 返回格式补 start/end offset:"N top0 bottom0 start0 end0 ...",
与 Android 对齐;BoundingBox 换用 TIGHT/TIGHT 避免行末跨行膨胀。
- core-render-ios/KRRichTextView.m
* css_lineMetrics/css_boundingBoxWithParams 补齐并对齐格式。
- core-render-android/KRRichTextView.kt
* getLineMetrics/getCharBoundingBox 补齐并对齐;跨行时用 primaryHorizontal + 行边界回退,
避免 getSelectionPath 把跨行选区扩展成整行盒。
## Compose 层 API 对齐
- MultiParagraph / TextLayoutResult
* 补齐 getLineTop/getLineBottom/getLineStart/getLineEnd/getBoundingBox;
getBoundingBox 空安全(fn null → Rect(0,0,0,0)),getLineXxx 全部 getOrElse 防越界。
- TextStringRichNode
* pageDensity 空安全(fallback to requireDensity().density),避免 !! 强解包。
* 兼容新旧 lineMetrics 返回格式;getBoundingBoxFn 走 callMethod('getBoundingBox', ...)。
* 使用 AnnotatedString 时跳过整段 textDecoration,避免覆盖 span 级 dashed 装饰。
## Demo
- 新增 DrawBehindDemo(合并原 DashedUnderlineDemo,删除后者):
统一验证 Text / Box / Image / Row / Column / BasicTextField / TextField / Switch / Slider / Checkbox
等宿主组件挂 drawBehind + pathEffect 的稳定性;文案改书面语,去掉过期底色说明与
'本期不继续追' 冗余表述。
- ComposeAllSample 注册 DrawBehind 入口。
## 工程与本地环境
- ohosApp/build-profile.json5 转本地不入库(DevEco 自动签名密文只在本地)。
- .gitignore 同步新增 ohosApp/build-profile.json5 / .hvigor/ / .vscode/ / .workbuddy/ / .logs_pid / /scripts/ 忽略。
- iosApp/project.pbxproj:Xcode 自动删除两处空 outputPaths = ()(无害规范化)。
## CR 与验证
- 用 kuikly_lint 三轮回归,compose 层 3 文件 0 issue;OHOS native hvigorw compileNative BUILD SUCCESSFUL。
- Android/iOS/OHOS 三端 DrawBehindDemo 已跑通并出截图,安装/启动/渲染正常,无崩溃。
- 单测 PathEffectTest.kt 6 个用例覆盖 dashPathEffect 工厂 + DashPathEffect 语义。
- 🔴 High = 0(OHOS 越界钳制已补齐);范围仅含本任务改动;无 'mentor' 字样。
…rawbehind-pathEffect-pr_2 # Conflicts: # compose/build.2.1.21.gradle.kts
KuiklyAI
left a comment
There was a problem hiding this comment.
🤖 KuiklyAI Code Review
基本信息
- PR:#1572 · feat(compose): 文本虚线 drawBehind + PathEffect 官方写法对齐(三端通用化)
- 作者:nikazhao main ← feat/kuikly-compose-drawbehind-pathEffect-pr_2
变更概述
把 Modifier.drawBehind 与 PathEffect.dashPathEffect 打通为通用文本虚线能力,覆盖 Android / iOS / OHOS 三端。Compose 侧新增 PathEffect 值对象、给 Paint 加 pathEffect、KuiklyCanvas.drawLine 下沉 setLineDash(含空/全零降级实线 + 显式清空防残留)、MultiParagraph/TextLayoutResult 补 getLineTop/getLineBottom/getLineStart/getLineEnd/getBoundingBox;native 侧三端 RichTextView 新增 lineMetrics / getBoundingBox 桥接;附 DrawBehindDemo 与 PathEffectTest。
方案整体在正确层级(compose DSL + 各 render 层桥接),未混用两种 DSL,模块边界清晰。二进制兼容方面 MultiParagraph 新增构造参数均带默认值,CoreTextField.kt 的 MultiParagraph(lineCount, placeholderRects) 旧调用仍可编译;Paint.pathEffect 用默认 no-op getter/setter 保持对既有实现的源码兼容——这些都 OK。
主要问题(建议处理)
- OHOS
endIndex语义与既有代码冲突(最需要确认):新代码按 inclusive 语义对endIndex做+1并用safeOffset <= endIndex定位行,但同族的KRRichTextView.cpp:714用的是index < endIndex、:471用info.end = endIndex,都把它当 exclusive。若 SDK 实际是 exclusive(与既有用法、AndroidgetLineEnd/iOSNSMaxRange对齐方向一致),+1会让 lineEnd 偏 1、textLen大 1,进而让safeOffset上界放宽一格、给OH_Drawing_TypographyGetRectsForRange喂越界 offset。请对照drawing_text_typography.h头文件里OH_Drawing_LineMetrics.endIndex的定义确认到底是 inclusive 还是 exclusive,再决定要不要+1/<=。 - brush 重载的
pathEffect被静默丢弃:configureStrokePaint(brush, ...)收了pathEffect参数却没赋给 paint(应用那行仍是注释),和 color 重载不一致。drawLine(brush, …, pathEffect = dash)不会出虚线。 - 跨 shape 的 dash 状态残留:
drawLine里setLineDash了,但drawRect/drawPath/fillOrStroke的 stroke 路径没复位,同一帧同一 canvas 上先画虚线再画实线 stroke 形状时,后画的矩形/路径会继承上一条的 dash。建议在 stroke 路径里按 paint.pathEffect 同样兜底清空。 - 整段
style.textDecoration在 AnnotatedString 下被丢弃:if (annotatedText == null)的守卫让Text(annotatedString, textDecoration = Underline)不再下发整段下划线。保护 span 级 dashed 装饰的出发点合理,但代价是丢了整段装饰继承——若官方语义里未显式设装饰的 span 应继承顶层 TextStyle,这里会偏。建议确认是否需要 merge 而非二选一。
其他
ohosApp/build-profile.json5整文件删除 + 入 .gitignore:该文件里products/compatibleSdkVersion/modules是非签名的通用配置,OHOS 构建参考文档里grep signingConfigs ohosApp/build-profile.json5是前置步骤,新克隆可能缺文件。建议保留文件(signingConfigs: []留空)只忽略签名相关本地改动,或补一个生成说明。DrawModifier.drawIntoBackgroundCanvasView的 kdoc 仍写“含 4dp 底部 padding”“z-order 由 drawInto 的 4dp padding 视觉处理”,但bottomPadDp = 0f、padding 已移除,注释与实现不符,建议同步改掉。- iOS
p_createPlaceholderSpanAttributedStringWithSpan删掉了NSWritingDirectionAttributeName(强制 LTR override),这是与虚线无关的行为变更,确认下是有意为之(对 RTL 占位符可能影响)。
文档
docs/ 目前未收录 drawBehind / PathEffect.dashPathEffect / TextLayoutResult 行度量 API。这些是新增的公共 Compose DSL 能力,建议后续补一节绘制/文本度量文档(setLineDash 在 docs/API/components/canvas.md 已有,native 侧无影响)。
整体方向和分层我都认可,三端桥接也基本对齐,上面几条主要是兼容性语义和跨 shape 一致性,确认后即可。
| result += " " + std::to_string(top); | ||
| result += " " + std::to_string(bottom); | ||
| result += " " + std::to_string(lineMetrics.startIndex); | ||
| result += " " + std::to_string(lineMetrics.endIndex + 1); |
There was a problem hiding this comment.
这里对 endIndex 做 +1、并在 BoundingBox 里用 safeOffset <= endIndex 定位行,前提是 OH_Drawing_LineMetrics.endIndex 是 inclusive。但同族的 KRRichTextView.cpp:714 用的是 index < line_info.line_metrics_.endIndex、:471 用 info.end = ... endIndex,都把它当 exclusive。如果 SDK 实际是 exclusive(这和 Android Layout.getLineEnd、iOS NSMaxRange 都是 exclusive 的对齐方向一致),+1 会让 lineEnd 偏 1、textLen 比 true length 大 1,进而让 safeOffset 上界放宽一格,给 OH_Drawing_TypographyGetRectsForRange(safeOffset, safeOffset+1) 喂越界 offset。建议对照 drawing_text_typography.h 里 endIndex 的定义确认语义后再定 +1 / <=,避免和既有 KRRichTextView.cpp 用法打架。
There was a problem hiding this comment.
已解决:endIndex 的 +1 已去掉,LineMetrics 直接输出 endIndex,注释同步成 end 为 exclusive,与 KRRichTextView.cpp 的 index < endIndex / info.end = endIndex 对齐。
| for (size_t i = 0; i < lineCount; ++i) { | ||
| OH_Drawing_LineMetrics lm; | ||
| OH_Drawing_TypographyGetLineInfo(typo_raw, i, true, true, &lm); | ||
| int end = static_cast<int>(lm.endIndex) + 1; // endIndex 与 LineMetrics 保持 inclusive 语义 |
There was a problem hiding this comment.
和上一条同源:这里 end = endIndex + 1 作为 textLen 上界,注释写“endIndex 与 LineMetrics 保持 inclusive 语义”。若实际是 exclusive,textLen 会比真实长度大 1,safeOffset 能取到真实文本长度那一格(越界),后续 GetRectsForRange(safeOffset, safeOffset+1) 即越界。同样建议先拿 SDK 头文件确认 endIndex 到底 inclusive 还是 exclusive,三端(Android/iOS 都是 exclusive)统一到一边。
There was a problem hiding this comment.
已解决:textLen 上界改为 end = endIndex(去掉 +1),行定位改回 safeOffset < endIndex。越界钳制用 textLen-1,和 Android coerceIn / iOS 语义统一,不会再给 GetRectsForRange 喂越界 offset。
| cap: StrokeCap, | ||
| join: StrokeJoin, | ||
| // pathEffect: PathEffect?, | ||
| pathEffect: PathEffect?, |
There was a problem hiding this comment.
brush 重载这里把 pathEffect 参数加回来了,但下面 obtainStrokePaint().apply { ... } 里给 paint 赋值的那行还是注释状态(// if (this.pathEffect != pathEffect) this.pathEffect = pathEffect),参数收了却没用。结果 drawLine(brush, …, pathEffect = dash) 之类走 brush 的入口不会把 dash 下发,和 color 重载行为不一致。要么把这行打开,要么直接去掉这个参数避免误导调用方。
There was a problem hiding this comment.
部分处理:color 重载那行已经打开(this.pathEffect = pathEffect 生效了),brush 重载这次按 commit message 声明暂不动(属未来增强,需三端 shader+dash 验证)。brush 重载里 pathEffect 参数仍在签名里但赋值行还是注释,调用方传 dash 不会生效——这点留意下,后续启用时一并打开或先去掉参数避免误导。
| setLineDash(intervals.map { it / densityValue }.toList()) | ||
| } | ||
| } else { | ||
| setLineDash(emptyList()) // 显式清空,防止上一帧残留 |
There was a problem hiding this comment.
drawLine 这里 setLineDash 了(含实线清空),但 drawRect / drawPath / fillOrStroke 走 stroke 时并没有按 paint.pathEffect 复位 dash。同一帧同一 canvas 上先画一条虚线、再画一个实线 stroke 的矩形/路径时,后者会继承前一条的 dash pattern(OHOS pen_ 上 OH_Drawing_PenSetPathEffect 是持久的,Android currentDrawStyle.lineDash 也是持久的,跨帧靠 reset() 清、但帧内不会清)。建议在 stroke 路径里也按 paint.pathEffect 兜底 setLineDash(emptyList()),保证每个 shape 自洽。
There was a problem hiding this comment.
已解决:fillOrStroke 开头补了 setLineDash(emptyList()),每个 stroke 图形进来先清 dash,不会再继承同一 lambda 内上一笔 drawLine 的虚线 pattern。
| attr.applyTextDecoration(style.textDecoration) | ||
| // 当使用 AnnotatedString 时,textDecoration 由各 span 自行下发, | ||
| // 这里跳过整段下发,避免覆盖 span 级别的 dashed 装饰。 | ||
| if (annotatedText == null) { |
There was a problem hiding this comment.
用 annotatedText == null 守卫后,Text(annotatedString, style = TextStyle(textDecoration = Underline)) 这类整段下划线就不会下发了——只有显式在 spanStyle 里设了 textDecoration 的 span 才有装饰。保护 span 级 dashed 装饰不被整段覆盖的出发点能理解,但代价是丢了顶层 TextStyle.textDecoration 的继承(官方语义里未显式覆盖装饰的 span 应继承顶层)。如果只是怕整段装饰盖掉 span 级 dashed,可以考虑只在有 span 级装饰冲突时跳过,而不是 annotatedText 一非空就整体跳过,确认下预期行为?
There was a problem hiding this comment.
已解决:if (annotatedText == null) 守卫去掉了,恢复 applyTextDecoration(style.textDecoration) 整段下发。顶层 TextStyle.textDecoration 的继承回来了,span 级装饰仍由 applyAnnotatedString 在各 span 自行下发覆盖。
| } | ||
|
|
||
| /** | ||
| * 把背景 CanvasView 定位到宿主同帧(含 4dp 底部 padding),并用 KuiklyCanvas + |
There was a problem hiding this comment.
kdoc 还写着“含 4dp 底部 padding”“z-order 由 drawInto 的 4dp padding 视觉处理”,但下面 bottomPadDp = 0f、padding 已经在二轮打磨里移除了。注释和实现不符,后面接手的人容易被带偏,顺手改一下。
There was a problem hiding this comment.
已解决:4dp padding 死注释清掉、bottomPadDp = 0f 死变量删除,类 kdoc 补了 z 序限制说明(bg 作为宿主兄弟节点插到父容器 index 0,兄弟重叠场景与官方 z=-1 语义有偏差)。
| .mcp.json | ||
|
|
||
| ## OHOS local build config (regenerated on every build, contains local signing paths) | ||
| ohosApp/build-profile.json5 |
There was a problem hiding this comment.
ohosApp/build-profile.json5 这个文件里除了本地签名,还带了 products / compatibleSdkVersion / modules(entry + render)这些通用配置,整文件删掉入 .gitignore 后,新克隆的仓库没有这个文件。而 OHOS 构建参考文档里 grep signingConfigs ohosApp/build-profile.json5 是构建前置步骤、hvigorw 也依赖它存在。建议保留文件入库(signingConfigs: [] 留空),只把签名相关本地字段忽略掉,或补一行说明从哪生成,避免别人 clone 下来 OHOS 起不来。
There was a problem hiding this comment.
已解决:ohosApp/build-profile.json5 重新入库(空 signingConfigs 版本,与 origin/main 一致),.gitignore 里对应忽略项移除,本地含密字段用 skip-worktree 保护。新克隆仓库 OHOS 构建链路不再缺文件。
| } | ||
|
|
||
| NSAttributedString *attrString = [NSAttributedString attributedStringWithAttachment:attachment]; | ||
| NSMutableAttributedString *mutableAttrString = [[NSMutableAttributedString alloc] initWithAttributedString:attrString]; |
There was a problem hiding this comment.
这里把 placeholder attachment 上的 NSWritingDirectionAttributeName(强制 LTR override)删掉了。和虚线能力不直接相关,属于行为变更:之前占位符在 RTL 文本里也被强制 LTR,删掉后会跟随段落方向。如果是有意改的(对齐官方/修 RTL),建议在 commit message 里单独说一句;如果只是顺手删的,留意下 RTL 场景的 placeholder 表现。
There was a problem hiding this comment.
已解决:占位符 span 的 NSWritingDirectionAttributeName LTR override 加回来了,恢复前次 commit 误删的行为。
KuiklyAI
left a comment
There was a problem hiding this comment.
🤖 KuiklyAI Code Review
增量评估(本轮 = merge commit 8323a37a)
本轮最新提交是把 origin/main(5cf4bea8,含 macOS 平台支持 + LazyList prefetch 重构)合并进 PR 分支,自身不包含业务逻辑改动。
上轮历史评论盘点:之前的 8 条 inline 全部落在 PR 的业务文件上(KRRichTextShadow.cpp 的 endIndex + 1 inclusive/exclusive 语义、CanvasDrawScope.kt brush 重载 pathEffect 未下发、KuiklyCanvas.kt drawRect/drawPath 帧内 dash 残留、TextStringRichNode.kt 整段 textDecoration 跳过、DrawModifier.kt kdoc 与 bottomPadDp = 0f 不符、.gitignore 删 ohosApp/build-profile.json5、iOS NSWritingDirectionAttributeName 删除)。本次 merge diff 与这些文件零交集,所以这 8 条均未在本轮改动中处理,保持原样。
其中 KRRichTextShadow.cpp 那两条(endIndex + 1)我顺着既有代码再核对了一遍:同文件族 KRRichTextView.cpp:714 用 index < endIndex 遍历、:471 用 endIndex 作整段 info.end,都把 endIndex 当 exclusive 用,和 Android Layout.getLineEnd、iOS NSMaxRange 的 exclusive 方向一致。这条仍然建议在合入前对照 OH_Drawing 头文件确认语义后定 +1/<=,否则 textLen 会比真实长度大 1、safeOffset 上界放宽一格,给 GetRectsForRange 喂越界 offset。
本轮新改动风险评估:
- 冲突文件
compose/build.2.1.21.gradle.kts解析干净,无冲突标记残留;引入的runtimeLegacy/runtime19源集分流是叠加在commonMain之上(dependsOn(commonMain)),不会把 commonMain 排除。 - PR 的全部业务文件都在
commonMain、单测在commonTest,所依赖的snapshotObserver/observeReads/requireOwner/DrawModifierNode等 API 定义在commonMain(Owner.kt/RootNodeOwner.kt),未被 runtime19/legacy 分流,drawBehind 重绘链路不受影响。 - main 对
SubcomposeLayout的 pausable composition 重构集中在 LazyList prefetch 路径,与 PR 走DrawModifierNode.invalidateDraw+snapshotObserver.observeReads的 draw 链路无交互。
本轮 merge 本身没有引入需要 inline 的新问题。一个小提醒:作者在最新 commit message 里没提合并 main 后的编译/测试验证状态,而 main 这次带进来的 LazyList prefetch 改动量不小(新增 ~7000 行、多个源集),建议补一次合并后三端编译 + :compose:testDebugUnitTest(含 PathEffectTest)确认无回归再合入。
KuiklyAI
left a comment
There was a problem hiding this comment.
🤖 KuiklyAI Code Review(增量)
这一笔是 CR 收敛 commit,逐条盘点上轮 8 条 inline 评论的处置情况:
- OHOS
endIndex语义(2 条):✅ 已改回 exclusive,去掉 +1、<=改<,与KRRichTextView.cpp的index < endIndex/info.end = endIndex及 Android/iOS 对齐,越界钳制回归正确。 drawLinedash 残留串色:✅fillOrStroke开头补setLineDash(emptyList()),同 lambda 内先虚线再描边图形不再串色。annotatedText == null守卫丢失顶层 TextDecoration:✅ 守卫去掉,整段下划线恢复,span 级装饰仍走applyAnnotatedString。- DrawModifier 4dp padding 死注释/死变量:✅ 清理完毕,并补了 z 序限制 kdoc。
build-profile.json5误入 .gitignore:✅ 文件重新入库(空 signingConfigs,与 origin/main 一致),本地含密字段用 skip-worktree 保护。- iOS placeholder
NSWritingDirectionAttributeName误删:✅ 已恢复。 - brush 重载
pathEffect参数收而不用:⚠️ color 重载那行已打开生效,brush 重载按 commit 声明暂缓(未来增强);签名仍带该参数但赋值行仍是注释,调用方传 dash 不会生效,后续启用时一并打开或先去参数避免误导。
这一笔没有新引入风险:三端 native 桥接、Compose API、demo/工程文件均在合理范围内,注释与实现已对齐。整体看 PR 已收敛,可以合入。
三份 CR 报告(hy3 / kimi3 / opus48)共性问题及 opus48 补充意见收敛:
## 逻辑修复
- OHOS KRRichTextShadow: endIndex 改回 exclusive(+1 去掉、<= 改 <),与
KRRichTextView / Android getLineEnd / iOS NSMaxRange / Skia 血统对齐。
- KuiklyCanvas.fillOrStroke 开头补 setLineDash(emptyList()),避免同 lambda
内 "先虚线 drawLine → 再描边图形" 状态残留串色。
- TextStringRichNode 去掉 if (annotatedText == null) 守卫(已废路线 C 遗留),
恢复 Text(AnnotatedString, textDecoration = Underline) 整段下划线。
- DrawModifier.ensureBackgroundCanvasView 引入 addedToParent 标志,catch
分支回滚已挂 bg,防止 addChild 成功但后续步骤抛异常时的 view 泄漏。
## 超范围回归恢复
- iOS KRRichTextView.m 恢复占位符 span 的 NSWritingDirectionAttributeName
LTR override(前次 commit 顺带删除,未在 message 中说明)。
- ohosApp/build-profile.json5 加回 origin/main 空 signingConfigs 版本;
.gitignore 移除对应忽略项;本地含密文件用 skip-worktree 保护。
## 注释 / 规范
- 头文件 KRRichTextShadow.h 的 LineMetrics 注释同步为 .cpp 实际返回格式
("N top0 bottom0 start0 end0 ...")。
- 清排期黑话:D2/D3 / Phase 2 / Phase 2 Step 1 spike / E3 完备性不足
/ scene6 / scene7 / Phase 2: PostProcessor(其中最后一处为老代码顺带
清理,因用户明确要求同步清零遗留黑话)。
- DrawModifier 清 "4dp padding" 死注释与 bottomPadDp = 0f 死变量。
- .gitignore 移除个人噪音 .logs_pid / .workbuddy/ / /scripts/,保留通用
工程配置 .vscode/ / .hvigor/。
## 已知限制注释(防下游误判)
- PathEffect.DashPathEffect 加 phase 当前不生效说明(setLineDash 协议
未开放 phase 位,iOS 硬编码 0)。
- DrawBackgroundModifier 类 kdoc 补 z 序限制:bg 作为宿主兄弟节点插到
父容器 index 0,与官方 "宿主内部 z=-1 层" 语义在兄弟重叠场景有偏差。
- MultiParagraph.getLineEnd fallback 补注释:旧格式兼容路径最后一行
退化为空区间是有意为之。
## 不含改动
- brush 重载 pathEffect(未来增强项,需三端 shader+dash 验证)。
… underline Showcase drawBehind + PathEffect.dashPathEffect on Compose Text: full-line dashed underline, local span dashed underline, and custom dash patterns. Lets readers follow TextDemo to learn how to add local dashed underlines.
Round 2 CR fixup covering hy3 / kimi3 / opus48 review reports plus
the P1/P2 issues (license / precision / hot-path perf) called out by
the reviewer:
- P1-1 add missing Tencent KuiklyUI license header to three new files
(PathEffect.kt / PathEffectTest.kt / DrawBehindDemo.kt) for open
source compliance.
- P1-2 align Android line-metric precision with iOS/OHOS: replace
toDpI (integer dp rounding) with toDpF in getLineMetrics and
getCharBoundingBox, eliminating up to 0.5dp geometry drift.
- P2-1 make TextLayoutResult line metrics lazy: hoist
callMethod("lineMetrics") out of the measure hot path and behind a
MultiParagraph.lineMetricsFn that only fires on first getLineTop /
getLineStart / lineCount access, matching the existing
getBoundingBoxFn design. Removes one sync bridge call + one split
+ four array allocations per Text measure.
- P2-2 avoid unconditional setLineDash bridge calls in KuiklyCanvas:
introduce a dashActive dirty flag so drawLine / fillOrStroke only
clear line dash when the previous stroke actually set one. Preserves
the anti-color-bleed fix while removing per-shape bridge overhead
on dense Canvas pages.
Verified with :compose:testDebugUnitTest (11/11 pass:
PathEffectTest 6, LazyListPrefetchBuildConfigTest 3, LegacyPrefetch-
BuildConfigTest 1, FramePrefetchSchedulerTest 1).
c658a5c to
7d8b7ca
Compare
KuiklyAI
left a comment
There was a problem hiding this comment.
🤖 KuiklyAI Code Review
增量盘点(HEAD 7d8b7ca4)
这次 push 把之前几笔提交 rebase 到了新 hash(e0937f1d / a2c69f12 / 7d8b7ca4,commit date 都重写成 07-28 20:56 +0800),最新一笔是 Round 2 CR fixup,内容上是上一轮问题的修复 + 性能/精度优化,没有新增功能面。
上轮问题处理情况
上一轮 8 条 inline 已逐条跟进过:endIndex 改回 exclusive、fillOrStroke 清 dash、annotatedText == null 守卫回滚、4dp 死注释清理、build-profile.json5 重新入库、iOS placeholder LTR override 恢复——这 7 条都已落地。剩 CanvasDrawScope brush 重载的 pathEffect 赋值仍注释着,按作者声明属未来增强(需三端 shader+dash 验证)暂缓,这点保持现状,后续启用 brush 虚线时一并打开或先去掉参数即可。
本笔新改动评估
- P1-2 精度对齐:Android
getLineMetrics/getCharBoundingBox由toDpI(取整)改toDpF,Rect改RectF,三端统一到浮点口径,最多 0.5dp 的几何漂移消除;compose 侧getBoundingBoxFn用toFloatOrNull解析,能正确吃浮点串,端到端一致。 - P2-1 行度量惰性化:
callMethod("lineMetrics")移出 measure 热路径、收进MultiParagraph.lineMetricsFn,首次访问getLineTop/getLineStart/lineCount才触发一次桥调用并lazy缓存。getLineEnd的旧格式兼容 fallback(lineEnds缺失走下一行 start / 末行退化空区间)语义保留,lineMetrics == null(CoreTextField 无行度量场景)退化为 0,与旧空数组行为一致,没引入回归。CoreTextField走位置参数MultiParagraph(lineCount, placeholderRects),第一个参数现在落到initialLineCount,签名兼容。 - P2-2 dashActive 脏标记:
KuiklyCanvas引入dashActive,drawLine/fillOrStroke仅在上一笔真设过 dash 时才下发setLineDash(emptyList()),view重置时同步复位。所有 stroke 入口(drawRect/drawOval/drawCircle/drawArc/drawPath/drawPoints)都走fillOrStroke,drawPoints 经fillOrStroke、drawLines 经drawLine,防串色修复保留的同时去掉了密集 Canvas 页每图形一次的桥开销。 - P1-1 license 头:
PathEffect.kt/PathEffectTest.kt/DrawBehindDemo.kt三个新文件已补 Tencent 开源 license 头。
没有发现新引入的功能正确性或二进制兼容问题,这一轮的优化改动是干净的。CanvasDrawScope brush 重载的 pathEffect 仍是悬而未决项,留意后续启用时处理。
|
性能侧完整看了下这次 先说结论:最新 head 已经把 建议区分下面几种具体场景:
1.
|
做了什么
把
Modifier.drawBehind与PathEffect.dashPathEffect打通为一套通用的文本虚线能力,覆盖 Android / iOS / OHOS 三端,补齐文本行度量的原生桥接,使绘制语义与官方 Compose 对齐;并在 demo 中提供「局部虚线(头/中/尾多段定位)」的可读示例。改动范围(9 个 commit,按阶段排列)
核心能力(框架层 compose/,未引入新依赖、未混用 DSL)
adad8c87文本虚线 drawBehind + PathEffect 官方写法对齐:KuiklyCanvas 下发 setLineDash、DrawModifier 用 snapshotObserver 触发重绘、PathEffect/DashPathEffect 值对象、TextLayoutResult/MultiParagraph 行度量 getter 与 KRRichTextView 桥接、新增 PathEffectTest 6 个用例3be45c0f移除 drawBehind 固定 4dp 底部补丁,DrawScope.size 严格等于组件自身尺寸,对齐官方语义6b571eafiOS/OHOS 补齐文本虚线行度量桥接,三端对齐 drawBehind + PathEffect296fcab6二轮打磨与 OHOS 越界钳制:非法 dash 输入降级实线、跨帧残留复位合入主线
8323a37aMerge origin/main(带入 LazyList prefetch 重构,与 drawBehind 绘制链路无交互)评审反馈修复
e0937f1d共性问题收敛:OHOS endIndex 改回 exclusive 对齐各端、串色 setLineDash 复位、恢复废弃守卫后的整段下划线、bg view 泄漏回滚、iOS LTR override 恢复、license 头补齐、Android toDpF 精度对齐、黑话清理、已知限制 kdoc7d8b7ca4Round 2 P1/P2 修复:demo 示例
f9211f59新增 DrawBehindDemo:覆盖 Box/Row/Column/Image/LazyColumn 五类组件与实线/虚线 8-4/虚线 16-8/点线 2-4 四种 PathEffecta2c69f12TextDemo 新增 DashedUnderlineDemo:单行与五行文本的头/中/尾多段虚线定位,含\n换行处自算字符偏移以规避三端换行边界不一致验证状态
:compose:testDebugUnitTest全过:PathEffectTest 6/6 + LazyList/Legacy/Frame prefetch 相关 5/5,共 11 用例、0 失败TextDemo新增的DashedUnderlineDemo覆盖整段与局部多段虚线、含换行文本场景已知限制