React Native定位服务 #

概述 #

定位服务是移动应用的常见功能,用于获取设备的地理位置。本章节介绍如何在 React Native 中使用定位服务。

权限配置 #

iOS 权限 #

ios/MyApp/Info.plist 中添加:

xml
<key>NSLocationWhenInUseUsageDescription</key>
<string>需要访问您的位置以提供相关服务</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>需要始终访问您的位置以提供后台定位服务</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>需要访问您的位置以提供相关服务</string>

Android 权限 #

android/app/src/main/AndroidManifest.xml 中添加:

xml
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />

@react-native-community/geolocation #

React Native 官方维护的定位库。

安装 #

bash
npm install @react-native-community/geolocation
cd ios && pod install

获取当前位置 #

tsx
import React, {useState} from 'react';
import {View, Text, Button, StyleSheet, Alert} from 'react-native';
import Geolocation from '@react-native-community/geolocation';

interface Position {
  latitude: number;
  longitude: number;
  accuracy: number;
  timestamp: number;
}

const LocationExample = () => {
  const [position, setPosition] = useState<Position | null>(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);

  const getCurrentPosition = () => {
    setLoading(true);
    setError(null);

    Geolocation.getCurrentPosition(
      pos => {
        setPosition({
          latitude: pos.coords.latitude,
          longitude: pos.coords.longitude,
          accuracy: pos.coords.accuracy,
          timestamp: pos.timestamp,
        });
        setLoading(false);
      },
      err => {
        setError(err.message);
        setLoading(false);
        Alert.alert('定位失败', err.message);
      },
      {
        enableHighAccuracy: true,
        timeout: 15000,
        maximumAge: 10000,
      },
    );
  };

  return (
    <View style={styles.container}>
      {loading && <Text>正在获取位置...</Text>}
      {error && <Text style={styles.error}>{error}</Text>}
      {position && (
        <View style={styles.info}>
          <Text style={styles.label}>纬度:</Text>
          <Text style={styles.value}>{position.latitude}</Text>
          <Text style={styles.label}>经度:</Text>
          <Text style={styles.value}>{position.longitude}</Text>
          <Text style={styles.label}>精度:</Text>
          <Text style={styles.value}>{position.accuracy}米</Text>
        </View>
      )}
      <Button title="获取当前位置" onPress={getCurrentPosition} />
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    padding: 16,
  },
  info: {
    marginBottom: 20,
    alignItems: 'center',
  },
  label: {
    fontSize: 14,
    color: '#666',
    marginTop: 8,
  },
  value: {
    fontSize: 18,
    fontWeight: 'bold',
  },
  error: {
    color: '#FF3B30',
    marginBottom: 16,
  },
});

export default LocationExample;

位置配置选项 #

tsx
interface GeolocationOptions {
  enableHighAccuracy?: boolean;  // 使用高精度定位
  timeout?: number;              // 超时时间(毫秒)
  maximumAge?: number;           // 缓存最大时间(毫秒)
  distanceFilter?: number;       // 最小更新距离(米)
  forceRequestLocation?: boolean; // 强制请求位置
  showLocationDialog?: boolean;  // 显示位置设置对话框
}

Geolocation.getCurrentPosition(
  successCallback,
  errorCallback,
  {
    enableHighAccuracy: true,
    timeout: 15000,
    maximumAge: 10000,
    distanceFilter: 10,
  },
);

位置追踪 #

监听位置变化 #

tsx
import React, {useState, useEffect, useRef} from 'react';
import {View, Text, StyleSheet} from 'react-native';
import Geolocation from '@react-native-community/geolocation';

const LocationTracker = () => {
  const [position, setPosition] = useState<Position | null>(null);
  const watchIdRef = useRef<number | null>(null);

  useEffect(() => {
    startWatching();

    return () => {
      stopWatching();
    };
  }, []);

  const startWatching = () => {
    watchIdRef.current = Geolocation.watchPosition(
      pos => {
        setPosition({
          latitude: pos.coords.latitude,
          longitude: pos.coords.longitude,
          accuracy: pos.coords.accuracy,
          timestamp: pos.timestamp,
          speed: pos.coords.speed,
          heading: pos.coords.heading,
        });
      },
      error => {
        console.error('Watch position error:', error);
      },
      {
        enableHighAccuracy: true,
        distanceFilter: 10,
        interval: 5000,
        fastestInterval: 2000,
      },
    );
  };

  const stopWatching = () => {
    if (watchIdRef.current !== null) {
      Geolocation.clearWatch(watchIdRef.current);
      watchIdRef.current = null;
    }
  };

  return (
    <View style={styles.container}>
      <Text style={styles.title}>实时位置追踪</Text>
      {position && (
        <View style={styles.info}>
          <Text>纬度: {position.latitude}</Text>
          <Text>经度: {position.longitude}</Text>
          <Text>速度: {position.speed} m/s</Text>
          <Text>方向: {position.heading}°</Text>
        </View>
      )}
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  title: {
    fontSize: 20,
    fontWeight: 'bold',
    marginBottom: 20,
  },
  info: {
    alignItems: 'center',
  },
});

export default LocationTracker;

react-native-geolocation-service #

功能更强大的定位库,支持后台定位。

安装 #

bash
npm install react-native-geolocation-service
cd ios && pod install

使用示例 #

tsx
import Geolocation from 'react-native-geolocation-service';
import {Platform, PermissionsAndroid} from 'react-native';

const requestLocationPermission = async (): Promise<boolean> => {
  if (Platform.OS === 'ios') {
    const auth = await Geolocation.requestAuthorization('whenInUse');
    return auth === 'granted';
  }

  if (Platform.OS === 'android') {
    const granted = await PermissionsAndroid.request(
      PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
    );
    return granted === PermissionsAndroid.RESULTS.GRANTED;
  }

  return false;
};

const getLocation = async () => {
  const hasPermission = await requestLocationPermission();
  if (!hasPermission) {
    return null;
  }

  return new Promise((resolve, reject) => {
    Geolocation.getCurrentPosition(
      position => {
        resolve(position);
      },
      error => {
        reject(error);
      },
      {
        enableHighAccuracy: true,
        timeout: 15000,
        maximumAge: 10000,
        forceRequestLocation: true,
      },
    );
  });
};

距离计算 #

使用 Haversine 公式 #

tsx
const calculateDistance = (
  lat1: number,
  lon1: number,
  lat2: number,
  lon2: number,
): number => {
  const R = 6371; // 地球半径(公里)
  const dLat = toRad(lat2 - lat1);
  const dLon = toRad(lon2 - lon1);
  const a =
    Math.sin(dLat / 2) * Math.sin(dLat / 2) +
    Math.cos(toRad(lat1)) *
      Math.cos(toRad(lat2)) *
      Math.sin(dLon / 2) *
      Math.sin(dLon / 2);
  const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
  const distance = R * c;
  return distance;
};

const toRad = (deg: number): number => {
  return deg * (Math.PI / 180);
};

// 使用示例
const distance = calculateDistance(
  39.9042, 116.4074, // 北京
  31.2304, 121.4737, // 上海
);
console.log(`距离: ${distance.toFixed(2)} 公里`);

地理编码 #

使用第三方 API #

tsx
interface Address {
  street: string;
  city: string;
  state: string;
  country: string;
  postalCode: string;
}

const reverseGeocode = async (
  latitude: number,
  longitude: number,
): Promise<Address | null> => {
  try {
    const response = await fetch(
      `https://nominatim.openstreetmap.org/reverse?lat=${latitude}&lon=${longitude}&format=json`,
    );
    const data = await response.json();

    return {
      street: data.address?.road || '',
      city: data.address?.city || data.address?.town || '',
      state: data.address?.state || '',
      country: data.address?.country || '',
      postalCode: data.address?.postcode || '',
    };
  } catch (error) {
    console.error('Geocoding error:', error);
    return null;
  }
};

// 使用示例
const address = await reverseGeocode(39.9042, 116.4074);
console.log(address);

地图显示 #

使用 react-native-maps #

bash
npm install react-native-maps
tsx
import React from 'react';
import {View, StyleSheet} from 'react-native';
import MapView, {Marker, PROVIDER_GOOGLE} from 'react-native-maps';

const MapExample = () => {
  const initialRegion = {
    latitude: 39.9042,
    longitude: 116.4074,
    latitudeDelta: 0.0922,
    longitudeDelta: 0.0421,
  };

  return (
    <View style={styles.container}>
      <MapView
        provider={PROVIDER_GOOGLE}
        style={styles.map}
        initialRegion={initialRegion}
        showsUserLocation
        showsMyLocationButton>
        <Marker
          coordinate={{
            latitude: 39.9042,
            longitude: 116.4074,
          }}
          title="北京"
          description="中国首都"
        />
      </MapView>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  map: {
    flex: 1,
  },
});

export default MapExample;

完整示例 #

tsx
import React, {useState, useEffect, useRef} from 'react';
import {
  View,
  Text,
  Button,
  StyleSheet,
  Alert,
  ActivityIndicator,
  TouchableOpacity,
} from 'react-native';
import Geolocation from 'react-native-geolocation-service';
import {PermissionsAndroid, Platform} from 'react-native';

interface LocationData {
  latitude: number;
  longitude: number;
  accuracy: number;
  timestamp: number;
  address?: string;
}

