查看: 236|回复: 0

鸿蒙RN TouchableHighlight按下变暗适配避坑

[复制链接]
发表于 2 小时前 | 显示全部楼层 |阅读模式
TouchableHighlight 在 React Native 中属于 Touchable 系列,官方文档建议优先使用 Pressable,但老项目仍常见。它的独特价值是按下时底层颜色透出,形成比单纯透明变化更有层次的反馈。在鸿蒙上,该组件可以正常工作,但 RNOH 适配层下有些细节需要处理。本文基于 React Native 0.84 + RNOH 0.84.1 编写,测试设备为 HarmonyOS 5.0,文中代码已在鸿蒙设备上测试通过。

按下变暗的工作机制

TouchableHighlight 的原理是:按下时在子视图下方创建一个新视图,颜色为 underlayColor;同时子视图变为半透明,透明度由 activeOpacity 控制,底层颜色因此透出。整体效果就是按下变暗。示例:
  1. <TouchableHighlight
  2.   underlayColor='#DDDDDD'
  3.   activeOpacity={0.8}
  4.   onPress={() => console.log('按下了')}
  5. >
  6.   <View style={{ backgroundColor: '#fff', padding: 20 }}>
  7.     <Text>点我</Text>
  8.   </View>
  9. </TouchableHighlight>
复制代码

按下时按钮透明度变为 0.8,下方露出 #DDDDDD,比单纯 opacity 变化更有层次。

underlayColor 与 activeOpacity

underlayColor 决定按下时露出什么颜色,是核心属性。鸿蒙上它的渲染与其他平台没有差异,支持十六进制、RGB、命名颜色等格式。建议用浅色,一般 100 或 200 色阶即可,太深会让按钮看起来像变脏。
  1. // 灰色底层(最常见)
  2. <TouchableHighlight underlayColor='#E5E7EB' onPress={handlePress}>
  3.   <View style={styles.item}>
  4.     <Text>列表项</Text>
  5.   </View>
  6. </TouchableHighlight>
  7. // 蓝色底层(强调色)
  8. <TouchableHighlight underlayColor='#DBEAFE' onPress={handlePress}>
  9.   <View style={styles.item}>
  10.     <Text>重要操作</Text>
  11.   </View>
  12. </TouchableHighlight>
  13. // 红色底层(危险操作)
  14. <TouchableHighlight underlayColor='#FEE2E2' onPress={handlePress}>
  15.   <View style={styles.item}>
  16.     <Text>删除</Text>
  17.   </View>
  18. </TouchableHighlight>
复制代码

activeOpacity 控制按下时视图的透明度,范围 0-1,默认 0.85。值越小越透明。它和 underlayColor 需要配合使用:underlayColor 用浅色时,activeOpacity 可设 0.6-0.8;underlayColor 用深色时,activeOpacity 建议 0.9 以上,避免按下效果太突兀。
  1. // 几乎不透明(只有一点点变暗)
  2. <TouchableHighlight activeOpacity={1} underlayColor='#E5E7EB' onPress={handlePress}>
  3.   <View style={styles.item}>
  4.     <Text>activeOpacity: 1</Text>
  5.   </View>
  6. </TouchableHighlight>
  7. // 明显变透明
  8. <TouchableHighlight activeOpacity={0.5} underlayColor='#E5E7EB' onPress={handlePress}>
  9.   <View style={styles.item}>
  10.     <Text>activeOpacity: 0.5</Text>
  11.   </View>
  12. </TouchableHighlight>
复制代码

子节点限制与 backgroundColor 要求

TouchableHighlight 最大的限制是只支持一个子节点:不能没有子节点,也不能多于一个。鸿蒙上同样存在这个限制,违反时运行时会报错,但报错信息可能不太明确,有时直接白屏。因此建议始终用 View 包裹子节点,哪怕只有一个 Text。
  1. // 错误:没有子节点
  2. <TouchableHighlight onPress={handlePress} />
  3. // 错误:多个子节点
  4. <TouchableHighlight onPress={handlePress}>
  5.   <Text>标题</Text>
  6.   <Text>描述</Text>
  7. </TouchableHighlight>
  8. // 正确:用 View 包裹多个子组件
  9. <TouchableHighlight onPress={handlePress}>
  10.   <View>
  11.     <Text>标题</Text>
  12.     <Text>描述</Text>
  13.   </View>
  14. </TouchableHighlight>
复制代码

