已解决:React Native中ScrollView聚焦输入框时滚动过度问题
解决React Native ScrollView聚焦输入框后滚动位置过高的问题
问题描述
点击输入框后,ScrollView会将输入框滚动到导航栏上方的位置,需求是让聚焦后的输入框刚好位于导航栏下方。
核心原因
当前ScrollView的自动键盘适配未考虑导航栏高度,导致滚动偏移量计算错误。以下是几种针对性的解决方案:
方案1:使用KeyboardAvoidingView适配键盘与导航栏
这是最直接的官方解决方案,通过包裹ScrollView并设置对应导航栏高度的偏移量,让系统自动调整滚动位置。
修改wrapper.js:
import React, { useState, useCallback } from 'react'; import { ScrollView, View, KeyboardAvoidingView, RefreshControl, Platform } from 'react-native'; import {styles} from '../assets/styles/components/Wrapper' export default function Wrapper(props){ const [refreshing, setRefreshing] = useState(false); const onRefresh = useCallback(() => { setRefreshing(true); setTimeout(() => { setRefreshing(false); }, 2000); }, []); return ( {/* 用KeyboardAvoidingView包裹ScrollView */} <KeyboardAvoidingView behavior={Platform.OS === "ios" ? "padding" : "height"} // 偏移量与导航栏高度一致(WrapperWithNavbar中设置的paddingTop为110) keyboardVerticalOffset={Platform.OS === "ios" ? 110 : 0} style={{ flex: 1 }} > <ScrollView automaticallyAdjustKeyboardInsets={true} refreshControl={ <RefreshControl refreshing={props.refreshing || false} onRefresh={props.onRefresh} /> } style={[styles.wrapper, props.style]} > <View style={[styles.wrapperInner, props.innerStyle]}> {props.children} </View> </ScrollView> </KeyboardAvoidingView> ) }
方案2:手动控制输入框聚焦时的滚动位置
如果方案1无法满足需求,可以通过测量输入框位置,手动调用ScrollView的滚动方法,精准控制滚动位置。
步骤1:修改wrapper.js,暴露ScrollView的ref
import React, { useState, useCallback, useRef } from 'react'; import { ScrollView, View, RefreshControl } from 'react-native'; import {styles} from '../assets/styles/components/Wrapper' export default function Wrapper(props){ const scrollViewRef = useRef(null); const [refreshing, setRefreshing] = useState(false); const onRefresh = useCallback(() => { setRefreshing(true); setTimeout(() => { setRefreshing(false); }, 2000); }, []); // 提供滚动到指定位置的方法给父组件 const scrollToPosition = (y) => { scrollViewRef.current?.scrollTo({ y, animated: true }); } return ( <ScrollView ref={scrollViewRef} automaticallyAdjustKeyboardInsets={true} refreshControl={ <RefreshControl refreshing={props.refreshing || false} onRefresh={props.onRefresh} /> } style={[styles.wrapper, props.style]} > <View style={[styles.wrapperInner, props.innerStyle]}> {/* 将滚动方法传递给子元素 */} {React.cloneElement(props.children, { scrollToPosition })} </View> </ScrollView> ) }
步骤2:修改输入框页面,处理聚焦事件
import React, { useState, useRef } from 'react'; import { Text, View, Platform } from 'react-native'; import {useNavigation} from '@react-navigation/native'; // components import WrapperWithNavbar from '../../components/WrapperWithNavbar' import Header from '../../components/Header' import Button from '../../components/Button' import Input from '../../components/Input' export default function CreateEventCultureplace(props){ const navigation = useNavigation(); const [event, setEvent] = useState(''); const inputRef = useRef(null); const { scrollToPosition } = props; const handleInputFocus = () => { inputRef.current?.measure((_x, _y, _width, _height, _pageX, pageY) => { // 计算滚动位置:输入框的页面顶部位置 - 导航栏高度 const targetY = pageY - 110; scrollToPosition(targetY); }); } return ( <WrapperWithNavbar type="CULTUREPLACE"> <Header title="Opret begivenhed"/> <Input ref={inputRef} value={event} onChangeText={setEvent} type="text" title="Begivenhed (f.eks. open mic)" onFocus={handleInputFocus} /> <Button onPress={()=>{allowPress && createEvent()}} style={[!allowPress?{backgroundColor:"#ffb38a"}:null,{"marginBottom":50}]} title="Opret begivenhed"/> {Platform.OS!="web" && <View style={{"height":100}}></View>} </WrapperWithNavbar> ) }
步骤3:修改input.js,支持ref传递
import React, { useState, forwardRef } from 'react'; import {SafeAreaView, Text, TextInput, Platform} from 'react-native'; import {styles} from '../assets/styles/components/Input' // 使用forwardRef让父组件可以获取TextInput的ref const Input = forwardRef((props, ref) => { const [isFocused, setIsFocused] = useState(false); return ( <SafeAreaView style={[props.style,styles.wrapper]}> <Text style={styles.text}>{props.title}</Text> <TextInput ref={ref} scrollEnabled={false} numberOfLines={props.numberOfLines} multiline={props.multiline === true} onKeyPress={props.onKeyPress} onFocus={() => { setIsFocused(true); props.onFocus?.(); }} onBlur={() => setIsFocused(false)} autofocus={props.autofocus || false} style={[styles.input, props.inputStyle, isFocused && !props.readonly ? styles.focused : '', Platform.OS=='web' && styles.web]} value={props.value} onChangeText={props.onChangeText} placeholder={props.title} placeholderTextColor="#777" autocomplete={props.autocomplete} secureTextEntry={props.type === "password"} editable={!props.readonly} /> </SafeAreaView> ); }) export default Input;
方案3:调整ScrollView的内容内边距
通过设置contentInset和scrollIndicatorInsets,让ScrollView的内容默认留出导航栏高度的空间,避免滚动过度:
修改wrapper.js中的ScrollView:
<ScrollView automaticallyAdjustKeyboardInsets={true} refreshControl={ <RefreshControl refreshing={props.refreshing || false} onRefresh={props.onRefresh} /> } style={[styles.wrapper, props.style]} // 设置内容内边距,顶部与导航栏高度一致 contentInset={{ top: Platform.OS === 'ios' ? 110 : 0 }} scrollIndicatorInsets={{ top: Platform.OS === 'ios' ? 110 : 0 }} > {/* ...子元素 */} </ScrollView>
推荐方案
优先选择方案1,因为KeyboardAvoidingView是React Native官方提供的键盘适配组件,配置简单且兼容性好,只需确保keyboardVerticalOffset的值与导航栏实际高度匹配即可。
内容的提问来源于stack exchange,提问作者Lukas Guldstøv
相关产品推荐
相关产品推荐

