核心問題
在 React Native 中為 TextInput 添加自訂鍵盤工具欄,支援文字插入到游標位置,且按鈕點擊時不關閉鍵盤。
關鍵技術要點
1. 文字插入到 Cursor (游標)位置的核心實現
const insertTextAtCursor = (insertString) => {
const { start, end } = selection;
const beforeText = text.substring(0, start);
const afterText = text.substring(end);
const newText = beforeText + insertString + afterText;
const newCursorPosition = start + insertString.length;
// 更新文字
setText(newText);
// 更新選擇位置
const newSelection = { start: newCursorPosition, end: newCursorPosition };
setSelection(newSelection);
// 立即重新聚焦,防止鍵盤關閉
if (textInputRef.current) {
textInputRef.current.focus();
// 延遲設置光標位置
setTimeout(() => {
if (textInputRef.current) {
textInputRef.current.setNativeProps({
selection: newSelection
});
// 再次確保焦點
textInputRef.current.focus();
}
}, 50);
}
};
2. 狀態管理
const [selection, setSelection] = useState({ start: 0, end: 0 });
const textInputRef = useRef(null);
// 處理選擇變化
const handleSelectionChange = (event) => {
setSelection(event.nativeEvent.selection);
};
3. TextInput 配置
<TextInput
ref={textInputRef}
onSelectionChange={handleSelectionChange}
selection={selection}
// 其他屬性...
/>
4. 防止鍵盤關閉的關鍵設定
ScrollView 配置
<ScrollView
horizontal
keyboardShouldPersistTaps="always" // 關鍵:允許點擊時保持鍵盤
keyboardDismissMode="none" // 關鍵:禁止滑動關閉鍵盤
>
按鈕事件處理
<TouchableOpacity
onPress={() => {
insertTextAtCursor(button.text);
}}
onPressIn={() => {
// 按下時立即聚焦,防止失焦
if (textInputRef.current) {
textInputRef.current.focus();
}
}}
delayPressIn={0} // 減少延遲
delayPressOut={0} // 減少延遲
hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }} // 增大點擊區域
>
5. 鍵盤監聽和工具欄定位
// 監聽鍵盤事件
useEffect(() => {
const keyboardDidShow = Keyboard.addListener('keyboardDidShow', (event) => {
setKeyboardHeight(event.endCoordinates.height);
setKeyboardVisible(true);
});
const keyboardDidHide = Keyboard.addListener('keyboardDidHide', () => {
setKeyboardVisible(false);
setKeyboardHeight(0);
});
return () => {
keyboardDidShow?.remove();
keyboardDidHide?.remove();
};
}, []);
// 工具欄定位
<View
style={[
styles.toolbar,
{
bottom: Platform.OS === 'ios' ? keyboardHeight - 34 : keyboardHeight,
marginBottom: 35 // 確保不被遮擋
}
]}
pointerEvents="box-none" // 允許子元素接收觸摸事件
>
失敗的方案
❌ react-native-keyboard-controller
- 問題:與 Expo 相容性問題,出現 "doesn't seem to be linked" 錯誤
- 原因:需要原生鏈接,Expo 環境難以配置
❌ InputAccessoryView
- 問題:官方文檔明確指出不支援 multiline TextInput
- 限制:只適用於單行輸入
❌ 簡單的 onPress 處理
- 問題:點擊按鈕會導致 TextInput 失焦,鍵盤關閉
- 原因:沒有正確處理焦點管理和事件傳播
成功關鍵因素
- 雙重焦點保證:onPressIn 和 onPress 都確保 TextInput 保持焦點
- 延遲設置光標:使用 setTimeout 確保 setNativeProps 生效
- 正確的 ScrollView 配置:keyboardShouldPersistTaps 和 keyboardDismissMode
- 精確的字串操作:使用 substring 而非正規表達式進行文字插入
- 適當的工具欄定位:考慮平台差異和安全區域
測試驗證
✅ 點擊按鈕時鍵盤保持顯示
✅ 文字正確插入到游標位置
✅ 工具欄不被鍵盤遮擋
✅ 支援多行文字編輯
✅ 平台相容性 (iOS/Android)
總結
成功的關鍵在於理解 React Native 的焦點管理機制,正確配置 ScrollView 的鍵盤行為,以及使用適當的延遲策略來確保原生屬性設置生效。避免使用第三方函式庫,採用原生 API 組合實現更加穩定可靠。