这个功能其实挺常用的,之前我都是用原生来写这个功能.
感觉还是用套JS 写 更通用点 这里放出源码 ,方便大家复制粘贴
这里给张图片 iOS模拟器的
本JS 会读取本机是否安装了 第三方地图 拉进行判断
如果没有安装任何地图 会显示常用的 高德 百度 腾讯 地图然后跳转到对应的 下载地址
源码:
MapLinking.js 文件
import { Linking, ActionSheetIOS, Platform } from "react-native";
import ActionSheet from "../../components/ActionSheet/index";
// 腾讯地图开发者key
let tmapKey = "";
// 下载地图app地址
const DownloadUrl = {
android: {
GaoDe: "http://mobile.amap.com",
BaiDu: "http://map.baidu.com",
// TengXun: `https://pr.map.qq.com/j/tmap/download?key=${tmapKey}`
},
ios: {
GaoDe:
"https://itunes.apple.com/cn/app/gao-tu-zhuan-ye-shou-ji-tu/id461703208?mt=8",
BaiDu:
"https://itunes.apple.com/cn/app/bai-du-tu-shou-ji-tu-lu-xian/id452186370?mt=8",
// TengXun: `https://pr.map.qq.com/j/tmap/download?key=${tmapKey}`
}
};
// 第三方地图应用Url
const openUrl = ({ startLocation, destLocation, mode, type, appName }) => {
// 高德地图参数配置
const GaoDeDev = type === "gcj02" ? 0 : 1; // 起终点是否偏移(0:lat 和 lon 是已经加密后的,不需要国测加密; 1:需要国测加密)
const GaoDeT =
mode === "drive" ? "0" : mode === "bus" ? "1" : mode === "walk" ? "2" : "3"; // 导航模式: t = 0(驾车)= 1(公交)= 2(步行)= 3(骑行)= 4(火车)= 5(长途客车)
// 百度地图参数配置
const BaiDuMode =
mode === "drive"
? "driving"
: mode === "bus"
? "transit"
: mode === "ride"
? "riding"
: "walking"; // 导航模式: 可选transit(公交)、driving(驾车)、walking(步行)和riding(骑行)
const BaiDuCoordType = type === "gcj02" ? "gcj02" : "wgs84"; // 坐标类型,必选参数
// 腾讯地图参数配置
const TengXunType =
mode === "drive"
? "drive"
: mode === "bus"
? "bus"
: mode === "ride"
? "bike"
: "walk"; // 路线规划方式参数: 公交:bus 驾车:drive 步行:walk 骑行:bike
// IOS系统地图配置
const IOSDirflg = mode === "drive" ? "d" : mode === "bus" ? "r" : "w";
if (Platform.OS === "android") {
return [
[
"高德地图",
`amapuri://route/plan/?sourceApplication=${appName}&slat=${
startLocation.lat
}&slon=${startLocation.lng}&sname=${startLocation.title}&dlat=${
destLocation.lat
}&dlon=${destLocation.lng}&dname=${
destLocation.title
}&dev=${GaoDeDev}&m=0&t=${GaoDeT}&rideType=elebike`
],
[
"百度地图",
`baidumap://map/direction?origin=name:${startLocation.title}|latlng:${
startLocation.lat
},${startLocation.lng}&destination=name:${destLocation.title}|latlng:${
destLocation.lat
},${
destLocation.lng
}&mode=${BaiDuMode}&coord_type=${BaiDuCoordType}&src=${appName}`
],
[
"腾讯地图",
`qqmap://map/routeplan?type=${TengXunType}&from=${
startLocation.title
}&fromcoord=${startLocation.lat},${startLocation.lng}&to=${
destLocation.title
}&tocoord=${destLocation.lat},${destLocation.lng}&referer=${tmapKey}`
]
];
}
return [
[
"高德地图",
`iosamap://path?sourceApplication=${appName}&slat=${
startLocation.lat
}&slon=${startLocation.lng}&sname=${startLocation.title}&dlat=${
destLocation.lat
}&dlon=${destLocation.lng}&dname=${
destLocation.title
}&dev=${GaoDeDev}&m=0&t=${GaoDeT}&rideType=elebike`
],
[
"百度地图",
`baidumap://map/direction?origin=name:${startLocation.title}|latlng:${
startLocation.lat
},${startLocation.lng}&destination=name:${destLocation.title}|latlng:${
destLocation.lat
},${
destLocation.lng
}&mode=${BaiDuMode}&coord_type=${BaiDuCoordType}&src=${appName}`
],
[
"腾讯地图",
`qqmap://map/routeplan?type=${TengXunType}&from=${
startLocation.title
}&fromcoord=${startLocation.lat},${startLocation.lng}&to=${
destLocation.title
}&tocoord=${destLocation.lat},${destLocation.lng}&referer=${tmapKey}`
],
[
"Apple地图",
`http://maps.apple.com/?ll=${destLocation.lat},${destLocation.lng}&q=${
destLocation.title
}&dirflg=${IOSDirflg}`
]
];
};
/**
* 系统内没有任何第三方地图应用,提示推荐下载列表
*/
const downloadTip = () => {
const obj = Platform.OS === "android" ? ActionSheet : ActionSheetIOS;
obj.showActionSheetWithOptions(
{
title: "未安装任何第三方地图应用,请选择其中一个应用",
options: ["取消", "高德地图", "百度地图","腾讯地图"],
cancelButtonIndex: 0
},
buttonIndex => {
const { GaoDe, BaiDu, TengXun } = DownloadUrl[Platform.OS];
let url = 0;
switch (buttonIndex) {
case 1:
url = GaoDe;
break;
case 2:
url = BaiDu;
break;
case 3:
url = TengXun;
break;
default:
url = GaoDe;
}
Linking.canOpenURL(url).then(result => {
if (result) {
Linking.openURL(url);
}
});
}
);
};
/**
* 显示已经存在的第三方地图应用
*/
const showExistApp = maps => {
const obj = Platform.OS === "android" ? ActionSheet : ActionSheetIOS;
const options = ["取消", ...maps.map(item => item[0])];
obj.showActionSheetWithOptions(
{
title: "请选择其中一个地图应用",
options,
cancelButtonIndex: 0
},
buttonIndex => {
if (buttonIndex !== 0) {
Linking.openURL(maps[buttonIndex - 1][1]);
}
}
);
};
/**
* 规划路线
*/
const planRoute = ({
startLocation = {},
destLocation = {},
mode = "ride",
type = "gcj02",
appName = "MapLinking"
}) => {
const maps = openUrl({ startLocation, destLocation, mode, type, appName });
const promises = maps.map(item => {
return Linking.canOpenURL(item[1]).then((supported) => {
if (supported) {
// console.log("Can't handle url: " + url);
return item;
} else {
return false;
}
})
.catch((err) => console.error('An error occurred', err));;
});
Promise.all(promises)
.then(results => {
return maps.filter((item, index) => results[index]);
})
.then(choices => {
if (!choices.length) {
// 系统内没有任何地图,展示推荐下载列表
downloadTip();
} else {
showExistApp(choices);
}
});
};
/**
* 初始化值(如果想兼容腾讯地图,需要传入腾讯开发者Key)
* 【申请地址】 https://lbs.qq.com/console/key.html
*/
const init = ({ tmapKey: refererKey = "" }) => {
tmapKey = refererKey;
};
const MapLinking = () => {};
MapLinking.planRoute = planRoute;
MapLinking.init = init;
export default MapLinking;
ActionSheet.js 文件
import React from 'react';
import { ActionSheetIOS, Platform } from 'react-native';
import ActionSheetContainer from './ActionSheetContainer';
import RootSiblings from 'react-native-root-siblings';
let instance = null;
const ActionSheet = {
Container: ActionSheetContainer,
useActionSheetIOS: true,
showActionSheetWithOptions: (config, callback) => {
if (Platform.OS === 'ios' && ActionSheet.useActionSheetIOS) {
ActionSheetIOS.showActionSheetWithOptions(config, callback);
return;
}
if (instance) {
return;
}
instance = new RootSiblings(
{
instance && instance.destroy(() => {
instance = null;
setTimeout(() => {
callback && callback(index);
}, 0);
});
}}
/>
);
},
};
export default ActionSheet;
ActionSheetContainer.js 文件
import React from 'react';
import { Dimensions, Modal, ScrollView, StyleSheet, Text, TouchableHighlight, TouchableWithoutFeedback, View } from 'react-native';
import { getSafeAreaInset, isLandscape } from './SafeArea';
export default class extends React.PureComponent {
static defaultProps = {
backgroundColor: 'rgba(0, 0, 0, 0.3)',
contentBackgroundColor: '#f9f9f9',
separatorColor: '#d7d7d7',
fontSize: 18,
color: '#007aff',
titleStyle: {
fontSize: 15,
fontWeight: 'bold',
color: '#8f8f8f',
},
messageStyle: {
fontSize: 15,
color: '#8f8f8f',
},
destructiveButtonStyle: {
color: '#d11f1f',
},
cancelButtonStyle: {
fontWeight: 'bold',
},
touchableUnderlayColor: '#dddddd',
supportedOrientations: ['portrait', 'landscape'],
};
constructor(props) {
super(props);
this.onWindowChange = this._onWindowChange.bind(this);
this.state = {
isLandscape: isLandscape(),
};
}
componentDidMount() {
Dimensions.addEventListener('change', this.onWindowChange);
}
componentWillUnmount() {
Dimensions.removeEventListener('change', this.onWindowChange);
}
render() {
const { config, backgroundColor, supportedOrientations } = this.props;
const { cancelButtonIndex } = config;
const closeFunc = this._click.bind(this, cancelButtonIndex);
return (
{this._renderSections()}
);
}
_renderSections = () => {
const {width, height} = Dimensions.get('window');
const inset = getSafeAreaInset();
const { config } = this.props;
const { title, message, options, cancelButtonIndex } = config;
const contentStyle = {
paddingHorizontal: 10,
marginBottom: inset.bottom > 0 ? inset.bottom : 10,
marginTop: this.state.isLandscape ? inset.top + 10 : inset.top + 44,
};
contentStyle.maxHeight = height - contentStyle.marginBottom - contentStyle.marginTop;
if (this.state.isLandscape) {
contentStyle.width = Math.max(width / 3, height - 10 * 2);
contentStyle.alignSelf = 'center';
}
const section = [];
let cancelView = null;
(title || message) && section.push(this._renderTitle(title, message));
options.forEach((item, index) => {
const itemView = this._renderItem(item, index);
if (index === cancelButtonIndex) {
cancelView = itemView;
} else {
section.push(itemView);
}
});
const sections = cancelView ? [section, cancelView] : [section];
return (
{sections.map((sectionItem, index) => {
const style = index > 0 ? {
marginTop: 9,
} : {};
return this._renderSection(sectionItem, index, style);
})}
);
};
_renderTitle = (title, message) => {
const { titleStyle, messageStyle } = this.props;
const style = {marginVertical: 6};
return (
{title && (
{title}
)}
{message && (
{message}
)}
);
};
_renderItem = (item, index) => {
const { config, destructiveButtonStyle, cancelButtonStyle, touchableUnderlayColor, fontSize, color } = this.props;
const { destructiveButtonIndex, cancelButtonIndex } = config;
const isCancel = index === cancelButtonIndex;
const isDestructive = index === destructiveButtonIndex;
const textStyle = isCancel ? cancelButtonStyle :
isDestructive ? destructiveButtonStyle : null;
return (
{item}
);
};
_renderSection(items, index, style) {
const {contentBackgroundColor: backgroundColor} = this.props;
const isArray = Array.isArray(items);
const Component = isArray ? ScrollView : View;
const props = isArray ? {
bounces: false,
} : {};
return (
{isArray ? items.map((item, innerIndex) => {
const views = [item];
if (innerIndex < items.length - 1) {
views.push(this._renderSeparatorLine('sepline' + innerIndex));
}
return views;
}) : items}
);
}
_renderSeparatorLine(key) {
const {separatorColor: backgroundColor} = this.props;
return (
);
}
_click(index) {
this.props.callback && this.props.callback(index);
}
_onWindowChange() {
this.setState({isLandscape: isLandscape()});
}
}
const styles = StyleSheet.create({
view: {
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
opacity: 1,
},
touchview: {
flex: 1,
backgroundColor: 'transparent',
},
content: {
flex: 0,
},
section: {
borderRadius: 11,
overflow: 'hidden',
},
seperator: {
height: StyleSheet.hairlineWidth,
},
title: {
justifyContent: 'center',
alignItems: 'center',
paddingVertical: 10,
},
button: {
alignItems: 'center',
justifyContent: 'center',
paddingHorizontal: 16,
height: 57,
},
buttonView: {
justifyContent: 'center',
alignItems: 'center',
},
});
SafeArea.js
import { Dimensions, Platform, StatusBar } from 'react-native';
export const isIos = Platform.OS === 'ios';
export const isAndroid = Platform.OS === 'android';
export const isWeb = Platform.OS === 'web';
const {width: D_WIDTH, height: D_HEIGHT} = Dimensions.get('window');
function _compare(targetWidth, targetHeight) {
return D_WIDTH === targetWidth && D_HEIGHT === targetHeight ||
D_WIDTH === targetHeight && D_HEIGHT === targetWidth;
}
export const isIPhoneX = function () {
// X + XS
const X_WIDTH = 375;
const X_HEIGHT = 812;
// XR + XS Max
const XSMAX_WIDTH = 414;
const XSMAX_HEIGHT = 896;
return isIos && (
_compare(X_WIDTH, X_HEIGHT) ||
_compare(XSMAX_WIDTH, XSMAX_HEIGHT)
);
}();
export const isNewIPadPro = function () {
const IPADPRO11_WIDTH = 834;
const IPADPRO11_HEIGHT = 1194;
const IPADPRO129_HEIGHT = 1024;
const IPADPRO129_WIDTH = 1366;
return isIos && (
_compare(IPADPRO11_WIDTH, IPADPRO11_HEIGHT) ||
_compare(IPADPRO129_WIDTH, IPADPRO129_HEIGHT)
);
}();
export const isIPad = function () {
if (!isIos || isIPhoneX) return false;
const PAD_WIDTH = 768;
const minEdge = Math.min(D_WIDTH, D_HEIGHT);
return minEdge >= PAD_WIDTH;
}();
export function getSafeAreaInset(landscape, translucent = false) {
if (landscape === undefined) {
landscape = isLandscape();
}
const inset = (top, right, bottom, left) => ({top, right, bottom, left});
if (isIos) {
if (isIPhoneX) {
return landscape ? inset(0, 44, 21, 44) : inset(44, 0, 34, 0);
} else if (isNewIPadPro) {
return inset(24, 0, 20, 0);
} else if (isIPad) {
return inset(20, 0, 0, 0);
} else {
return landscape ? inset(0, 0, 0, 0) : inset(20, 0, 0, 0);
}
} else if (isAndroid) {
const top = translucent ? StatusBar.currentHeight : 0;
return inset(top, 0, 0, 0);
} else {
return inset(0, 0, 0, 0);
}
}
export function isLandscape() {
const {width, height} = Dimensions.get('window');
return width > height;
}
这里是使用:
handleChange = () => {
// starting location info
const startLocation = {
lng: 106.534892,
lat: 29.551891,
title: '公园地址'
};
// end location info
const destLocation = {
lng: 106.27613,
lat: 29.972084,
title: '邮局'
};
MapLinking.planRoute({ startLocation, destLocation });
};
render() {
return (
start navigation
);
}
}