已合并
修改pickerview子组件渲染方式;解决pickerview页面不能滑动问题 #16
lizhe_创建于 5 天前
修改pickerview子组件渲染方式;解决pickerview页面不能滑动问题 #16
已合并
lizhe_创建于 5 天前
lizhe_成员
5 天前

修改pickerview子组件渲染方式;解决pickerview页面不能滑动问题

likedislike
Pull Request已成功合入, 合并人@zyssky465
(感谢 lizhe_ 的贡献)
Llizhe_成员
5 天前 推送  1 个提交:2d146efc-修改pickerview子组件渲染方式;解决pickerview页面不能滑动问题
Llizhe_成员
5 天前 推送  1 个提交:6453de34-修改pickerview子组件渲染方式;解决pickerview页面不能滑动问题
sueayee
sueayee成员5 天前进行代码检视2
flexui-engine/framework/ohos/src/main/cpp/impl/renderer/native/src/components/base_view.cc
已过期
@@ -1042,0 +1261,4 @@
1261+ } else if (IsPickerViewType(GetViewType())) {
1262+ if (lazyFrame_.has_value() && lazyFrame_->height >= 80.f) {
1263+ const float prev = lazyFrame_->height;
1264+ if (applied.height > prev + 1.f && applied.height >= prev + 20.f) {
sueayee
sueayee5 天前评论:

PickerView 高度防涨钳制会永久冻结宿主高度:一旦 lazyFrame_ 已有高度(≥80),任何 ≥ prev+20 的新帧高度都被回写成 prev 并存入 lazyFrame_,之后每帧继续满足钳制条件,合法的运行时增高永远不会生效。

场景:picker-view 高度 238px→300px(setData 驱动样式、flex 拉伸迟达、转屏),applied=300 被折回 238 且无任何路径收敛到真实高度;同时 GetPickerViewportHeight/GetWheelPadY/ETS 选中条都按 238 计算,与 Yoga 后续兄弟布局(300)错位。规格示例 style="height:300px" 的动态修改即命中。

建议:区分用户显式高度与行撑开脏高(例如要求连续 ≥2 帧同高才放行,或与 style 解析结果比对)。

likedislike
System
系统消息系统
4 天前 评论:

changed this line on 2450af9f view diff detail

sueayee
sueayee成员5 天前进行代码检视2
flexui-engine/framework/ohos/src/main/cpp/impl/renderer/native/src/components/custom_ts_view.cc
已过期
@@ -346,0 +959,4 @@
959+ const float pad = GetWheelPadY();
960+ const float item_h = GetPickerItemHeight();
961+ ApplyWheelTranslateY(pad - static_cast<float>(idx) * item_h);
962+ const bool should_emit = emit_change && (idx != last_emitted_index_ || !IsImmediateChange());
sueayee
sueayee5 天前评论:

should_emit 逻辑使非 immediate 模式在索引未变化时也发 change:!IsImmediateChange() 恒为 true,任意 ≥2vp 的滚动松手(包括 CANCEL 被外层 scroll 仲裁截断、以及滑回原索引的摆动)都会 CommitWheelIndex 并发 change。

规格 §2.16 规定 bindchange 在 value 改变时触发,这里同值重复触发,JS 端会收到与用户未选择动作对应的 value 提交。

建议:非 immediate 模式应要求 idx != last_emitted_index_ 才 emit;CANCEL 路径也应与 END 区分(至少不 commit 同值)。

likedislike
System
系统消息系统
4 天前 评论:

changed this line on 2450af9f view diff detail

sueayee
sueayee成员5 天前进行代码检视1
flexui-engine/framework/ohos/src/main/cpp/impl/renderer/native/src/components/custom_ts_view.cc
@@ -346,0 +652,4 @@
652+ }
653+ }
654+ }
655+ } else if (params[0].IsString()) {
sueayee
sueayee5 天前评论:

params[0].IsString() 分支把整串交给 ParsePickerIndexList 提取数字:该方法扫描串中所有数字序列,若桥接层将对象 JSON 字符串化(如 {"range":"[["01",..."31"]]","selected":"[0,1,30]"}),range 标签里的 01..31 会被全部解析成索引,picker_selected_ 变成垃圾数组,滚轮位置与 change 事件 detail 都错。

建议:该分支改为 JSON.parse 后取 selected 字段;若对象必为结构化传递,删除该分支并加注释说明。

likedislike
sueayee
sueayee成员5 天前进行代码检视2
flexui-engine/framework/ohos/src/main/cpp/impl/renderer/native/src/components/custom_ts_view.cc
已过期
@@ -346,0 +890,4 @@
890+ }
891+ float velocity_vp = FRPixelUtils::PxToVp(OH_ArkUI_PanGesture_GetVelocityY(event));
892+ if (std::abs(velocity_vp) < std::abs(pan_last_velocity_vp_)) {
893+ velocity_vp = pan_last_velocity_vp_;
sueayee
sueayee5 天前评论:

END 时的速度回退整体沿用最后一次 UPDATE 的速度(幅值和符号),且无符号一致性检查:快速上滑后停住手指再抬起,最后一次 UPDATE 的 +3000vp/s 被保留,滚轮会越过用户停住的项继续冲;若释放前短促反向刹车(未产生 UPDATE),则按与实测释放速度相反的旧方向 fling。

建议:幅值取 max,符号取 END 事件的符号(或至少要求两者同号才回退)。

likedislike
System
系统消息系统
4 天前 评论:

changed this line on 2450af9f view diff detail

sueayee
sueayee成员5 天前进行代码检视1
flexui-engine/framework/ohos/src/main/cpp/impl/renderer/native/src/components/custom_ts_view.cc
@@ -346,0 +866,4 @@
866+ const ArkUI_GestureEventActionType action = OH_ArkUI_GestureEvent_GetActionType(event);
867+ if (action == GESTURE_EVENT_ACTION_ACCEPT) {
868+ StopWheelFling(false);
869+ pan_start_translate_ = wheel_translate_y_;
sueayee
sueayee5 天前评论:

手势期间的程序化推送不重定 pan 基线:SyncColumnWheelTranslates→ApplyWheelTranslateY 会直接改 translate,但 pan_start_translate_ 只在 ACCEPT 记录一次;手指不动时松手会把程序推入的索引提交掉(滚轮从手指下方跳走),手指一动,下一个 UPDATE 又按旧基线 pan_start_translate_+dy 打回,SettleWheel 提交的是拖动索引,与 JS 模型矛盾。

picker-view.ts 的 value observer 会在 _userPicking 期间强行推(dependent-column 复位场景),native 内部行重建(OnChildInsertedImpl)也会触发。

建议:推送时若 wheel_picking_ 为真,重定 pan_start_translate_(或推迟推送至 pickend)。

likedislike
sueayee
sueayee成员5 天前进行代码检视1
flexui-engine/framework/ohos/src/main/cpp/impl/renderer/native/src/components/custom_ts_view.cc
@@ -152,3 +360,4 @@
360+ }
152361}
153362 
154363void CustomTsView::OnChildRemovedImpl(std::shared_ptr<BaseView> const &childView, int32_t index) {
sueayee
sueayee5 天前评论:

行移除路径不回补选择状态:列的行数缩小时(如日列 31→28 的 has:for splice),OnChildRemovedImpl 不重新钳制 wheel_index_/translate,也不更新 picker_selected_;与插入路径(OnChildInsertedImpl→SyncColumnWheelTranslates)不对称。

此后另一列的 change 会原样发射越界的旧索引(EmitPickerChange 无钳制),JS 端再钳回并回推,出现「事件值 ≠ 滚轮显示值」的窗口;且 JS 侧 valueExceedsRange 的 skip 会让该列长期停在越界 translate(选中条悬空)。

建议:行移除后按新行数钳制并重推 translate。

likedislike
sueayee
sueayee成员5 天前进行代码检视2
flexui-engine/driver/js/packages/flexui-backend/src/native-backend/NativeBackendElement.ts
已过期
@@ -2045,0 +2201,4 @@
2201+ if (this._findAncestorPickerViewColumn()) {
2202+ return false;
2203+ }
2204+ return this._findAncestorPickerView() != null;
sueayee
sueayee5 天前评论:

兜底 return this._findAncestorPickerView() != null 会把 picker-view 下所有非列子节点(包括内含 picker-view-column 的自定义组件包装层)从 native 树剔除:包装层宿主不建 native 节点,其内部的列因父 domNodeId 无 native 节点而永远挂载不上,滚轮空白。

但 nativePickerColumns(kids.length===1 递归)与 ETS collectPickerColumns(深度递归+isPickerColumnWrapper)都专门支持包装层,前端 light-tree 收集也能找到这些列——三层逻辑不一致。

建议:omit 前探测子树内是否含 picker-view-column,含则放行包装层,仅剔除纯非列叶子。

likedislike
System
系统消息系统
4 天前 评论:

changed this line on 2450af9f view diff detail

sueayee
sueayee成员5 天前进行代码检视1
flexui-engine/driver/js/packages/flexui-backend/src/native-backend/NativeBackendElement.ts
@@ -39,0 +69,4 @@
69+}
70+ 
71+/** picker-view indicator-style 的 height;缺省 34,与选中框一致。 */
72+function parsePickerIndicatorItemHeight(style: unknown): number {
sueayee
sueayee5 天前评论:

indicator-style 的 height 与 34/238 默认值在三个实现里各自独立解析、口径不一致:TS 正则 [0-9.]+(无单位也可)、C++ ParsePickerIndicatorItemHeight(strtof 前缀解析,无单位也可,还会命中 line-height)、ETS parsePxFromDecl(必须带 px 后缀)。

indicator-style="height:40" 时 C++/TS 按 40 排行走,ETS 高亮带和分割线回退 34——选中条比行距矮 6vp、偏移 3vp。默认值也散布 TS/C++/ETS 多处。

建议:收敛为单一解析实现(或至少统一单位语义)并共享常量。

likedislike
sueayee
sueayee成员5 天前进行代码检视1
flexui-engine/framework/ohos/src/main/cpp/impl/renderer/native/src/components/custom_ts_view.cc
@@ -346,0 +680,4 @@
680+ col_idx++;
681+ continue;
682+ }
683+ col->StopWheelFling(false);
sueayee
sueayee5 天前评论:

