已关闭
fix: 修复外部改变状态,开关动画消失;onAsyncPress 失败时会先切换 UI 再回滚[update verson to 1.1.1-beta.1] #16
fix: 修复外部改变状态,开关动画消失;onAsyncPress 失败时会先切换 UI 再回滚[update verson to 1.1.1-beta.1] #16
已关闭
yesses创建于 9月15日关闭于 6 天前
yesses
yesses
9月15日

变更概述

本 PR 对鸿蒙化三方库 react-native-switch-pro 质量加固问题修复与工程完善:

  • 修复外部 value 属性变更时,鸿蒙版无动画直接切换到新值(瞬时跳变)
  • 修复 onAsyncPress 失败时会先切换 UI 再回滚,产生"闪烁"效果
  • 新增 Example demo 示例工程(含 HarmonyOS harmony 目录及 testerino 演示页面)
  • 更新 README 文件为新模板,并新增英文版 README(README_en.md)

一、三方库源码修改点(重点)

1. 修复外部 value 属性变更时无动画直接切换

根因:componentWillReceiveProps 中检测到外部 value 变更时调用 syncSwitchToValue,该函数直接通过 switchAnimation.setValue() 将动画值瞬间设到目标位置(±offset),不经过 Animated.timing,因此开关状态被外部改变时 UI 无动画直接跳变到新值。

修改前——syncSwitchToValue 直接 setValue,无动画过渡:

if (typeof nextProps.value !== 'undefined' && nextProps.value !== this.props.value) {
  this.syncSwitchToValue(nextProps.value)
}

修改后——外部变更与用户点击收敛到同一条切换动画路径 toggleSwitchToValue,从当前位置 200ms 平滑动画到目标位:

if (typeof nextProps.value !== 'undefined' && nextProps.value !== this.props.value) {
  this.toggleSwitchToValue(true, nextProps.value)
}

toggleSwitchToValue 先复位圆点缩放(animateHandler),再由 animateSwitch 以 Animated.timing 从当前位置动画到目标位,动画真正完成后才 setState 更新内部 value,全程无 setValue 瞬移。配套调整:

  • 删除 syncSwitchToValue(直接 setValue 瞬移)与中间实现 executeToggleToValue(先 setValue 瞬移到反侧起点再动画,自身会产生一次可见跳动),切换动画统一收敛到 animateSwitch / animateHandler,动画结构对齐上游 1.0.5
  • switchAnimation 初始值由 value ? this.offset : -this.offset 调整为 value ? -1 : 1,对齐 render 插值区间端点,切换动画从边界起全程可见
  • 调用处保留上游扩展注释:程序化改值前可先放大圆点(animateHandler(this.handlerSize * SCALE))再延时切换,按需启用

example 验证:Example/src/switch-pro/index.tsx「外部修改value:通过按钮改变value prop,应有200ms动画过渡(非瞬时跳变)」用例。

2. 修复 onAsyncPress 失败时先切换 UI 再回滚产生的"闪烁"

根因:异步模式下点击开关时先乐观执行 executeToggleToValue(targetValue) 立即切换 UI;业务侧回调 result === false 时再执行 executeToggleToValue(value) 切回原状态。两次 200ms 动画来回切换,视觉上产生"闪烁"。且调用方无法实现"等待业务确认后再切换"的等待模式。

修改前——乐观切换 + 失败回滚,两次动画闪烁:

} else if (onAsyncPress) {
  this.executeToggleToValue(targetValue)
  onAsyncPress((result) => {
    if (!result) {
      this.executeToggleToValue(value)
    }
  })
}

修改后——将 toggleSwitch 传入回调,由调用方通过 callback(true/false) 决定是否切换:

} else if (onAsyncPress) {
          const targetValue = result ? !value : this.state.value
          if (targetValue === this.state.value){
            callback(targetValue)
            return
          }
          this.toggleSwitchToValue(result, !value, () => callback(targetValue))
}
  • callback(true):执行一次切换动画(executeToggleToValue)
  • callback(false):仅复位圆点缩放(animateHandler),开关保持原位,不产生回滚
  • 默认 onAsyncPress = (callback) => {callback(true)},未传回调时行为与同步切换一致,无兼容性影响

example 验证:Example/src/switch-pro/index.tsx「onAsyncPress成功:点击后开关不动,1s后callback(true)才动画切换(等待模式)」「onAsyncPress失败:callback(false)后开关保持不动,不出现回滚闪烁」两个用例。


二、example 测试用例

重构 Example 工程:删除旧版 android/ios 示例,新增基于 RNOH 的 harmony 工程与 testerino 演示页面(Example/src/switch-pro/index.tsx),共 10 个用例:

