Bootstrap

React Native 全栈开发实战班 - 原生功能集成之地理位置服务

地理位置服务 是移动应用中常见的功能之一,应用可以通过获取用户的地理位置信息提供个性化服务,如地图导航、附近商家推荐、实时位置跟踪等。React Native 提供了多种方式来实现地理位置服务,包括使用第三方库(如 react-native-geolocation-service)和调用原生模块。本章节将详细介绍如何使用 react-native-geolocation-service 库来实现地理位置服务,包括获取当前位置、监听位置变化、权限管理以及处理位置信息。


3.1 地理位置服务概述

地理位置服务主要涉及以下几个方面:

  1. 获取当前位置: 获取用户当前的地理位置信息,包括经度、纬度、海拔等。
  2. 监听位置变化: 实时监听用户的位置变化,适用于需要实时跟踪用户位置的场景。
  3. 地理围栏(Geofencing): 设置地理围栏,当用户进入或离开特定区域时触发相应的事件。
  4. 地图集成: 在应用中集成地图功能,显示用户位置、标记位置、绘制路线等。

React Native 提供了多种方式来实现地理位置服务:

  1. 第三方库:react-native-geolocation-service, react-native-location, react-native-maps 等,提供了封装好的 API,可以快速实现地理位置功能。
  2. 原生模块: 可以通过原生代码实现自定义地理位置功能,适用于需要高度定制化的场景。

本章节将重点介绍如何使用 react-native-geolocation-service 库来实现地理位置服务。


3.2 使用 react-native-geolocation-service

react-native-geolocation-service 是一个流行的第三方库,用于获取和监听地理位置信息,支持 iOS 和 Android 平台。

3.2.1 安装 react-native-geolocation-service
npm install react-native-geolocation-service

链接原生依赖(React Native 0.60 及以上版本自动链接):

cd ios
pod install
cd ..
3.2.2 配置权限

iOS:

Info.plist 文件中添加位置权限说明。

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

Android:

AndroidManifest.xml 文件中添加位置权限。

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

注意: 对于 Android 6.0 及以上版本,还需要在代码中动态请求权限。

3.2.3 基本用法

获取当前位置:

import React, { useEffect, useState } from 'react';
import { View, Text, StyleSheet, Platform, Alert } from 'react-native';
import Geolocation from 'react-native-geolocation-service';

const LocationExample = () => {
  const [location, setLocation] = useState(null);

  const getLocation = () => {
    Geolocation.getCurrentPosition(
      (position) => {
        setLocation(position);
      },
      (error) => {
        console.error(error);
        Alert.alert('获取位置失败', error.message);
      },
      {
        enableHighAccuracy: true,
        timeout: 15000,
        maximumAge: 10000,
      }
    );
  };

  useEffect(() => {
    const requestLocationPermission = async () => {
      try {
        const granted = await Geolocation.requestPermission({
          androidPermission: {
            title: '位置权限',
            message: '应用需要访问您的位置以提供定位服务',
            buttonPositive: '确定',
            buttonNegative: '取消',
          },
          iosPermission: {
            title: '位置权限',
            message: '应用需要访问您的位置以提供定位服务',
            buttonPositive: '确定',
            buttonNegative: '取消',
          },
        });
        if (granted) {
          getLocation();
        } else {
          Alert.alert('权限被拒绝', '位置权限被拒绝,无法获取位置信息');
        }
      } catch (error) {
        console.error(error);
      }
    };

    requestLocationPermission();
  }, []);

  return (
    <View style={styles.container}>
      {location ? (
        <Text style={styles.text}>
          经度: {location.coords.longitude}, 纬度: {location.coords.latitude}
        </Text>
      ) : (
        <Text style={styles.text}>获取位置中...</Text>
      )}
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    padding: 20,
  },
  text: {
    fontSize: 18,
  },
});

export default LocationExample;

解释:

  • Geolocation.getCurrentPosition 方法: 获取当前位置信息。
  • enableHighAccuracy: 是否使用高精度模式。
  • timeout: 超时时间。
  • maximumAge: 缓存位置信息的最长时间。

监听位置变化:

import React, { useEffect, useState } from 'react';
import { View, Text, StyleSheet, Platform, Alert } from 'react-native';
import Geolocation from 'react-native-geolocation-service';

const WatchLocationExample = () => {
  const [location, setLocation] = useState(null);

  useEffect(() => {
    const watchId = Geolocation.watchPosition(
      (position) => {
        setLocation(position);
      },
      (error) => {
        console.error(error);
        Alert.alert('监听位置失败', error.message);
      },
      {
        enableHighAccuracy: true,
        distanceFilter: 10, // 位置变化距离阈值(米)
      }
    );

    return () => {
      Geolocation.clearWatch(watchId);
    };
  }, []);

  return (
    <View style={styles.container}>
      {location ? (
        <Text style={styles.text}>
          经度: {location.coords.longitude}, 纬度: {location.coords.latitude}
        </Text>
      ) : (
        <Text style={styles.text}>监听位置中...</Text>
      )}
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    padding: 20,
  },
  text: {
    fontSize: 18,
  },
});

