You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.31 05:03:36