用例 测试场景 预期
基础切换 非受控/受控开关点击 点击后 200ms 平滑动画,无回闪(防闪烁回归)
onSyncPress 同步回调 回调参数为切换后的新值(true/false 交替)
onAsyncPress 成功 点击后延迟 1s callback(true) 等待期间开关保持原位,1s 后才动画切换
onAsyncPress 失败 点击后延迟 1s callback(false) 开关保持原位,不出现回滚闪烁
外部修改 value 按钮改变 value prop 200ms 动画过渡,非瞬时跳变
width、height 自定义宽高 开关尺寸正确
circleColorActive、circleColorInactive 圆点颜色 两态颜色正确
disabled 禁用状态 点击/拖拽无响应
circleStyle 圆点自定义样式 样式生效
backgroundActive、backgroundInactive 背景颜色 两态背景色正确

三、文档修改

  • CHANGELOG.md:新增 v1.1.1-beta.1 版本记录
  • README.md:更新为新版文档模板(含简介、下载安装、Link 说明、接口文档)
  • 新增 README_en.md 英文版文档
  • 版本号 1.1.0 -> 1.1.1-beta.1(涉及 package.json / CHANGELOG.md)

四、自测说明

  • 基础切换:点击后 200ms 平滑动画,无回闪
  • onSyncPress:回调参数为切换后的新值
  • onAsyncPress 成功:点击后开关保持原位,1s 后 callback(true) 才动画切换
  • onAsyncPress 失败:callback(false) 后开关保持原位,无回滚闪烁
  • 外部修改 value:按钮改变 value prop,有 200ms 动画过渡(非瞬时跳变)
  • width/height、圆点颜色、背景颜色、circleStyle、disabled 等样式属性均正常

资料修改

  • 是否已在 README 说明该接口/功能的变化:是(README 更新为新模板,新增英文版 README_en.md)
  • 是否已在 README 和 package.json 修改了版本信息:是(版本号 1.1.0 -> 1.1.1-beta.1)

Checklist

likedislike
当前Pull Request已关闭, 关闭人@dingchengjie
openharmony_ci
openharmony_ci成员
9月15日 评论:

感谢提交 Pull Requests !此PR未通过DCO校验。
校验失败可能原因:

1. 未签署“DCO协议”(开发者原创声明协议),在线签署、查看签署状态。

2. Commits 中未包含 Signed-off-by信息,参考FAQ处理。

修复上述问题后,在PR的评论框输入“check dco” ,单击”评论”,系统将再次进行DCO校验。

当前检测到如下Commits 未包含Signed-off-by信息:


Thanks for submitting a pull request. This pull request has not passed the DCO check.
Possible causes:

1. You have not signed the Developer Certificate of Origin (DCO). Sign the DCO and check DCO status.

2. The commits do not contain the Signed-off-by information. To resolve this issue, see FAQs.

After resolving the preceding issues, enter check dco in the comment box of this pull request and click Comment. The system will check DCO status again.

The following commits do not contain the Signed-off-by information:

likedislike
openharmony_ciopenharmony_ci成员
9月15日 添加了label:dco检查失败
yessesyesses
9月15日 强制推送  1 个提交:81b954c1-fix: 修复外部改变状态,开关动画消失;onAsyncPress 失败时会先切换 UI 再回滚
openharmony_ci
openharmony_ci成员
9月15日 评论:

感谢提交 Pull Requests!
Thanks for submitting a pull request.

likedislike
openharmony_ciopenharmony_ci成员
9月15日 删除了label:dco检查失败
此处折叠了95条消息 查看更多
yessesyesses
23 天前 强制推送  1 个提交:34b80196-fix: external state change causing the switch animation to disappear; onAsyncPress toggles UI before rolling back on failure
openharmony_ci
openharmony_ci成员
23 天前 评论:

感谢提交 Pull Requests!
Thanks for submitting a pull request.

