React Native:KeyboardAvoidingView与SlidingUpPanel收起键盘后面板不归位
React Native滑动面板收起键盘后无法复位的解决方法
问题场景
我实现了一个快速登录界面,使用rn-sliding-up-panel组件作为密码登录面板。调用panelRef.current.show()显示面板时尺寸正常,但打开键盘后面板会上移,收起键盘后无法回到初始位置。
原组件代码
import { useEffect, useRef, useState } from "react"; import { KeyboardAvoidingView, Pressable, ScrollView, StyleSheet, TextInput, View, } from "react-native"; import colors from "../config/colors"; import SlidingUpPanel from "rn-sliding-up-panel"; import Header from "../components/common/Header"; import Text from "../components/Text"; import PasswordIcon from "../assets/svg/Login/PasswordIcon"; import Separator from "../assets/svg/Login/Separator"; import Checkbox from "expo-checkbox"; import Button from "../components/common/Button"; import { useNavigation } from "@react-navigation/native"; import FaceID from "../assets/svg/QuickLogin/FaceID"; import InsrgramButton from "../assets/svg/QuickLogin/InsrgramButton"; import WhatsappButton from "../assets/svg/QuickLogin/WhatsappButton"; import XButton from "../assets/svg/QuickLogin/XButton"; import DefaultAvatar from "../assets/svg/QuickLogin/DefaultAvatar"; import * as LocalAuthentication from "expo-local-authentication"; const QuickLoginScreen = () => { const panelRef = useRef(null); const [isChecked, setChecked] = useState(); const navigation = useNavigation(); const draggableRange = { top: 320, // 完全展开的位置 bottom: 0, // 收起位置 }; const handleFaceID = () => { LocalAuthentication.authenticateAsync({ promptMessage: "تسجيل دخول لمرشحكم", }) .then((r) => console.log(r)) .catch((e) => console.log(e)); }; const handleLogin = () => { navigation.navigate("Home"); }; return ( <View style={styles.container}> <Header title="تسجيل الدخول" /> <View style={styles.welcomeTextContainer}> <View style={styles.avatarContainer}> <DefaultAvatar /> </View> <Text size="14" style={{ marginTop: 16, marginBottom: 10 }} bold> أهلا بك </Text> <Text size="14" bold> هاشم عبدالله </Text> </View> <Pressable onPress={handleFaceID}> <FaceID /> </Pressable> <View> <Pressable onPress={() => panelRef.current.show()} style={styles.passwordLoginContainer} > <Text size="12" style={styles.passwordLoginText} bold> أو قم بتسجيل الدخول باستخدام </Text> <Text size="12" style={{ color: colors.green, marginLeft: 4 }} bold> كلمة المرور </Text> </Pressable> </View> <View style={styles.socialLoginButtonsContainer}> <XButton /> <InsrgramButton /> <WhatsappButton /> </View> <KeyboardAvoidingView behavior="height" style={{ flex: 1 }} keyboardVerticalOffset={Platform.OS === "ios" ? 0 : -500} > <SlidingUpPanel ref={panelRef} draggableRange={draggableRange} showBackdrop={false} initialAnimatedValue={0} containerStyle={styles.panelContainer} > <ScrollView contentContainerStyle={styles.panel}> <View style={styles.panelButton} /> <View style={styles.panelContents}> <Text size="13" style={styles.enterPasswordText}> ادخل كلمة السر </Text> <View style={styles.textInputContainer}> <PasswordIcon /> <Text size="10" style={styles.inputLabel}> Pass </Text> <Separator /> <TextInput style={styles.textInput} secureTextEntry={!isChecked} /> </View> <View style={styles.showPasswordContainer}> <Text size="12">اظهار كلمة المرور</Text> <Checkbox style={styles.checkbox} value={isChecked} onValueChange={setChecked} color={isChecked ? colors.green : undefined} /> </View> <View style={styles.linksContainer}> <Text size="12" style={{ ...styles.textLinks, marginBottom: 12 }} > إستعادة كلمة المرور ؟ </Text> <Text size="12" style={styles.textLinks}> إستعادة اسم المستخدم ؟ </Text> </View> <Button title="تسجيل الدخول" style={{ marginTop: 24 }} onPress={handleLogin} /> </View> </ScrollView> </SlidingUpPanel> </KeyboardAvoidingView> </View> ); }; const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: "white", }, welcomeTextContainer: { justifyContent: "center", alignItems: "center", marginTop: 86, }, avatarContainer: { justifyContent: "center", alignItems: "center", height: 104, width: 104, borderColor: colors.green, borderWidth: 2, borderRadius: 70, }, passwordLoginContainer: { flexDirection: "row", justifyContent: "center", marginTop: 25, height: 30, }, passwordLoginText: { textAlign: "center", }, socialLoginButtonsContainer: { flexDirection: "row-reverse", justifyContent: "center", alignItems: "center", marginTop: 63, }, panelContainer: { position: "absolute", bottom: 0, left: 0, right: 0, zIndex: 10, shadowColor: "#000", shadowOffset: { width: 0, height: 6, }, shadowOpacity: 0.37, shadowRadius: 7.49, elevation: 12, }, panel: { backgroundColor: "white", alignItems: "center", height: 320, borderTopRightRadius: 10, borderTopLeftRadius: 10, }, panelContents: { marginTop: 19, }, panelButton: { backgroundColor: colors.lgray4, width: 47, height: 5, marginTop: 8, borderRadius: 10, }, enterPasswordText: { textAlign: "center", marginBottom: 16.2, color: colors.lgray5, }, textInputContainer: { flexDirection: "row-reverse", alignItems: "center", borderColor: colors.lightGreen, borderWidth: 1, height: 48, width: 327, borderRadius: 8, marginBottom: 12, }, textInput: { textAlign: "left", height: "100%", width: 150, marginRight: 10.79, }, inputLabel: { color: colors.lgray1, marginRight: 12, marginLeft: 12.21, }, showPasswordContainer: { flexDirection: "row", marginBottom: 12, }, checkbox: { marginLeft: "auto", borderRadius: 5, }, linksContainer: {}, textLinks: { color: colors.lightGreen, textAlign: "left", }, }); export default QuickLoginScreen;
解决方法
1. 监听键盘收起事件,强制面板复位
添加Keyboard模块的监听,在键盘收起时将面板拉回初始位置:
- 首先导入
Keyboard:
import { KeyboardAvoidingView, Pressable, ScrollView, StyleSheet, TextInput, View, Keyboard, // 新增 } from "react-native";
- 然后在组件内添加
useEffect监听:
useEffect(() => { const keyboardHideListener = Keyboard.addListener('keyboardDidHide', () => { if (panelRef.current) { // 调用show()让面板回到完全展开的初始位置 panelRef.current.show(); // 或者用setAnimatedValue精准设置到draggableRange的bottom值 // panelRef.current.setAnimatedValue(0); } }); // 组件卸载时移除监听 return () => keyboardHideListener.remove(); }, []);
2. 优化KeyboardAvoidingView配置
将behavior改为平台自适应,移除不合理的keyboardVerticalOffset负值:
<KeyboardAvoidingView behavior={Platform.OS === "ios" ? "padding" : "height"} style={{ flex: 1 }} >
修改后的完整组件代码
import { useEffect, useRef, useState } from "react"; import { KeyboardAvoidingView, Pressable, ScrollView, StyleSheet, TextInput, View, Keyboard, } from "react-native"; import colors from "../config/colors"; import SlidingUpPanel from "rn-sliding-up-panel"; import Header from "../components/common/Header"; import Text from "../components/Text"; import PasswordIcon from "../assets/svg/Login/PasswordIcon"; import Separator from "../assets/svg/Login/Separator"; import Checkbox from "expo-checkbox"; import Button from "../components/common/Button"; import { useNavigation } from "@react-navigation/native"; import FaceID from "../assets/svg/QuickLogin/FaceID"; import InsrgramButton from "../assets/svg/QuickLogin/InsrgramButton"; import WhatsappButton from "../assets/svg/QuickLogin/WhatsappButton"; import XButton from "../assets/svg/QuickLogin/XButton"; import DefaultAvatar from "../assets/svg/QuickLogin/DefaultAvatar"; import * as LocalAuthentication from "expo-local-authentication"; const QuickLoginScreen = () => { const panelRef = useRef(null); const [isChecked, setChecked] = useState(); const navigation = useNavigation(); const draggableRange = { top: 320, // 完全展开的位置 bottom: 0, // 收起位置 }; useEffect(() => { const keyboardHideListener = Keyboard.addListener('keyboardDidHide', () => { if (panelRef.current) { panelRef.current.show(); } }); return () => keyboardHideListener.remove(); }, []); const handleFaceID = () => { LocalAuthentication.authenticateAsync({ promptMessage: "تسجيل دخول لمرشحكم", }) .then((r) => console.log(r)) .catch((e) => console.log(e)); }; const handleLogin = () => { navigation.navigate("Home"); }; return ( <View style={styles.container}> <Header title="تسجيل الدخول" /> <View style={styles.welcomeTextContainer}> <View style={styles.avatarContainer}> <DefaultAvatar /> </View> <Text size="14" style={{ marginTop: 16, marginBottom: 10 }} bold> أهلا بك </Text> <Text size="14" bold> هاشم عبدالله </Text> </View> <Pressable onPress={handleFaceID}> <FaceID /> </Pressable> <View> <Pressable onPress={() => panelRef.current.show()} style={styles.passwordLoginContainer} > <Text size="12" style={styles.passwordLoginText} bold> أو قم بتسجيل الدخول باستخدام </Text> <Text size="12" style={{ color: colors.green, marginLeft: 4 }} bold> كلمة المرور </Text> </Pressable> </View> <View style={styles.socialLoginButtonsContainer}> <XButton /> <InsrgramButton /> <WhatsappButton /> </View> <KeyboardAvoidingView behavior={Platform.OS === "ios" ? "padding" : "height"} style={{ flex: 1 }} > <SlidingUpPanel ref={panelRef} draggableRange={draggableRange} showBackdrop={false} initialAnimatedValue={0} containerStyle={styles.panelContainer} > <ScrollView contentContainerStyle={styles.panel}> <View style={styles.panelButton} /> <View style={styles.panelContents}> <Text size="13" style={styles.enterPasswordText}> ادخل كلمة السر </Text> <View style={styles.textInputContainer}> <PasswordIcon /> <Text size="10" style={styles.inputLabel}> Pass </Text> <Separator /> <TextInput style={styles.textInput} secureTextEntry={!isChecked} /> </View> <View style={styles.showPasswordContainer}> <Text size="12">اظهار كلمة المرور</Text> <Checkbox style={styles.checkbox} value={isChecked} onValueChange={setChecked} color={isChecked ? colors.green : undefined} /> </View> <View style={styles.linksContainer}> <Text size="12" style={{ ...styles.textLinks, marginBottom: 12 }} > إستعادة كلمة المرور ؟ </Text> <Text size="12" style={styles.textLinks}> إستعادة اسم المستخدم ؟ </Text> </View> <Button title="تسجيل الدخول" style={{ marginTop: 24 }} onPress={handleLogin} /> </View> </ScrollView> </SlidingUpPanel> </KeyboardAvoidingView> </View> ); }; const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: "white", }, welcomeTextContainer: { justifyContent: "center", alignItems: "center", marginTop: 86, }, avatarContainer: { justifyContent: "center", alignItems: "center", height: 104, width: 104, borderColor: colors.green, borderWidth: 2, borderRadius: 70, }, passwordLoginContainer: { flexDirection: "row", justifyContent: "center", marginTop: 25, height: 30, }, passwordLoginText: { textAlign: "center", }, socialLoginButtonsContainer: { flexDirection: "row-reverse", justifyContent: "center", alignItems: "center", marginTop: 63, }, panelContainer: { position: "absolute", bottom: 0, left: 0, right: 0, zIndex: 10, shadowColor: "#000", shadowOffset: { width: 0, height: 6, }, shadowOpacity: 0.37, shadowRadius: 7.49, elevation: 12, }, panel: { backgroundColor: "white", alignItems: "center", height: 320, borderTopRightRadius: 10, borderTopLeftRadius: 10, }, panelContents: { marginTop: 19, }, panelButton: { backgroundColor: colors.lgray4, width: 47, height: 5, marginTop: 8, borderRadius: 10, }, enterPasswordText: { textAlign: "center", marginBottom: 16.2, color: colors.l
相关产品推荐
相关产品推荐

