如何在react-native-google-places-autocomplete中实现自定义ListView及双输入复用?
实现双输入框共用自定义ListView的地点 Autocomplete
核心思路
通过父组件统一管理搜索状态(搜索结果、当前激活的输入框),禁用react-native-google-places-autocomplete自带的列表,改用自定义FlatList作为共用下拉面板,点击输入框时触发搜索并绑定对应输入框的回填逻辑。
实现步骤及代码示例
1. 父组件状态定义与核心逻辑
import { useState, useCallback } from 'react'; import { View, FlatList, TouchableOpacity, Text, TouchableWithoutFeedback, Keyboard } from 'react-native'; import { GooglePlacesAutocomplete } from 'react-native-google-places-autocomplete'; const DestinationSelector = () => { // 输入框值 const [fromValue, setFromValue] = useState(''); const [toValue, setToValue] = useState(''); // 搜索结果列表 const [places, setPlaces] = useState([]); // 当前激活的输入框标识('from'/'to') const [activeInput, setActiveInput] = useState(null); // Google Places API Key(替换为你的密钥) const API_KEY = 'YOUR_GOOGLE_PLACES_API_KEY'; // 防抖搜索函数,避免频繁调用API const debounceSearch = useCallback( (() => { let timeoutId; return (query) => { clearTimeout(timeoutId); timeoutId = setTimeout(() => fetchPlaces(query), 300); }; })(), [] ); // 调用Google Places Autocomplete API获取结果 const fetchPlaces = async (query) => { if (!query.trim()) { setPlaces([]); return; } try { const response = await fetch( `https://maps.googleapis.com/maps/api/place/autocomplete/json?input=${encodeURIComponent(query)}&key=${API_KEY}&language=zh-CN` ); const data = await response.json(); setPlaces(data.status === 'OK' ? data.predictions : []); } catch (err) { console.error('地点搜索失败:', err); setPlaces([]); } }; // 处理列表项点击,回填到对应输入框 const handlePlaceSelect = (place) => { if (activeInput === 'from') { setFromValue(place.description); } else { setToValue(place.description); } setPlaces([]); setActiveInput(null); Keyboard.dismiss(); }; return ( <TouchableWithoutFeedback onPress={() => { setPlaces([]); setActiveInput(null); Keyboard.dismiss(); }}> <View style={{ padding: 16, flex: 1 }}> {/* 起点输入框 */} <GooglePlacesAutocomplete placeholder="输入起点" value={fromValue} onChangeText={(text) => { setFromValue(text); debounceSearch(text); setActiveInput('from'); }} onFocus={() => { setActiveInput('from'); if (fromValue) debounceSearch(fromValue); }} // 禁用组件自带的列表 listViewDisplayed={false} // 隐藏组件默认的下拉图标(可选) renderRightButton={() => null} style={{ container: { marginBottom: 12 }, textInput: { borderWidth: 1, borderColor: '#ddd', borderRadius: 8, padding: 12, }, }} /> {/* 终点输入框 */} <GooglePlacesAutocomplete placeholder="输入终点" value={toValue} onChangeText={(text) => { setToValue(text); debounceSearch(text); setActiveInput('to'); }} onFocus={() => { setActiveInput('to'); if (toValue) debounceSearch(toValue); }} listViewDisplayed={false} renderRightButton={() => null} style={{ container: { marginBottom: 12 }, textInput: { borderWidth: 1, borderColor: '#ddd', borderRadius: 8, padding: 12, }, }} /> {/* 共用的自定义下拉列表 */} {activeInput && places.length > 0 && ( <View style={{ backgroundColor: '#fff', borderWidth: 1, borderColor: '#ddd', borderRadius: 8, maxHeight: 300, position: 'absolute', top: activeInput === 'from' ? 70 : 140, left: 16, right: 16, zIndex: 100, }}> <FlatList data={places} keyExtractor={(item) => item.place_id} renderItem={({ item }) => ( <TouchableOpacity onPress={() => handlePlaceSelect(item)} style={{ padding: 12, borderBottomWidth: 1, borderBottomColor: '#f0f0f0' }} > <Text>{item.description}</Text> </TouchableOpacity> )} ItemSeparatorComponent={() => <View style={{ height: 1, backgroundColor: '#f0f0f0' }} />} /> </View> )} </View> </TouchableWithoutFeedback> ); }; export default DestinationSelector;
2. 关键细节说明
- 禁用自带列表:通过
listViewDisplayed={false}关闭组件默认下拉面板,完全自定义列表展示逻辑。 - 防抖搜索:添加300ms防抖,避免用户输入过程中频繁调用API,优化性能。
- 激活状态绑定:通过
activeInput标记当前聚焦的输入框,确保列表点击后的值回填到正确输入框。 - 外部点击关闭:用
TouchableWithoutFeedback包裹组件,点击外部区域时清空列表并收起键盘,提升交互体验。 - 列表定位:根据当前激活的输入框动态设置列表
top值,确保列表始终显示在对应输入框下方。
注意事项
- 确保已在Google Cloud Console中启用Places API,并正确配置API密钥的访问权限(如允许你的APP包名/域名访问)。
- 可根据需求自定义列表项样式,比如添加地点图标、结构化地址展示等。
- 如果需要获取地点详细信息(如经纬度),可在
handlePlaceSelect中调用Place Details API,传入选中项的place_id获取更多数据。
内容的提问来源于stack exchange,提问作者Özlem Yücel
相关产品推荐
相关产品推荐

