查看: 99|回复: 0

鸿蒙 RN 键盘事件适配:will 不触发与高度兜底处理

[复制链接]
发表于 半小时前 | 显示全部楼层 |阅读模式
在 React Native 鸿蒙化项目里,键盘处理是评论输入框、聊天输入框、搜索页和表单页都会遇到的高频问题。原文基于 React Native 0.84 + RNOH 0.84.1,在 HarmonyOS 6.0 设备上测试。核心结论很明确:鸿蒙侧 Keyboard 的 will 系列事件不触发,只有 did 系列及 keyboardDidChangeFrame 可用;keyboardDidShow 里的键盘高度可能不准;scheduleLayoutAnimation 不生效;Keyboard.isVisible() 存在延迟。下面按 API 能力、监听器管理、典型场景、鸿蒙坑位和排障建议重组。

一、Keyboard API 能做什么

Keyboard 的职责不多:监听键盘弹出和收起、获取键盘尺寸、主动收起键盘。原文提到 React Native 的 Keyboard 支持 6 种事件,但鸿蒙上和 Android 类似,will 系列无法使用。可以稳定工作的事件是 keyboardDidShow、keyboardDidHide 和 keyboardDidChangeFrame。事件回调参数为 KeyboardEvent,包含 duration、easing、startCoordinates、endCoordinates。endCoordinates 中有 height、width、screenX、screenY,聊天输入框计算偏移量时通常取 endCoordinates.height。
  1. import { Keyboard } from 'react-native';
  2. useEffect(() => {
  3.   const showSub = Keyboard.addListener('keyboardDidShow', (e) => {
  4.     console.log('键盘已显示,高度:', e.endCoordinates.height);
  5.   });
  6.   const hideSub = Keyboard.addListener('keyboardDidHide', () => {
  7.     console.log('键盘已隐藏');
  8.   });
  9.   return () => {
  10.     showSub.remove();
  11.     hideSub.remove();
  12.   };
  13. }, []);
复制代码

Keyboard.dismiss 是全局方法,调用后所有 TextInput 都会失去焦点,键盘收起。Keyboard.isVisible 用来检查键盘是否弹出,Keyboard.metrics 用来获取键盘尺寸。但在鸿蒙上 isVisible 有延迟,不建议在 keyboardDidShow 回调里立刻依赖它。
  1. Keyboard.addListener('keyboardDidShow', (e) => {
  2.   const { height, width, screenX, screenY } = e.endCoordinates;
  3.   console.log('键盘高度:', height, '宽度:', width);
  4. });
  5. const visible = Keyboard.isVisible();
  6. const metrics = Keyboard.metrics();
  7. if (metrics) {
  8.   console.log('键盘高度:', metrics.height);
  9.   console.log('键盘 Y:', metrics.screenY);
  10. }
复制代码

二、监听器注册与清理

原文里反复强调一个反面教材:不在 useEffect 中注册监听器,而是在每次渲染时都 Keyboard.addListener,旧的监听器又没有清理,最终造成内存泄漏。正确做法是在 useEffect 中注册,并在返回函数里调用 sub.remove()。
  1. useEffect(() => {
  2.   const sub = Keyboard.addListener('keyboardDidShow', () => {
  3.     setStatus('显示');
  4.   });
  5.   return () => sub.remove();
  6. }, []);
复制代码

三、聊天输入框:用 keyboardDidShow 高度撑起输入区

聊天输入框的常见做法是监听 keyboardDidShow 获取键盘高度,把输入区域的 paddingBottom 设为该高度,让输入框浮在键盘上方。发送消息后调用 Keyboard.dismiss 收起键盘。输入框高度可以随内容自适应,范围从 40px 到 120px。
  1. const ChatInput = () => {
  2.   const [inputHeight, setInputHeight] = useState(40);
  3.   const [keyboardHeight, setKeyboardHeight] = useState(0);
  4.   const inputRef = useRef(null);
  5.   useEffect(() => {
  6.     const show = Keyboard.addListener('keyboardDidShow', (e) => {
  7.       setKeyboardHeight(e.endCoordinates.height);
  8.     });
  9.     const hide = Keyboard.addListener('keyboardDidHide', () => {
  10.       setKeyboardHeight(0);
  11.     });
  12.     return () => {
  13.       show.remove();
  14.       hide.remove();
  15.     };
  16.   }, []);
  17.   const handleSend = () => {
  18.     sendMessage();
  19.     Keyboard.dismiss();
  20.   };
  21.   return (
  22.     <View style={{ paddingBottom: keyboardHeight, backgroundColor: '#fff' }}>
  23.       <View style={{ flexDirection: 'row', alignItems: 'flex-end' }}>
  24.         <TextInput
  25.           ref={inputRef}
  26.           placeholder='输入消息...'
  27.           multiline
  28.           style={{
  29.             flex: 1,
  30.             height: Math.max(40, Math.min(120, inputHeight)),
  31.             borderWidth: 1,
  32.             borderRadius: 20,
  33.             paddingHorizontal: 16,
  34.           }}
  35.           onContentSizeChange={(e) =>
  36.             setInputHeight(e.nativeEvent.contentSize.height)
  37.           }
  38.         />
  39.         <Pressable onPress={handleSend}>
  40.           <Text>发送</Text>
  41.         </Pressable>
  42.       </View>
  43.     </View>
  44.   );
  45. };
复制代码

四、搜索页自动聚焦与表单页跳转

