Skip to content

Instantly share code, notes, and snippets.

@hungtrn75
Created June 30, 2021 03:34
Show Gist options
  • Save hungtrn75/7c8dfb96a1f2e5c8a289c134e4ee7d98 to your computer and use it in GitHub Desktop.
Save hungtrn75/7c8dfb96a1f2e5c8a289c134e4ee7d98 to your computer and use it in GitHub Desktop.
react-native-maps
import React, {useRef, useState} from 'react';
import {
Dimensions,
Image,
InteractionManager,
Platform,
StyleSheet,
} from 'react-native';
import {AnimatedRegion, Marker, Polyline} from 'react-native-maps';
import Animated, {
runOnJS,
useAnimatedStyle,
useDerivedValue,
useSharedValue,
withTiming,
} from 'react-native-reanimated';
const screen = Dimensions.get('window');
const ASPECT_RATIO = screen.width / screen.height;
const LATITUDE_DELTA = 0.0922;
const LONGITUDE_DELTA = LATITUDE_DELTA * ASPECT_RATIO;
const getRotation = (prevPos, curPos) => {
if (!curPos) {
return 0;
}
const xDiff = curPos.latitude - prevPos.latitude;
const yDiff = curPos.longitude - prevPos.longitude;
return (Math.atan2(yDiff, xDiff) * 180.0) / Math.PI;
};
const AnimatePolyline = ({Direction, index, isPlaying}) => {
const [polylinePath, setPolylinePath] = useState([]);
const marker = useRef();
const [coordinate, setCoordinate] = useState(
new AnimatedRegion({
latitude: Direction[0].latitude,
longitude: Direction[0].longitude,
longitudeDelta: LONGITUDE_DELTA,
latitudeDelta: LATITUDE_DELTA,
}),
);
const rotate = useSharedValue(getRotation(Direction[0], Direction[1]));
const getData = (data = []) => {
'worklet';
const clone = JSON.parse(JSON.stringify(data));
return clone.map(e => ({...e}));
};
const animatePolylineStart = cIndex => {
if (cIndex < 1) return;
const duration = isPlaying.current ? 250 : 50;
if (cIndex <= Direction.length - 1) {
const nC = Direction[cIndex];
let rotation = rotate.value;
if (cIndex >= Direction.length - 1) {
} else {
rotation = getRotation(Direction[cIndex - 1], nC);
}
rotate.value = withTiming(rotation);
coordinate
.timing(nC, {
duration: duration,
})
.start();
}
const updatePath = () => {
const nP = Direction.slice(
0,
cIndex == Direction.length - 1 ? cIndex + 1 : cIndex,
);
setPolylinePath(nP);
};
if (isPlaying.current) {
updatePath();
} else {
InteractionManager.runAfterInteractions(updatePath);
}
};
useDerivedValue(() => {
runOnJS(animatePolylineStart)(index.value);
}, [index]);
const style = useAnimatedStyle(() => {
return {
transform: [
{
rotate: `${rotate.value}deg`,
},
],
};
});
return (
<>
{polylinePath.length > 0 ? (
<Polyline
coordinates={polylinePath}
strokeColor="orange"
strokeWidth={5}
/>
) : null}
<Marker.Animated ref={marker} coordinate={coordinate} anchor={anchor}>
<Animated.View style={style}>
<Image source={require('./navigate.png')} style={styles.icon} />
</Animated.View>
</Marker.Animated>
</>
);
};
export default AnimatePolyline;
const anchor = {
x: 0.5,
y: 0.5,
};
const styles = StyleSheet.create({
icon: {
width: 20,
height: 20,
},
});
import React, {useCallback, useMemo, useRef, useState} from 'react';
import {StyleSheet, View} from 'react-native';
import MapView, {Polyline} from 'react-native-maps';
import {useSharedValue} from 'react-native-reanimated';
import {dimensions} from '../../constants/theme';
import {useLoadDone} from '../../utils/hooks';
import AnimatePolyline from './AnimatePolyline';
import XemLaiSheet from './XemLaiSheet';
import Direction from './routes.json';
import BottomSheet from '@gorhom/bottom-sheet';
const FDirection = Direction.map(el => ({
latitude: el[1],
longitude: el[0],
}));
const DirectionRoutes = [...FDirection];
const initialRegion = {
latitude: 20.998384443680877,
longitude: 105.84221005439758,
latitudeDelta: 0.0922,
longitudeDelta: 0.0421,
};
const MapTracking = () => {
const mapview = useRef();
const bottomSheetRef = useRef(null);
const sliderRef = useRef(null);
const isPlaying = useRef(false);
const [disable, setDisable] = useState(false);
const cIndex = useSharedValue(0);
const mounted = useLoadDone();
const snapPoints = useMemo(
() => [100 + dimensions.BOTTOM_SPACE, 350 + dimensions.BOTTOM_SPACE],
[],
);
const onChange = index => {
if (index == 1) {
setDisable(true);
} else {
setDisable(false);
}
};
const onSelect = useCallback(values => {
bottomSheetRef.current?.snapTo(0);
}, []);
if (!mounted) return null;
return (
<View style={styles.container}>
<MapView
ref={mapview}
// provider="google"
initialRegion={initialRegion}
style={StyleSheet.absoluteFillObject}
showsUserLocation={false}
showsMyLocationButton={false}
rotateEnabled={false}
pitchEnabled={false}
// mapType={mapType}
>
<Polyline coordinates={FDirection} strokeColor="#666" strokeWidth={5} />
<AnimatePolyline
Direction={DirectionRoutes}
index={cIndex}
isPlaying={isPlaying}
/>
</MapView>
<BottomSheet
// waitFor={[sliderRef]}
// enableContentPanningGesture={false}
// simultaneousHandlers={sliderRef}
ref={bottomSheetRef}
snapPoints={snapPoints}
activeOffsetY={[-1, 1]}
failOffsetX={[-5, 5]}
onChange={onChange}>
<XemLaiSheet
ref={sliderRef}
index={cIndex}
max={FDirection.length}
isPlaying={isPlaying}
data={DirectionRoutes}
disable={disable}
onSelect={onSelect}
/>
</BottomSheet>
</View>
);
};
export default MapTracking;
const styles = StyleSheet.create({
container: {
flex: 1,
},
});
[
[105.84818601608276, 20.997933707711468],
[105.84773540496825, 20.997953740450114],
[105.8476173877716, 20.997743396560207],
[105.84759593009947, 20.997492986780994],
[105.84755301475523, 20.997202510910867],
[105.84749937057495, 20.996952100224355],
[105.84739208221434, 20.996711705570117],
[105.847327709198, 20.996541425789154],
[105.84674835205077, 20.996611541016613],
[105.84634065628052, 20.996691672664795],
[105.84587931632996, 20.996791837164547],
[105.84548234939575, 20.99690201803664],
[105.8447742462158, 20.99709233026055],
[105.844269990921, 20.997232560165017],
[105.8436369895935, 20.997362773529797],
[105.84300398826599, 20.997513019578786],
[105.84251046180725, 20.997683298251506],
[105.84240317344666, 20.997943724081136],
[105.84221005439758, 20.998384443680877],
[105.84208130836487, 20.998745031476464],
[105.84190964698792, 20.999145683561068],
[105.84173798561096, 20.999606432128886],
[105.84160923957825, 20.99990691955907],
[105.84145903587341, 21.000167341509236],
[105.84137320518494, 21.00040773059842],
[105.84135174751282, 21.00104876627674],
[105.841383934021, 21.001579621863783],
[105.8414375782013, 21.00172986366826],
[105.84168434143066, 21.001840040895395],
[105.84226369857788, 21.001840040895395],
[105.84269285202026, 21.001850057002923],
[105.84306836128235, 21.001820008678333],
[105.84349751472473, 21.001830024787196],
[105.8439266681671, 21.001830024787196],
[105.84428071975708, 21.001830024787196],
[105.84461331367491, 21.001809992568784],
[105.8449351787567, 21.001809992568784],
[105.84534287452698, 21.001799976458575],
[105.84571838378906, 21.001809992568784],
[105.84597587585449, 21.001799976458575],
[105.84622263908386, 21.001820008678333],
[105.84609389305115, 21.001980266339594],
[105.8459436893463, 21.002150539916123],
[105.84577202796936, 21.002320813298397],
[105.8456003665924, 21.002501102550237],
[105.84545016288757, 21.00266135948015],
[105.84568619728088, 21.002871696439488],
[105.84592223167418, 21.003031952971437],
[105.84609389305115, 21.00318219331385],
[105.84626555442809, 21.003292369468824],
[105.84642648696898, 21.003422577547102],
[105.84628701210022, 21.003602865467933],
[105.84644794464111, 21.00371304131233],
[105.84663033485413, 21.00385326499665],
[105.84678053855896, 21.00399348854922],
[105.84694147109985, 21.003823217075396],
[105.84713459014893, 21.003983472585546],
[105.84724187850952, 21.004043568357485],
[105.84752082824707, 21.00374308925579],
[105.84771394729613, 21.00356280150435],
[105.84786415100098, 21.003392529539116],
[105.84801435470581, 21.00330238547887],
[105.84826111793518, 21.00305198502583],
[105.8484435081482, 21.002851664360897],
[105.8486795425415, 21.002611279208033],
[105.84893703460693, 21.00234084544826],
[105.84864735603333, 21.002070411198428],
[105.84836840629578, 21.001860073109775],
[105.84799289703369, 21.001509508969946]
]
import Slider from '@react-native-community/slider';
import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
import {StyleSheet, Text, TouchableOpacity, View, Platform} from 'react-native';
import Animated, {useAnimatedProps} from 'react-native-reanimated';
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
import Block from '../../components/Block';
import DatePicker from '../../components/DatePicker';
import {colors, dimensions} from '../../constants/theme';
import moment from 'moment';
import BaseButton from '../../components/BaseButton';
import Spacer from '../../components/Spacer';
import DateWheelPicker from '../truc_tuyen/components/DateWheelPicker';
const AnimatedSlider = Animated.createAnimatedComponent(Slider);
const XemLaiSheet = React.forwardRef(
({index, isPlaying, max, data, disable, onSelect}, ref) => {
const interval = useRef(null);
const datePickerRef = useRef(null);
const [playing, setPlaying] = useState(false);
const [item, setItem] = useState(data[0]);
const [startTime, setStartTime] = useState(moment().toDate());
const [endTime, setEndTime] = useState(moment().toDate());
const onPlay = () => {
if (isPlaying.current) {
clear();
} else {
setPlayStatus(true);
interval.current = setInterval(onStartImp, 500);
}
};
const setPlayStatus = val => {
isPlaying.current = val;
setPlaying(val);
};
const onStartImp = () => {
if (index.value < max - 1) {
index.value += 1;
} else {
index.value = 0;
clear();
}
setItem(data[index.value]);
};
const clear = () => {
setPlayStatus(false);
if (interval.current) clearInterval(interval.current);
};
const onValueChange = val => {
index.value = val;
setItem(data[index.value]);
};
const onSlidingStart = () => {
clear();
setPlayStatus(false);
};
useEffect(() => {
if (disable) {
clear();
}
}, [disable]);
useEffect(() => {
return () => {
if (interval.current) {
clearInterval(interval.current);
}
};
}, []);
const onDateSelect = useCallback(() => {
const values = datePickerRef.current?.getValues();
setStartTime(values[0]);
setEndTime(values[1]);
onSelect(values);
}, []);
const onCancelSelect = useCallback(() => {
onSelect(null);
}, [onSelect]);
const animatedSliderProps = useAnimatedProps(() => {
return {
value: index.value,
};
}, [index]);
const btnStyle = useMemo(
() => ({borderColor: disable ? colors.GRAY : colors.IOS_BTN}),
[disable],
);
const txtBtnStyle = useMemo(
() => ({
color: disable ? colors.GRAY : colors.IOS_BTN,
letterSpacing: 0.6,
}),
[disable],
);
return (
<View>
<Block padding={[0, 15]}>
<Text>{JSON.stringify(item)}</Text>
<Block row center>
<TouchableOpacity
onPress={onPlay}
disabled={disable}
style={[styles.t1, btnStyle]}>
<Icon
name={playing ? 'pause' : 'play'}
size={20}
color={disable ? colors.GRAY : colors.IOS_BTN}
/>
</TouchableOpacity>
<AnimatedSlider
ref={ref}
style={styles.s1}
minimumValue={0}
maximumValue={max - 1}
step={1}
animatedProps={animatedSliderProps}
onValueChange={onValueChange}
onSlidingStart={onSlidingStart}
disabled={disable}
/>
<TouchableOpacity onPress={onPlay} style={[styles.t1, btnStyle]}>
<Text style={txtBtnStyle}>1x</Text>
</TouchableOpacity>
</Block>
</Block>
<DateWheelPicker
ref={datePickerRef}
startTime={startTime}
endTime={endTime}
/>
<View style={styles.v1}>
<BaseButton
label={'HUỶ'}
style={styles.b1}
onPress={onCancelSelect}
/>
<Spacer width={15} />
<BaseButton label={'CHỌN'} style={styles.b2} onPress={onDateSelect} />
</View>
</View>
);
},
);
export default XemLaiSheet;
const styles = StyleSheet.create({
b1: {
flex: 1,
backgroundColor: colors.ERROR,
},
b2: {
flex: 1,
},
s1: {flex: 1, marginHorizontal: 10},
t1: {
width: 28,
height: 28,
borderWidth: 1,
borderRadius: 14,
justifyContent: 'center',
alignItems: 'center',
},
v1: {
marginTop: -20,
flexDirection: 'row',
paddingHorizontal: 15,
},
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment