Skip to content

定位功能(GPS)

第七部分:常用原生功能开发

在 React Native 应用中,定位功能是一个常见的需求,比如获取用户当前位置、计算距离、导航等。本文将详细介绍如何在 React Native 应用中实现定位功能。

1. 使用 Expo Location

1.1 安装依赖

bash
# 使用 npm
npm install expo-location

# 使用 yarn
yarn add expo-location

1.2 请求权限

在使用定位功能之前,需要请求相应的权限。

jsx
import React, { useState, useEffect } from 'react';
import { View, Text, TouchableOpacity, StyleSheet, Alert } from 'react-native';
import * as Location from 'expo-location';

export default function LocationExample() {
  const [location, setLocation] = useState(null);
  const [errorMsg, setErrorMsg] = useState(null);

  useEffect(() => {
    (async () => {
      try {
        // 请求权限
        const { status } = await Location.requestForegroundPermissionsAsync();
        if (status !== 'granted') {
          setErrorMsg('需要位置权限才能获取定位信息');
          return;
        }

        // 获取当前位置
        const location = await Location.getCurrentPositionAsync({});
        setLocation(location);
      } catch (error) {
        setErrorMsg('获取位置失败');
      }
    })();
  }, []);

  // 手动获取位置
  const getLocation = async () => {
    try {
      const { status } = await Location.requestForegroundPermissionsAsync();
      if (status !== 'granted') {
        Alert.alert('权限错误', '需要位置权限才能获取定位信息');
        return;
      }

      const location = await Location.getCurrentPositionAsync({});
      setLocation(location);
      setErrorMsg(null);
    } catch (error) {
      setErrorMsg('获取位置失败');
    }
  };

  let text = '正在获取位置...';
  if (errorMsg) {
    text = errorMsg;
  } else if (location) {
    text = `纬度: ${location.coords.latitude}\n经度: ${location.coords.longitude}\n精度: ${location.coords.accuracy}米`;
  }

  return (
    <View style={styles.container}>
      <Text style={styles.title}>定位功能示例</Text>
      <Text style={styles.locationText}>{text}</Text>
      <TouchableOpacity style={styles.button} onPress={getLocation}>
        <Text style={styles.buttonText}>获取位置</Text>
      </TouchableOpacity>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
    alignItems: 'center',
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
    marginBottom: 30,
  },
  locationText: {
    fontSize: 16,
    textAlign: 'center',
    marginBottom: 30,
  },
  button: {
    backgroundColor: '#4CAF50',
    padding: 15,
    borderRadius: 8,
    width: '100%',
    alignItems: 'center',
  },
  buttonText: {
    color: '#fff',
    fontSize: 16,
    fontWeight: '500',
  },
});

2. 使用 react-native-location

2.1 安装依赖

bash
# 使用 npm
npm install react-native-location

# 使用 yarn
yarn add react-native-location

2.2 配置权限

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" />

iOS 配置

Info.plist 文件中添加以下权限:

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

2.3 示例代码

jsx
import React, { useState, useEffect } from 'react';
import { View, Text, TouchableOpacity, StyleSheet, Alert, Platform } from 'react-native';
import RNLocation from 'react-native-location';

export default function RNLocationExample() {
  const [location, setLocation] = useState(null);
  const [errorMsg, setErrorMsg] = useState(null);

  useEffect(() => {
    // 配置位置更新
    RNLocation.configure({
      distanceFilter: 5.0,
    });

    (async () => {
      try {
        // 请求权限
        const granted = await RNLocation.requestPermission({ ios: 'whenInUse', android: { detail: 'fine' } });
        if (!granted) {
          setErrorMsg('需要位置权限才能获取定位信息');
          return;
        }

        // 获取当前位置
        const location = await RNLocation.getLatestLocation({ timeout: 60000 });
        setLocation(location);
      } catch (error) {
        setErrorMsg('获取位置失败');
      }
    })();
  }, []);

  // 手动获取位置
  const getLocation = async () => {
    try {
      const granted = await RNLocation.requestPermission({ ios: 'whenInUse', android: { detail: 'fine' } });
      if (!granted) {
        Alert.alert('权限错误', '需要位置权限才能获取定位信息');
        return;
      }

      const location = await RNLocation.getLatestLocation({ timeout: 60000 });
      setLocation(location);
      setErrorMsg(null);
    } catch (error) {
      setErrorMsg('获取位置失败');
    }
  };

  let text = '正在获取位置...';
  if (errorMsg) {
    text = errorMsg;
  } else if (location) {
    text = `纬度: ${location.latitude}\n经度: ${location.longitude}\n精度: ${location.accuracy}米`;
  }

  return (
    <View style={styles.container}>
      <Text style={styles.title}>定位功能示例</Text>
      <Text style={styles.locationText}>{text}</Text>
      <TouchableOpacity style={styles.button} onPress={getLocation}>
        <Text style={styles.buttonText}>获取位置</Text>
      </TouchableOpacity>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
    alignItems: 'center',
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
    marginBottom: 30,
  },
  locationText: {
    fontSize: 16,
    textAlign: 'center',
    marginBottom: 30,
  },
  button: {
    backgroundColor: '#4CAF50',
    padding: 15,
    borderRadius: 8,
    width: '100%',
    alignItems: 'center',
  },
  buttonText: {
    color: '#fff',
    fontSize: 16,
    fontWeight: '500',
  },
});

3. 高级功能

3.1 位置监听

jsx
import React, { useState, useEffect } from 'react';
import { View, Text, StyleSheet, Switch } from 'react-native';
import * as Location from 'expo-location';

export default function LocationTrackingExample() {
  const [location, setLocation] = useState(null);
  const [errorMsg, setErrorMsg] = useState(null);
  const [isTracking, setIsTracking] = useState(false);
  const [subscription, setSubscription] = useState(null);

  // 开始位置追踪
  const startTracking = async () => {
    try {
      const { status } = await Location.requestForegroundPermissionsAsync();
      if (status !== 'granted') {
        setErrorMsg('需要位置权限才能获取定位信息');
        return;
      }

      // 配置位置更新
      const subscription = await Location.watchPositionAsync(
        {
          accuracy: Location.Accuracy.Balanced,
          timeInterval: 5000, // 5秒更新一次
          distanceInterval: 10, // 移动10米更新一次
        },
        (location) => {
          setLocation(location);
          setErrorMsg(null);
        }
      );

      setSubscription(subscription);
      setIsTracking(true);
    } catch (error) {
      setErrorMsg('获取位置失败');
    }
  };

  // 停止位置追踪
  const stopTracking = () => {
    if (subscription) {
      subscription.remove();
      setSubscription(null);
      setIsTracking(false);
    }
  };

  // 切换追踪状态
  const toggleTracking = () => {
    if (isTracking) {
      stopTracking();
    } else {
      startTracking();
    }
  };

  useEffect(() => {
    // 组件卸载时停止追踪
    return () => {
      if (subscription) {
        subscription.remove();
      }
    };
  }, [subscription]);

  let text = '位置追踪已关闭';
  if (errorMsg) {
    text = errorMsg;
  } else if (location) {
    text = `纬度: ${location.coords.latitude}\n经度: ${location.coords.longitude}\n精度: ${location.coords.accuracy}米\n时间: ${new Date(location.timestamp).toLocaleString()}`;
  }

  return (
    <View style={styles.container}>
      <Text style={styles.title}>位置追踪示例</Text>
      <View style={styles.trackingSwitch}>
        <Text style={styles.switchLabel}>开启位置追踪</Text>
        <Switch
          value={isTracking}
          onValueChange={toggleTracking}
          trackColor={{ false: '#767577', true: '#81b0ff' }}
          thumbColor={isTracking ? '#4CAF50' : '#f4f3f4'}
        />
      </View>
      <Text style={styles.locationText}>{text}</Text>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
    marginBottom: 30,
    textAlign: 'center',
  },
  trackingSwitch: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
    marginBottom: 30,
  },
  switchLabel: {
    fontSize: 16,
  },
  locationText: {
    fontSize: 16,
    textAlign: 'center',
  },
});

3.2 地理编码

jsx
import React, { useState } from 'react';
import { View, Text, TextInput, TouchableOpacity, StyleSheet, Alert } from 'react-native';
import * as Location from 'expo-location';

export default function GeocodingExample() {
  const [address, setAddress] = useState('');
  const [coordinates, setCoordinates] = useState(null);
  const [location, setLocation] = useState(null);
  const [errorMsg, setErrorMsg] = useState(null);

  // 地址转坐标(正向地理编码)
  const geocodeAddress = async () => {
    if (!address) {
      Alert.alert('错误', '请输入地址');
      return;
    }

    try {
      const results = await Location.geocodeAsync(address);
      if (results.length > 0) {
        setCoordinates(results[0]);
        setErrorMsg(null);
      } else {
        setErrorMsg('未找到地址');
      }
    } catch (error) {
      setErrorMsg('地理编码失败');
    }
  };

  // 坐标转地址(反向地理编码)
  const reverseGeocode = async () => {
    try {
      const { status } = await Location.requestForegroundPermissionsAsync();
      if (status !== 'granted') {
        Alert.alert('权限错误', '需要位置权限才能获取定位信息');
        return;
      }

      const currentLocation = await Location.getCurrentPositionAsync({});
      const { latitude, longitude } = currentLocation.coords;
      
      const results = await Location.reverseGeocodeAsync({
        latitude,
        longitude,
      });

      if (results.length > 0) {
        setLocation(results[0]);
        setErrorMsg(null);
      } else {
        setErrorMsg('未找到地址');
      }
    } catch (error) {
      setErrorMsg('反向地理编码失败');
    }
  };

  return (
    <View style={styles.container}>
      <Text style={styles.title}>地理编码示例</Text>
      
      <View style={styles.section}>
        <Text style={styles.sectionTitle}>正向地理编码(地址转坐标)</Text>
        <TextInput
          style={styles.input}
          value={address}
          onChangeText={setAddress}
          placeholder="输入地址"
          marginBottom={15}
        />
        <TouchableOpacity style={styles.button} onPress={geocodeAddress}>
          <Text style={styles.buttonText}>获取坐标</Text>
        </TouchableOpacity>
        {coordinates && (
          <Text style={styles.resultText}>
            纬度: {coordinates.latitude}\n经度: {coordinates.longitude}
          </Text>
        )}
      </View>
      
      <View style={styles.section}>
        <Text style={styles.sectionTitle}>反向地理编码(坐标转地址)</Text>
        <TouchableOpacity style={styles.button} onPress={reverseGeocode}>
          <Text style={styles.buttonText}>获取当前地址</Text>
        </TouchableOpacity>
        {location && (
          <Text style={styles.resultText}>
            地址: {location.name}\n{location.street}\n{location.city}, {location.region}, {location.country}
          </Text>
        )}
      </View>
      
      {errorMsg && (
        <Text style={styles.errorText}>{errorMsg}</Text>
      )}
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
    marginBottom: 30,
    textAlign: 'center',
  },
  section: {
    marginBottom: 30,
    padding: 15,
    backgroundColor: '#f9f9f9',
    borderRadius: 8,
  },
  sectionTitle: {
    fontSize: 18,
    fontWeight: '500',
    marginBottom: 15,
  },
  input: {
    borderWidth: 1,
    borderColor: '#ddd',
    padding: 10,
    borderRadius: 5,
  },
  button: {
    backgroundColor: '#4CAF50',
    padding: 12,
    borderRadius: 5,
    alignItems: 'center',
    marginBottom: 15,
  },
  buttonText: {
    color: '#fff',
    fontSize: 16,
    fontWeight: '500',
  },
  resultText: {
    fontSize: 14,
    marginTop: 10,
  },
  errorText: {
    fontSize: 14,
    color: 'red',
    textAlign: 'center',
  },
});

3.3 计算距离

jsx
import React, { useState } from 'react';
import { View, Text, TextInput, TouchableOpacity, StyleSheet, Alert } from 'react-native';
import * as Location from 'expo-location';

export default function DistanceCalculatorExample() {
  const [lat1, setLat1] = useState('');
  const [lon1, setLon1] = useState('');
  const [lat2, setLat2] = useState('');
  const [lon2, setLon2] = useState('');
  const [distance, setDistance] = useState(null);
  const [errorMsg, setErrorMsg] = useState(null);

  // 计算两点之间的距离
  const calculateDistance = () => {
    if (!lat1 || !lon1 || !lat2 || !lon2) {
      Alert.alert('错误', '请输入所有坐标');
      return;
    }

    try {
      const distanceInMeters = Location.distance(
        { latitude: parseFloat(lat1), longitude: parseFloat(lon1) },
        { latitude: parseFloat(lat2), longitude: parseFloat(lon2) }
      );
      const distanceInKm = (distanceInMeters / 1000).toFixed(2);
      setDistance(`${distanceInKm} 公里`);
      setErrorMsg(null);
    } catch (error) {
      setErrorMsg('计算距离失败');
    }
  };

  // 使用当前位置
  const useCurrentLocation = async () => {
    try {
      const { status } = await Location.requestForegroundPermissionsAsync();
      if (status !== 'granted') {
        Alert.alert('权限错误', '需要位置权限才能获取定位信息');
        return;
      }

      const location = await Location.getCurrentPositionAsync({});
      setLat1(location.coords.latitude.toString());
      setLon1(location.coords.longitude.toString());
    } catch (error) {
      setErrorMsg('获取位置失败');
    }
  };

  return (
    <View style={styles.container}>
      <Text style={styles.title}>距离计算器</Text>
      
      <View style={styles.section}>
        <Text style={styles.sectionTitle}>起点坐标</Text>
        <View style={styles.coordinateInput}>
          <TextInput
            style={[styles.input, { flex: 1, marginRight: 10 }]}
            value={lat1}
            onChangeText={setLat1}
            placeholder="纬度"
            keyboardType="numeric"
          />
          <TextInput
            style={[styles.input, { flex: 1 }]}
            value={lon1}
            onChangeText={setLon1}
            placeholder="经度"
            keyboardType="numeric"
          />
        </View>
        <TouchableOpacity style={styles.smallButton} onPress={useCurrentLocation}>
          <Text style={styles.smallButtonText}>使用当前位置</Text>
        </TouchableOpacity>
      </View>
      
      <View style={styles.section}>
        <Text style={styles.sectionTitle}>终点坐标</Text>
        <View style={styles.coordinateInput}>
          <TextInput
            style={[styles.input, { flex: 1, marginRight: 10 }]}
            value={lat2}
            onChangeText={setLat2}
            placeholder="纬度"
            keyboardType="numeric"
          />
          <TextInput
            style={[styles.input, { flex: 1 }]}
            value={lon2}
            onChangeText={setLon2}
            placeholder="经度"
            keyboardType="numeric"
          />
        </View>
      </View>
      
      <TouchableOpacity style={styles.button} onPress={calculateDistance}>
        <Text style={styles.buttonText}>计算距离</Text>
      </TouchableOpacity>
      
      {distance && (
        <Text style={styles.distanceText}>距离: {distance}</Text>
      )}
      
      {errorMsg && (
        <Text style={styles.errorText}>{errorMsg}</Text>
      )}
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
    marginBottom: 30,
    textAlign: 'center',
  },
  section: {
    marginBottom: 20,
    padding: 15,
    backgroundColor: '#f9f9f9',
    borderRadius: 8,
  },
  sectionTitle: {
    fontSize: 16,
    fontWeight: '500',
    marginBottom: 10,
  },
  coordinateInput: {
    flexDirection: 'row',
    marginBottom: 10,
  },
  input: {
    borderWidth: 1,
    borderColor: '#ddd',
    padding: 10,
    borderRadius: 5,
  },
  smallButton: {
    backgroundColor: '#2196F3',
    padding: 8,
    borderRadius: 5,
    alignItems: 'center',
  },
  smallButtonText: {
    color: '#fff',
    fontSize: 14,
  },
  button: {
    backgroundColor: '#4CAF50',
    padding: 15,
    borderRadius: 8,
    alignItems: 'center',
    marginBottom: 20,
  },
  buttonText: {
    color: '#fff',
    fontSize: 16,
    fontWeight: '500',
  },
  distanceText: {
    fontSize: 18,
    textAlign: 'center',
    marginBottom: 20,
  },
  errorText: {
    fontSize: 14,
    color: 'red',
    textAlign: 'center',
  },
});

4. 常见问题与解决方案

问题 1:权限被拒绝

问题:用户拒绝了位置权限。

解决方案

  • 在应用中添加权限请求逻辑
  • 当权限被拒绝时,引导用户手动开启权限
  • 提供替代方案,如手动输入位置

问题 2:定位精度低

问题:获取的位置精度不够高。

解决方案

  • 设置更高的精度选项
  • 等待位置稳定后再获取
  • 结合网络定位和 GPS 定位

问题 3:定位失败

问题:获取位置时出现错误。

解决方案

  • 检查设备位置服务是否开启
  • 检查网络连接
  • 处理异常情况,提供友好的错误提示
  • 实现重试机制

问题 4:电池消耗过快

问题:位置追踪导致电池消耗过快。

解决方案

  • 合理设置位置更新的频率和距离
  • 只在需要时开启位置追踪
  • 使用后台位置更新时要谨慎
  • 及时停止位置追踪

问题 5:iOS 权限配置

问题:在 iOS 上无法获取位置。

解决方案

  • Info.plist 文件中添加正确的权限描述
  • 确保权限描述清晰明了,说明为什么需要这些权限
  • 对于需要后台位置更新的应用,需要添加相应的配置

5. 最佳实践

5.1 权限处理

  • 提前请求权限,避免在用户操作时才请求
  • 当权限被拒绝时,提供清晰的提示和引导
  • 处理权限请求的各种状态( granted, denied, restricted, undetermined )
  • 只请求必要的权限级别

5.2 定位精度

  • 根据应用需求选择合适的精度级别
  • 高精度定位会消耗更多电量,应谨慎使用
  • 对于不需要高精度的应用,可以使用低精度定位

5.3 错误处理

  • 捕获并处理所有可能的错误
  • 提供友好的错误提示
  • 记录错误信息,便于调试
  • 实现重试机制

5.4 性能优化

  • 合理设置位置更新的频率和距离
  • 及时停止位置追踪
  • 使用缓存减少重复请求
  • 考虑网络状况,提供离线支持

5.5 用户体验

  • 添加加载状态,如获取位置时显示进度
  • 提供清晰的操作反馈
  • 优化位置获取的速度和准确性
  • 考虑用户隐私,明确告知用户位置使用目的

6. 总结

本文介绍了在 React Native 应用中实现定位功能的方法,包括使用 Expo Location 和 react-native-location 库。通过本文的学习,你应该掌握了以下内容:

  1. 如何请求位置权限
  2. 如何获取当前位置
  3. 如何实现位置追踪
  4. 如何进行地理编码和反向地理编码
  5. 如何计算两点之间的距离
  6. 常见问题的解决方案
  7. 最佳实践

在实际开发中,合理使用这些技术,可以创建出更加丰富、用户友好的位置相关功能,提升应用的整体体验。

© 2026 编程马·菜鸟教程 版权所有