搜索页的典型模式是页面加载后延迟 300ms 自动聚焦输入框,让键盘弹出;同时监听 keyboardDidShow,一旦键盘弹出就隐藏搜索历史。表单页则可以用 returnKeyType 和 onSubmitEditing 在多个输入框之间跳转,最后一个字段用 Keyboard.dismiss 收起键盘。ScrollView 需要设置 keyboardShouldPersistTaps='handled',并把 contentContainerStyle 的 paddingBottom 与 keyboardOffset 联动。
  1. useEffect(() => {
  2.   const timer = setTimeout(() => {
  3.     inputRef.current?.focus();
  4.   }, 300);
  5.   return () => clearTimeout(timer);
  6. }, []);
  7. useEffect(() => {
  8.   const sub = Keyboard.addListener('keyboardDidShow', () => {
  9.     setShowHistory(false);
  10.   });
  11.   return () => sub.remove();
  12. }, []);
复制代码
  1. const focusNext = (currentField) => {
  2.   const fieldOrder = ['name', 'phone', 'email', 'address'];
  3.   const currentIndex = fieldOrder.indexOf(currentField);
  4.   if (currentIndex < fieldOrder.length - 1) {
  5.     const nextField = fieldOrder[currentIndex + 1];
  6.     inputs.current[nextField]?.focus();
  7.   } else {
  8.     Keyboard.dismiss();
  9.   }
  10. };
复制代码

五、KeyboardAvoidingView 与底部工具栏

KeyboardAvoidingView 可以和 Keyboard API 搭配使用。KeyboardAvoidingView 负责自动偏移,Keyboard 提供额外状态控制。原文示例中,鸿蒙上的 behavior 同样使用 padding,keyboardVerticalOffset 对 harmony 设为 0。底部工具栏则处理键盘、表情面板、语音面板之间的切换:切换到表情或语音时先 Keyboard.dismiss,避免两个面板同时出现;点击同一个按钮时,根据 keyboardVisible 决定收起键盘还是重新聚焦输入框。
  1. <KeyboardAvoidingView
  2.   behavior={Platform.OS === 'harmony' ? 'padding' : 'padding'}
  3.   style={{ flex: 1 }}
  4.   keyboardVerticalOffset={Platform.select({
  5.     ios: 88,
  6.     android: 0,
  7.     harmony: 0,
  8.   })}
  9. >
  10.   ...
  11. </KeyboardAvoidingView>
复制代码
  1. const switchTool = (mode) => {
  2.   if (mode === toolMode) {
  3.     if (keyboardVisible) {
  4.       Keyboard.dismiss();
  5.     } else {
  6.       inputRef.current?.focus();
  7.     }
  8.   } else {
  9.     setToolMode(mode);
  10.     Keyboard.dismiss();
  11.   }
  12. };
复制代码

六、鸿蒙上的四个坑

坑 1:will 系列事件不支持。鸿蒙跟 Android 一样,keyboardWillShow、keyboardWillHide、keyboardWillChangeFrame 不触发,只有 keyboardDidShow、keyboardDidHide、keyboardDidChangeFrame 可用。
  1. Keyboard.addListener('keyboardDidShow', handler);
  2. Keyboard.addListener('keyboardDidHide', handler);
  3. Keyboard.addListener('keyboardDidChangeFrame', handler);
  4. Keyboard.addListener('keyboardWillShow', handler);
  5. Keyboard.addListener('keyboardWillHide', handler);
  6. Keyboard.addListener('keyboardWillChangeFrame', handler);
复制代码

坑 2:键盘高度获取时机。鸿蒙上 keyboardDidShow 事件中的 endCoordinates.height 可能不准确,跟实际键盘高度有偏差。原文建议做兜底:如果高度小于 100 或大于 1000,就用 400 作为默认键盘高度。
  1. Keyboard.addListener('keyboardDidShow', (e) => {
  2.   let height = e.endCoordinates.height;
  3.   if (height < 100 || height > 1000) {
  4.     height = 400;
  5.   }
  6.   setKeyboardHeight(height);
  7. });
复制代码

坑 3:scheduleLayoutAnimation 不生效。鸿蒙上该方法可能不生效,因为底层依赖 iOS 的键盘通知机制。原文建议直接用状态控制偏移量。
  1. Keyboard.addListener('keyboardDidShow', (e) => {
  2.   setKeyboardHeight(e.endCoordinates.height);
  3.   setShowToolbar(true);
  4. });
复制代码

坑 4:isVisible 延迟。鸿蒙上 Keyboard.isVisible() 在 keyboardDidShow 回调执行后可能仍返回 false,因为底层状态还没更新。不要在鸿蒙上依赖 isVisible 做实时判断,建议用状态变量跟踪键盘状态。
  1. Keyboard.addListener('keyboardDidShow', () => {
  2.   const visible = Keyboard.isVisible();
  3.   setKeyboardOpen(true);
  4. });
复制代码

七、方法速查与适配建议

鸿蒙上用 keyboardDidShow 和 keyboardDidHide,别依赖 will 系列;监听事件要在 useEffect 中注册并清理,防止内存泄漏;用一个状态变量跟踪键盘状态,比 isVisible 更可靠;键盘高度做兜底,鸿蒙上获取到的高度可能异常;配合 KeyboardAvoidingView 使用,大多数场景下够用;scheduleLayoutAnimation 在鸿蒙不生效时,手动控制偏移量。原文还给出了键盘事件统计思路,可以用 useRef 记录 showCount、hideCount、lastShowTime、totalVisibleDuration,用于埋点分析用户输入习惯。

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

使用道具 举报

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

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

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

官方核心成员

关注微信公众号

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

GMT+8, 2026-9-15 12:57 , Processed in 0.019452 second(s), 18 queries , Gzip On, Redis On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部