const LocationService = () => {
  const [location, setLocation] = useState<LocationData | null>(null);
  const [loading, setLoading] = useState(false);
  const [tracking, setTracking] = useState(false);
  const watchIdRef = useRef<number | null>(null);

  useEffect(() => {
    return () => {
      if (watchIdRef.current !== null) {
        Geolocation.clearWatch(watchIdRef.current);
      }
    };
  }, []);

  const requestPermission = async (): Promise<boolean> => {
    if (Platform.OS === 'ios') {
      const auth = await Geolocation.requestAuthorization('whenInUse');
      return auth === 'granted';
    }

    const granted = await PermissionsAndroid.request(
      PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
      {
        title: '定位权限',
        message: '需要访问您的位置以提供服务',
        buttonNeutral: '稍后询问',
        buttonNegative: '取消',
        buttonPositive: '确定',
      },
    );

    return granted === PermissionsAndroid.RESULTS.GRANTED;
  };

  const getCurrentLocation = async () => {
    const hasPermission = await requestPermission();
    if (!hasPermission) {
      Alert.alert('权限被拒绝', '请在设置中开启定位权限');
      return;
    }

    setLoading(true);

    Geolocation.getCurrentPosition(
      position => {
        setLocation({
          latitude: position.coords.latitude,
          longitude: position.coords.longitude,
          accuracy: position.coords.accuracy,
          timestamp: position.timestamp,
        });
        setLoading(false);
      },
      error => {
        setLoading(false);
        Alert.alert('定位失败', error.message);
      },
      {
        enableHighAccuracy: true,
        timeout: 15000,
        maximumAge: 10000,
      },
    );
  };

  const startTracking = async () => {
    const hasPermission = await requestPermission();
    if (!hasPermission) {
      Alert.alert('权限被拒绝', '请在设置中开启定位权限');
      return;
    }

    watchIdRef.current = Geolocation.watchPosition(
      position => {
        setLocation({
          latitude: position.coords.latitude,
          longitude: position.coords.longitude,
          accuracy: position.coords.accuracy,
          timestamp: position.timestamp,
        });
      },
      error => {
        Alert.alert('追踪错误', error.message);
        setTracking(false);
      },
      {
        enableHighAccuracy: true,
        distanceFilter: 10,
        interval: 5000,
        fastestInterval: 2000,
      },
    );

    setTracking(true);
  };

  const stopTracking = () => {
    if (watchIdRef.current !== null) {
      Geolocation.clearWatch(watchIdRef.current);
      watchIdRef.current = null;
    }
    setTracking(false);
  };

  return (
    <View style={styles.container}>
      <Text style={styles.title}>定位服务</Text>

      {loading && <ActivityIndicator size="large" color="#007AFF" />}

      {location && (
        <View style={styles.info}>
          <Text style={styles.label}>纬度</Text>
          <Text style={styles.value}>{location.latitude.toFixed(6)}</Text>
          
          <Text style={styles.label}>经度</Text>
          <Text style={styles.value}>{location.longitude.toFixed(6)}</Text>
          
          <Text style={styles.label}>精度</Text>
          <Text style={styles.value}>{location.accuracy?.toFixed(1)} 米</Text>
          
          <Text style={styles.label}>时间</Text>
          <Text style={styles.value}>
            {new Date(location.timestamp).toLocaleString()}
          </Text>
        </View>
      )}

      <View style={styles.buttons}>
        <TouchableOpacity
          style={styles.button}
          onPress={getCurrentLocation}
          disabled={loading}>
          <Text style={styles.buttonText}>获取位置</Text>
        </TouchableOpacity>

        <TouchableOpacity
          style={[styles.button, tracking ? styles.stopButton : styles.startButton]}
          onPress={tracking ? stopTracking : startTracking}>
          <Text style={styles.buttonText}>
            {tracking ? '停止追踪' : '开始追踪'}
          </Text>
        </TouchableOpacity>
      </View>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
    backgroundColor: '#f5f5f5',
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
    textAlign: 'center',
    marginBottom: 20,
  },
  info: {
    backgroundColor: '#fff',
    borderRadius: 8,
    padding: 16,
    marginBottom: 20,
  },
  label: {
    fontSize: 12,
    color: '#666',
    marginTop: 8,
  },
  value: {
    fontSize: 16,
    fontWeight: '600',
  },
  buttons: {
    gap: 12,
  },
  button: {
    padding: 16,
    borderRadius: 8,
    alignItems: 'center',
  },
  startButton: {
    backgroundColor: '#34C759',
  },
  stopButton: {
    backgroundColor: '#FF3B30',
  },
  buttonText: {
    color: '#fff',
    fontSize: 16,
    fontWeight: '600',
  },
});

export default LocationService;

总结 #

定位服务要点:

  • 权限配置:iOS 和 Android 都需要配置权限
  • 获取位置:单次获取当前位置
  • 位置追踪:实时监听位置变化
  • 距离计算:使用 Haversine 公式
  • 地图集成:使用 react-native-maps

继续学习 推送通知,了解通知功能实现。

最后更新:2026-03-28