export default WatchLocationExample;

解释:

  • Geolocation.watchPosition 方法: 监听位置变化。
  • distanceFilter: 位置变化距离阈值,只有位置变化超过该值时才会触发回调。
3.2.4 地理围栏

地理围栏(Geofencing) 是一种基于地理位置的服务,通过在地图上定义虚拟边界,当用户进入或离开特定区域时触发相应的事件。地理围栏广泛应用于各种场景,如:

  • 零售: 当用户接近商店时,发送促销信息或优惠券。
  • 物流: 跟踪车辆或货物是否进入或离开指定区域。
  • 安全: 监控用户是否进入或离开安全区域。
  • 社交: 提醒用户附近有朋友或活动。

在 React Native 中,可以通过 react-native-geolocation-service 库结合地理计算来实现简单的地理围栏功能。以下是详细的实现步骤和示例代码。

3.2.4.1 实现地理围栏

以下是一个简单的地理围栏实现示例,当用户进入或离开指定区域时,会弹出相应的提示。

import React, { useEffect, useState } from 'react';
import { View, Text, StyleSheet, Platform, Alert } from 'react-native';
import Geolocation from 'react-native-geolocation-service';

const GeofencingExample = () => {
  const [location, setLocation] = useState(null);
  const [geofence, setGeofence] = useState({
    latitude: 37.7749,  // 地理围栏中心纬度(例如:旧金山)
    longitude: -122.4194, // 地理围栏中心经度
    radius: 500, // 地理围栏半径(米)
  });

  useEffect(() => {
    const watchId = Geolocation.watchPosition(
      (position) => {
        setLocation(position);
        checkGeofence(position);
      },
      (error) => {
        console.error(error);
        Alert.alert('监听位置失败', error.message);
      },
      {
        enableHighAccuracy: true,
        distanceFilter: 10, // 位置变化距离阈值(米)
      }
    );

    return () => {
      Geolocation.clearWatch(watchId);
    };
  }, []);

  const checkGeofence = (position) => {
    const { latitude, longitude } = position.coords;
    const { latitude: fenceLat, longitude: fenceLng, radius } = geofence;

    // 计算两点之间的距离(使用 Haversine 公式)
    const distance = haversine(latitude, longitude, fenceLat, fenceLng);

    if (distance <= radius) {
      if (!geofence.inside) {
        setGeofence({ ...geofence, inside: true });
        Alert.alert('进入地理围栏', `已进入地理围栏区域,半径: ${radius}`);
      }
    } else {
      if (geofence.inside) {
        setGeofence({ ...geofence, inside: false });
        Alert.alert('离开地理围栏', `已离开地理围栏区域`);
      }
    }
  };

  // Haversine 公式计算两点之间的距离
  const haversine = (lat1, lon1, lat2, lon2) => {
    const R = 6371; // 地球半径,单位:公里
    const dLat = deg2rad(lat2 - lat1);
    const dLon = deg2rad(lon2 - lon1);
    const a =
      Math.sin(dLat / 2) * Math.sin(dLat / 2) +
      Math.cos(deg2rad(lat1)) * Math.cos(deg2rad(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 * 1000; // 转换为米
  };

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

  return (
    <View style={styles.container}>
      <Text style={styles.text}>当前位置:</Text>
      {location ? (
        <Text style={styles.text}>
          经度: {location.coords.longitude}, 纬度: {location.coords.latitude}
        </Text>
      ) : (
        <Text style={styles.text}>获取位置中...</Text>
      )}
      <Text style={styles.text}>地理围栏:</Text>
      <Text style={styles.text}>
        中心: 经度 {geofence.longitude}, 纬度 {geofence.latitude}, 半径 {geofence.radius}</Text>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    padding: 20,
  },
  text: {
    fontSize: 18,
    marginVertical: 5,
  },
});

export default GeofencingExample;

解释:

  • 地理围栏定义:geofence 状态中定义地理围栏的中心坐标和半径。
  • 位置监听: 使用 Geolocation.watchPosition 监听用户的位置变化。
  • Haversine 公式: 计算当前位置与地理围栏中心之间的距离。
  • 进入/离开地理围栏: 当距离小于等于半径时,认为用户进入地理围栏;当距离大于半径时,认为用户离开地理围栏,并弹出相应的提示。

作者简介

前腾讯电子签的前端负责人,现 whentimes tech CTO,专注于前端技术的大咖一枚!一路走来,从小屏到大屏,从 Web 到移动,什么前端难题都见过。热衷于用技术打磨产品,带领团队把复杂的事情做到极简,体验做到极致。喜欢探索新技术,也爱分享一些实战经验,帮助大家少走弯路!

温馨提示:可搜老码小张公号联系导师

;