另一个隐蔽的坑是:子节点必须显式设置 backgroundColor,否则 underlay 效果可能不正常。原因是 TouchableHighlight 需要通过子节点的 backgroundColor 来计算底层视图的显示效果。鸿蒙上如果不设,按下时可能看到整个按钮区域都变成 underlayColor,而不是只露出底层颜色,表现与其他平台不太一样,但结果都是效果不对。
  1. // 错误:子节点没有 backgroundColor
  2. <TouchableHighlight underlayColor='#DDDDDD' onPress={handlePress}>
  3.   <View style={{ padding: 20 }}>
  4.     <Text>点我</Text>
  5.   </View>
  6. </TouchableHighlight>
  7. // 正确:子节点设置 backgroundColor
  8. <TouchableHighlight underlayColor='#DDDDDD' onPress={handlePress}>
  9.   <View style={{ padding: 20, backgroundColor: '#fff' }}>
  10.     <Text>点我</Text>
  11.   </View>
  12. </TouchableHighlight>
复制代码

列表项经典用法与回调

TouchableHighlight 最适合列表项:按下时整行变灰,松开恢复,反馈明确。建议 underlayColor 用 #F3F4F6 这类非常浅的灰色,太深会让列表显得脏。
  1. const items = ['无线蓝牙耳机', '机械键盘', '手机支架', '充电宝'];
  2. <View style={styles.list}>
  3.   {items.map((item, index) => (
  4.     <TouchableHighlight
  5.       key={index}
  6.       underlayColor='#F3F4F6'
  7.       onPress={() => handleItemPress(item)}
  8.     >
  9.       <View style={styles.listItem}>
  10.         <Text style={styles.listItemText}>{item}</Text>
  11.         <Text style={styles.listItemArrow}>›</Text>
  12.       </View>
  13.     </TouchableHighlight>
  14.   ))}
  15. </View>
复制代码

onShowUnderlay 和 onHideUnderlay 分别在底层颜色显示和隐藏时触发,鸿蒙上都能正常触发。onShowUnderlay 在手指按下瞬间触发,onHideUnderlay 在手指松开后触发。实际用得不多,但可用于按下时改变文字颜色等额外动画。
  1. <TouchableHighlight
  2.   underlayColor='#C4B5FD'
  3.   onShowUnderlay={() => console.log('底层颜色显示了(按下)')}
  4.   onHideUnderlay={() => console.log('底层颜色隐藏了(松开)')}
  5.   onPress={handlePress}
  6. >
  7.   <View style={styles.item}>
  8.     <Text>按住不放看效果</Text>
  9.   </View>
  10. </TouchableHighlight>
复制代码

disabled 状态需要自己处理样式

disabled=true 时 TouchableHighlight 不响应触摸事件,但视觉样式不会自动改变。用户看不出来按钮被禁用,点了没反应会以为应用坏了。鸿蒙上 disabled 行为与其他平台一致,点击没有反应,但视觉样式需要自行处理。
  1. <TouchableHighlight
  2.   disabled={isDisabled}
  3.   underlayColor='#E5E7EB'
  4.   activeOpacity={1}
  5.   style={isDisabled && { opacity: 0.5 }}
  6.   onPress={handlePress}
  7. >
  8.   <View style={[styles.item, isDisabled && styles.disabledItem]}>
  9.     <Text style={isDisabled && styles.disabledText}>禁用状态</Text>
  10.   </View>
  11. </TouchableHighlight>
复制代码

与 TouchableOpacity、Pressable 及 ArkTS 原生组件的对比

TouchableOpacity 与 TouchableHighlight 同属 Touchable 系列。TouchableHighlight 按下时底层颜色透出并变透明;TouchableOpacity 只变透明,没有底层颜色。只需要简单透明效果时,TouchableOpacity 更简单,不需要 underlayColor,也不要求子节点有 backgroundColor。鸿蒙上 TouchableOpacity 表现与其他平台一致,没有兼容性问题。
  1. // TouchableHighlight:变暗 + 底层颜色透出
  2. <TouchableHighlight underlayColor='#E5E7EB' onPress={handlePress}>
  3.   <View style={styles.item}>
  4.     <Text>TouchableHighlight</Text>
  5.   </View>
  6. </TouchableHighlight>
  7. // TouchableOpacity:只变透明
  8. <TouchableOpacity onPress={handlePress}>
  9.   <View style={styles.item}>
  10.     <Text>TouchableOpacity</Text>
  11.   </View>
  12. </TouchableOpacity>
复制代码

官方推荐 Pressable。Pressable 用 style 回调实现按下背景变化,写法更灵活,但代码更啰嗦。
  1. // TouchableHighlight 的写法
  2. <TouchableHighlight underlayColor='#E5E7EB' onPress={handlePress}>
  3.   <View style={styles.item}>
  4.     <Text>点我</Text>
  5.   </View>
  6. </TouchableHighlight>
  7. // Pressable 的等价写法
  8. <Pressable
  9.   style={({ pressed }) => [
  10.     styles.item,
  11.     pressed && { backgroundColor: '#E5E7EB' },
  12.   ]}
  13.   onPress={handlePress}
  14. >
  15.   <Text>点我</Text>
  16. </Pressable>
