1.问题描述
最近通过React Native开发安卓app的时候有个需求,是需要实现一个可以跟着手机旋转的类似地图APP上导航的那种箭头定位图标。
2.插件选择
实现该功能主要用到两个库,一个React Native Sensors传感器库和一个工具库rxjs,至于rn和rnmapbox就不赘述了,该文章重点不是这俩。
2.1React Native Sensors
react-native-sensors是一个用于在React Native应用程序中访问传感器数据的库。它提供了对设备内置传感器的访问,包括加速度计、陀螺仪和磁力计。
2.2RxJS
RxJS 是使用 Observables 的响应式编程的库,它使编写异步或基于回调的代码更容易。
3.代码实现
3.1安装依赖
安装依赖就不多讲了,通过npm或者yarn添加依赖:
npm install react-native-sensors
npm install rxjs
3.2权限申明
在使用react-native-sensors之前需要在AndroidManifest.xml
中申请权限来确保可以读取相关的硬件数据:
<!-- 传感器权限-->
<uses-permission android:name="android.permission.BODY_SENSORS"/>
<uses-permission android:name="android.permission.HIGH_SAMPLING_RATE_SENSORS" />
<!-- 传感器权限-->
3.3插件引入
主要使用了react-native-sensors插件的加速度计和磁力计功能,以及rxjs的一些方法:
import { accelerometer, magnetometer } from "react-native-sensors";
import { throttleTime, combineLatestWith, map } from "rxjs/operators";
import { from } from 'rxjs';
3.4主体代码
主要就是通过react-native-sensors插件的加速度计和磁力计返回的真机数据来计算出偏转角度,然后通过React Native的Animated改变角度以及RxJS的一些方法来提高性能。
export function MyLocation() {
const location = useLocation() as Position
const [heading, setHeading] = useState(0);
const [rotation, setRotation] = useState(new Animated.Value(0));
useEffect(() => {
// 使用Animated提高性能
Animated.timing(rotation, {
toValue: heading,
duration: 0, // 瞬间变化
useNativeDriver: true,
}).start();
}, [heading])
useEffect(() => {
// 加速度计
const accObservable = from(accelerometer);
// 磁力计
const magObservable = from(magnetometer);
const subscription = accObservable.pipe(
combineLatestWith(magObservable),
map(([accelerometerData, magnetometerData]) => {
const { x: ax, y: ay, z: az } = accelerometerData;
const { x: mx, y: my, z: mz } = magnetometerData;
// Normalize the accelerometer vector
const normA = Math.sqrt(ax * ax + ay * ay + az * az);
const aX = ax / normA;
const aY = ay / normA;
const aZ = az / normA;
// Calculate the horizontal component of the magnetic field
const hX = mx - aX * (mx * aX + my * aY + mz * aZ);
const hY = my - aY * (mx * aX + my * aY + mz * aZ);
const normH = Math.sqrt(hX * hX + hY * hY);
const hXNorm = hX / normH;
const hYNorm = hY / normH;
// Calculate the heading in radians
let headingRad = Math.atan2(hYNorm, hXNorm);
// Convert heading to degrees
let headingDeg = headingRad * (180 / Math.PI);
// Adjust for negative angles
if (headingDeg < 0) {
headingDeg += 360;
}
return headingDeg;
}),
// 加节流,不用那么频繁
throttleTime(250),
).subscribe(heading => {
setHeading(Number(heading.toFixed(1)))
})
return () => {
subscription.unsubscribe();
}
}, [])
return (
location ? (
<MarkerView anchor={{ y: 0.5, x: 0 }} coordinate={location}>
<Animated.View
style={{
transform: [{ rotate: rotation.interpolate({ inputRange: [0, 360], outputRange: ['0deg', '360deg'] }) }]
}}
>
<Image
source={导航图标图片的地址}
/>
</Animated.View>
</MarkerView>
) : null
)
}
4.参考文献
API·React Native Sensors
如何使用React Native Sensors库:全面指南
RxJS中文文档