likedislike
cpf-manager
cpf-manager23 天前进行代码检视1
lib/index.js
@@ -110,1 +115,4 @@
110115 if (toggleable) {
116+ // 动画飞行中 state.value 尚未更新(推迟到动画完成),
117+ // 读 pending 目标作为有效当前值,保证连点立即反向
118+ const value = this._pendingValue !== undefined ? this._pendingValue : this.state.value
cpf-manager
cpf-manager23 天前评论:

【AI-Review】【一般】【基础代码问题】【代码逻辑错误】_onPanResponderMove 与 _onPanResponderRelease 读取的 value 不一致,动画飞行中拖拽会导致开关卡在中间位或反向切换

● 问题:_onPanResponderRelease(lib/index.js:118)已改为读取 this._pendingValue 作为动画飞行中的有效当前值,但 _onPanResponderMove(lib/index.js:100)仍直接读取 this.state.value。由于 executeToggleToValue(lib/index.js:154-185)把 setState({value}) 推迟到动画完成后才执行,动画飞行中 state.value 仍是旧值,_pendingValue 才是新目标,两个手势回调读取的"当前值"不一致。

触发路径:用户第一次点击(state.value=false,executeToggleToValue(true) 启动,_pendingValue=true,switchAnimation 1->19 动画启动)-> 200ms 动画期间用户再次触摸并拖拽 -> _onPanResponderGrant(lib/index.js:91-97)调用 animateHandler(this.handlerSize * SCALE),animateHandler 内部 handlerAnimation.stopAnimation() 通过 Animated.parallel 默认的 stopTogether=true 中断整个 parallel,switchAnimation 停在中间值(例如 10)-> _onPanResponderMove 读取 state.value=false(旧值),计算 toggleable = false ? ... : (gestureState.dx > -10);若用户向右拖(dx=15),toggleable = (15 > -10) = true -> _onPanResponderRelease 读取 _pendingValue=true,targetValue = !true = false,executeToggleToValue(false),开关切到与用户拖拽方向相反的 false。若用户向左拖(dx=-15),toggleable = (-15 > -10) = false -> 走 else 分支(lib/index.js:146-148)只调用 animateHandler(this.handlerSize),不重启 switchAnimation,开关圆点卡在 1->19 的中间位(例如 10)。

● 影响:一般。仅在 200ms 动画飞行中再触摸并拖拽时触发,但一旦触发会出现圆点卡在中间位或反向切换的可见缺陷,与 PR "防闪烁/快速连点" 的目标相悖。

● 建议:_onPanResponderMove 与 _onPanResponderRelease 使用同一有效值来源。_onPanResponderMove(lib/index.js:100)改为 const value = this._pendingValue !== undefined ? this._pendingValue : this.state.value,使 toggleable 与 release 的 targetValue = !value 基准一致;另外 _onPanResponderRelease 的 else 分支(toggleable=false)在 _pendingValue !== undefined 时应调用 executeToggleToValue(this._pendingValue) 恢复飞行中被打断的动画,避免圆点卡在中间位。

likedislike
cpf-manager
cpf-manager23 天前进行代码检视1
lib/index.js
@@ -244,1 +275,3 @@
244- width: handlerAnimation,
275+ // 宽度常量化:逐帧 JS 驱动布局属性(width)在 RNOH 上会闪烁
276+ // (真机:快速连点残闪),布局属性不参与逐帧动画
277+ width: this.handlerSize,
cpf-manager
cpf-manager23 天前评论:

【AI-Review】【一般】【基础代码问题】【代码逻辑错误】handlerAnimation 仍在动画但 render 不再使用,按压放大反馈丢失且产生无效动画

● 问题:render(lib/index.js:277)把内层 Animated.View 的 width 从 handlerAnimation 改为常量 this.handlerSize,但 executeToggleToValue(lib/index.js:156、164-167)和 animateHandler(lib/index.js:188、190-193)仍在 this.state.handlerAnimation 上调用 stopAnimation() 和 Animated.timing(...)。_onPanResponderGrant(lib/index.js:96)调用 animateHandler(this.handlerSize * SCALE) 想放大圆点提供按压反馈,但 handlerAnimation 已不参与任何样式属性(render 中无任何地方引用 handlerAnimation),Animated.timing 产生的动画值无人消费。

触发路径:用户按下 -> _onPanResponderGrant -> animateHandler(this.handlerSize * SCALE) -> handlerAnimation 从 handlerSize(19) 动画到 handlerSize*6/5(22.8),但 render 中 width: this.handlerSize(19)是常量,圆点尺寸不变,按压放大反馈完全丢失。同样,executeToggleToValue 中 Animated.timing(this.state.handlerAnimation, {toValue: this.handlerSize, ...}) 也是无人消费的无效动画。

● 影响:一般。原库的按压放大反馈(圆点按下时放大 1.2 倍)在 RNOH 上完全消失,用户按下无任何尺寸反馈;同时 Animated.timing(handlerAnimation, ...) 仍在每帧驱动一个无人读取的 Animated.Value,属于无效计算(stopAnimation() 的副作用用于打断 parallel 是有意义的,但 Animated.timing 部分本身是冗余的)。

● 建议:若按压放大反馈是有意移除,应一并删除 handlerAnimation 状态、animateHandler 函数及 executeToggleToValue 中对 handlerAnimation 的 Animated.timing(保留 switchAnimation.stopAnimation() 用于打断),改为单独 Animated.timing(switchAnimation).start(...);若按压反馈需要保留,可使用非布局属性驱动放大,例如 transform: [{ scale: handlerAnimation.interpolate({ inputRange: [this.handlerSize, this.handlerSize * SCALE], outputRange: [1, 1.2] }) }],避免逐帧 JS 驱动 width 布局属性引发 RNOH 闪烁。

likedislike
dingchengjiedingchengjie成员
6 天前 关闭了 pull request