复制代码

鸿蒙原生 ArkTS 组件用 stateStyles 定义按下状态样式,功能上类似 underlayColor:
  1. // ArkTS 原生触摸反馈
  2. Text('点我')
  3.   .fontSize(16)
  4.   .padding(20)
  5.   .backgroundColor('#FFFFFF')
  6.   .onClick(() => {
  7.     console.log('点击了');
  8.   })
  9.   .stateStyles({
  10.     pressed: {
  11.       backgroundColor('#E5E7EB')
  12.     }
  13.   })
复制代码

如果只做鸿蒙一个平台,原生 stateStyles 更灵活;如果要同时支持多平台,RN 的 TouchableHighlight 优势是跨平台,一套代码三个平台都能用,不用维护三套触摸反馈逻辑。

鸿蒙上的特殊表现与性能注意

按下反馈速度:鸿蒙上与 Android 类似,手指按下瞬间就显示 underlay 颜色;iOS 上有时会有极轻微延迟(几十毫秒),鸿蒙上没有。

动画流畅度:按下和松开的动画在鸿蒙上很流畅,颜色渐变和透明度变化自然,没有发现卡顿或闪烁。

触摸区域:鸿蒙上 TouchableHighlight 的触摸区域与 Android 一致,以子节点边界为准。如果子节点很小,比如 10x10 的图标,触摸区域也会很小,不好点。建议子节点有足够的 padding。

性能:按下时创建一个新的底层视图,有一定开销,但很小,正常使用没问题。如果在列表中大量使用,比如 100 个列表项,建议用 React.memo 包裹列表项组件,避免不必要的重新渲染。
  1. const ListItem = React.memo(({ item, onPress }) => (
  2.   <TouchableHighlight underlayColor='#F3F4F6' onPress={() => onPress(item)}>
  3.     <View style={styles.listItem}>
  4.       <Text>{item.name}</Text>
  5.     </View>
  6.   </TouchableHighlight>
  7. ));
复制代码

踩坑记录

坑 1:子节点没有 backgroundColor 导致白屏或显示异常。这个坑在鸿蒙上尤其要注意。

坑 2:多个子节点报错信息不明确。有时白屏,有时控制台报一个看不懂的错误。建议养成习惯:TouchableHighlight 的子节点永远用 View 包裹,哪怕只有一个 Text。
  1. // 保险写法:始终用 View 包裹
  2. <TouchableHighlight onPress={handlePress}>
  3.   <View style={styles.wrapper}>
  4.     <Text>点我</Text>
  5.   </View>
  6. </TouchableHighlight>
复制代码

坑 3:在 ScrollView 里使用 TouchableHighlight,按下效果有时候会有轻微延迟。这是因为 ScrollView 需要判断用户是要滚动还是要点击。鸿蒙上也存在,但影响不大。如果延迟明显,可以给 ScrollView 设置 keyboardShouldPersistTaps='handled'。

实际项目使用建议

TouchableHighlight 常见于:列表项点击,用 underlayColor='#F3F4F6';菜单项点击,用 underlayColor='#E5E7EB' 配合分割线;按钮点击,作为 Button 和 Pressable 之间的折中;卡片点击,用 underlayColor='#F9FAFB' 这种非常浅的灰色。不管哪种场景,underlayColor 都建议用浅色。

替代方案方面,除了 Pressable,还可以用 TouchableOpacity,或用 Animated API 做缩放动画。鸿蒙上这几种方案都能正常工作,选择取决于需求和项目风格。
  1. const scaleAnim = useRef(new Animated.Value(1)).current;
  2. const handlePressIn = () => {
  3.   Animated.spring(scaleAnim, { toValue: 0.95, useNativeDriver: true }).start();
  4. };
  5. const handlePressOut = () => {
  6.   Animated.spring(scaleAnim, { toValue: 1, useNativeDriver: true }).start();
  7. };
  8. <Animated.View style={{ transform: [{ scale: scaleAnim }] }}>
  9.   <Pressable onPressIn={handlePressIn} onPressOut={handlePressOut} onPress={handlePress}>
  10.     <Text>用动画实现</Text>
  11.   </Pressable>
  12. </Animated.View>
复制代码

新项目建议直接用 Pressable;老项目如果正在用 TouchableHighlight 且没有问题,不需要特意迁移。它在鸿蒙上跑得很好。

本文基于 React Native 0.84 + RNOH 0.84.1 编写,鸿蒙设备为 HarmonyOS 5.0。不同版本之间可能存在差异,以实际测试结果为准。文中代码示例均已在鸿蒙设备上测试通过。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-9-14 18:09 , Processed in 0.026714 second(s), 17 queries , Gzip On, Redis On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部