SyncColumnWheelTranslates 对每列调用 StopWheelFling(false):fling 中途被任何程序化推送(含 native 内部行重建触发)杀死时,既不 settle 也不发 pickend。

JS 侧 pickstart 置 _userPicking=true、pickend 才清——无 pickend 时 _userPicking 永久卡 true,此后 _syncColumnsToNative 全部被跳过,range/selected 更新失效,直到用户开启下一次完整手势。

建议:StopWheelFling(false) 路径补发 pickend(或 settle 后发),保证 pickstart/pickend 配对。

likedislike
sueayee
sueayee成员5 天前进行代码检视2
flexui-engine/framework/ohos/src/main/ets/renderer_native/components/pickerview/FRPickerWheelView.ets
已过期
@@ -822,0 +810,4 @@
810+ 
811+@Observed
812+export class FRPickerColumnView extends FlexUICustomComponentView {
813+ slotRowCount: number = 0
sueayee
sueayee5 天前评论:

新增的 ETS 状态是 C++ 滚轮的无消费镜像:syncColumnTranslates 写 FRPickerColumnView.wheelTranslateY(真实位移由 C++ NODE_TRANSLATE 负责,文件头注释也如此声明)、slotRowCount 从未读写、FlexUIRenderBaseView.pickerRowItemHeight 被 applyPickerRowItemHeight 写但全仓无读取。

同时 FRViewManager.onChildInsertedForCApi 故意不把 picker 子节点挂进 ETS 树,addSubRenderView/aboutToAppear 中的 pendingSelected 分支也基本不可达。镜像公式 pad − idx×itemH 与 C++ SyncColumnWheelTranslates 重复维护,漂移后难以排查。

建议:删除镜像与死簿记,以 C++ 为唯一事实源。

likedislike
System
系统消息系统
4 天前 评论:

changed this line on 2450af9f view diff detail

sueayee
sueayee成员5 天前进行代码检视2
flexui-engine/driver/js/packages/flexui-backend/src/native-backend/NativeBackendElement.ts
已过期
@@ -2044,0 +2208,4 @@
2208+ return !this._isVirtual && !this._omitNativeNode;
2209+ }
2210+ 
2211+ private _findAncestorPickerViewColumn(): NativeBackendElement | null {
sueayee
sueayee5 天前评论:

_findAncestorPickerViewColumn 与既有的 _findAncestorPickerView 是逐字节同构的 64 跳父链遍历(仅 tag 谓词不同),且经 _shouldOmitPickerNonColumn 的兜底在每次元素创建时都可能各跑一遍——不用 picker 的页面也要为每个节点付两次祖先遍历(每跳一次 elementMap 查找)。

建议:抽一个共享 _findAncestor(predicate) 助手(先用「进 picker 子树」标记或计数器短路),一次遍历同时检查两个谓词。

likedislike
System
系统消息系统
4 天前 评论:

changed this line on 2450af9f view diff detail

sueayee
sueayee成员5 天前进行代码检视1
flexui-engine/framework/ohos/src/main/ets/renderer_native/uimanager/FRViewManager.ets
@@ -5,6 +5,7 @@ import { LogUtils } from '../../support/utils/LogUtils'
55import FlexUIRenderBaseView from '../components/base/FlexUIRenderBaseView'
66import { FlexUICustomComponentView } from '../components/custom/FlexUICustomComponentView'
77import { FRRenderViewCreator } from '../components/FlexUIRenderRegisterMap'
8+import { FRPickerColumnView, FRPickerWheelView } from '../components/pickerview/FRPickerWheelView'
89import { FRWebView } from '../components/web/FRWebView'
910import { FlexUIRenderView } from '../FlexUIRenderView'
1011import { NativeRenderContext } from '../NativeRenderContext'
@@ -296,6 +297,10 @@ export class FRViewManager {
296297 let renderView: FlexUIRenderBaseView | null = null
297298 if (viewName == "WebView") {
298299 renderView = new FRWebView(this.ctx);
sueayee
sueayee5 天前评论:

PickerView/PickerViewColumn 同时以硬编码特例与注册表两条路径注册:createRenderViewForCApi 的特例先于 createRenderViewFromCreator 兜底执行,会遮蔽 provider 对同名组件的自定义 creator;C++ IsCustomTsRenderView 追加的字面量 OR 永远无法改变结果(custom_ts_render_views_ 已通过 native_renderer_napi.cc 注入同名条目)。

建议:删除特例,统一走注册表。

likedislike
sueayee
sueayee成员5 天前进行代码检视1
flexui-engine/framework/ohos/src/main/cpp/impl/renderer/native/include/renderer/components/custom_ts_view.h
@@ -79,0 +119,4 @@
119+ float pan_last_velocity_vp_ = 0.f;
120+ int wheel_index_ = 0;
121+ int last_emitted_index_ = -1;
122+ bool wheel_picking_ = false;
sueayee
sueayee5 天前评论:

wheel_picking_ 仅被写(cc:872/955/964)从未被读;WheelPanToken.generation 与 wheel_generation_、hub 的 running_/epoch_ 也互为冗余镜像。

文档(docs/refs/picker-view-vs-components.md)声称 wheel_picking_ 用于「commit only when panned」门控,但代码从未查询它——后续若有人按文档实现该门控会踩到与预期不符的旧值。

建议:每个生命周期只保留一个权威计数器,删除镜像。

likedislike
sueayee
sueayee成员
5 天前 评论:

检视结论(对照 atomic-component-spec.md §2.16/§2.17)

规格符合性 ✅

  • 属性:picker-view 的 value(Array)/indicator-style(String)/immediate-change(Boolean)名称与类型约束符合规格;picker-view-column 无独有属性,符合规格。
  • 事件bindchange(detail: {value: number[]})/bindpickstart/bindpickend 名称、bind 前缀与 detail 结构符合规格。
  • 约束关系:「数字大于可选项长度时选择最后一项」的钳制在 JS/C++/ETS 三层均有实现;「只可放置 picker-view-column,其他节点不显示」由 NativeBackendElement 的 omit 机制实现;「列子节点高度自动等于选中框高度」由 resolveNativeProps/TryApplyPickerWheelFrame 实现,方向符合规格。

主要偏差 ⚠️

  • 非 immediate 模式下 change 在索引未变化时也会触发(custom_ts_view.cc:962),与规格「value 改变时触发」不符。
  • 另有 13 条逻辑性问题已按行号逐一 inline 评论(涉及高度钳制、手势/fling 边界、行数收缩回补、三层解析口径不一致、死代码与重复注册等),请一并处理。
likedislike
Llizhe_成员
4 天前 推送  1 个提交:2450af9f-修改pickerview子组件渲染方式;解决pickerview页面不能滑动问题
sueayeesueayee成员
4 天前 解决了最后一个问题
sueayee
sueayee成员4 天前进行代码检视1
flexui-engine/framework/ohos/src/main/cpp/impl/renderer/native/src/components/custom_ts_view.cc
@@ -346,0 +835,4 @@
835+ SyncColumnWheelTranslates();
836+}
837+ 
838+void CustomTsView::SyncColumnWheelTranslates() {
sueayee
sueayee4 天前评论:

跟进(新提交 2450af9f):包装层放行后 C++ 侧列收集仍只认直接子节点。

NBE 已放行内含 column 的包装层(subtreeHasPickerColumn),JS nativePickerColumns 也递归 depth 8,但这里仍只遍历 picker 的 children 里 viewType == PickerViewColumn 的直接子节点;EmitPickerChange(:960)同样只遍历直接子节点。包装层内的列会正常挂载、手势也能工作(列自身上有 pan),但:

  1. updatePickerRange / 行插入触发的 SyncColumnWheelTranslates 永远找不到嵌套列 → 滚轮行停在列顶、无 pad 居中;
  2. change 事件遍历不到任何列 → value 数组为空 → JS 端 bindchange 静默不发。

建议与 JS 对齐:收集列时递归遍历(限定深度),或至少复用 GetPickerHost 的向上查找思路做向下收集。另注意:_subtreeHasPickerColumn 依赖挂载时包装层 _childOrder 已填充,若自顶向下先 append 包装层再填子(空包装层先被 omit),嵌套列仍会回到挂不上 native 的状态——建议 omit 判定也覆盖该顺序(如 _afterPickerChildTreeChange 时对已 omit 的包装层做重评估)。

likedislike
Llizhe_成员
4 天前 推送  1 个提交:9417199a-修改pickerview子组件渲染方式;解决pickerview页面不能滑动问题
sueayeesueayee成员
4 天前 解决了最后一个问题
zyssky465zyssky465成员
4 天前 合入了pull request,合并节点 SHA:f9c9039407e7ea2f4840affd34027b6800fcfc2d