React Native Looping Animation Performance - react-native

I have a react native app and I want to have a fireworks animation playing on loop more or less indefinitely. I have implemented an animation I am happy with using the built-in react native animated library, however the performance monitor shows the UI thread down at 30 fps (and I can feel my iPhone heating up after about 10 seconds too). I can bring it back up to 60 fps by reducing the number of firework bursts or stars, but I would like to keep the amount I have if possible because it looks nicer. Are there any optimizations you see I can do to get it back to 60 fps without compromising the look of the fireworks?
import React, { useRef, useEffect, useState, useCallback } from 'react';
import { View, Animated, Easing, TouchableOpacity, Text } from 'react-native';
export default function App() {
return (
<View style={{ flex: 1, justifyContent: "center", alignItems: "center", backgroundColor: "black" }}>
<Fireworks />
</View>
);
}
const random = (min, max) => Math.floor(Math.random() * (max - min)) + min;
const NUM_BURSTS = 10;
const STAR_SIZE_MIN = 2;
const STAR_SIZE_MAX = 6;
const NUM_STARS = 200;
const NUM_OUTER_STARS = NUM_STARS / 2;
const DURATION_MIN = 1000;
const DURATION_MAX = 3000;
const BURST_SPEED = 7;
const RADIUS_MIN = 30;
const RADIUS_MAX = 100;
const Fireworks = () => {
const [bursts, setBursts] = useState([]);
useEffect(() => {
//Create all the firework bursts one time at component mount.
const newBursts = [];
for (let i = 0; i < NUM_BURSTS; i++) {
const starSize = random(STAR_SIZE_MIN, STAR_SIZE_MAX);
newBursts.push({
starSize
});
}
setBursts(newBursts);
}, []);
const renderBursts = bursts?.map((burst, index) =>
<FireworkBurst starSize={burst.starSize} key={"" + index} />
);
return (
<View style={{ position: "absolute", top: 0, left: 0, bottom: 0, right: 0 }}>
{renderBursts}
</View>
)
}
const FireworkBurst = ({ starSize }) => {
const viewRef = useRef();
const animValue = useRef(new Animated.Value(0)).current;
const [stars, setStars] = useState([]);
const loop = useRef(true);
useEffect(() => {
const radius = random(RADIUS_MIN, RADIUS_MAX);
const newStars = [];
for (let i = 0; i < NUM_STARS; i++) {
const star = generateStar(i, radius);
newStars.push(star);
}
setStars(newStars);
loop.current = true;
animate();
return () => {
loop.current = false;
}
}, []);
const generateStar = (iteration, radius) => {
const star = {
starSize
};
const end = {}
if (iteration < NUM_OUTER_STARS) {
const value = iteration / NUM_OUTER_STARS;
end.x = Math.sin(value * Math.PI * 2) * radius;
end.x += random(-10, 10);
end.y = -Math.cos(value * Math.PI * 2) * radius;
end.y += random(-10, 10);
}
else {
end.x = random(-(radius - 10), radius - 10);
end.y = random(-(radius - 10), radius - 10);
}
star.end = end;
return star;
}
const animate = useCallback(() => {
const x = random(0, 100);
const y = random(0, 100);
//set native props to avoid re-rendering
viewRef?.current?.setNativeProps({ top: `${y}%`, left: `${x}%` });
animValue.setValue(0);
Animated.timing(
animValue,
{
toValue: 1,
duration: random(DURATION_MIN, DURATION_MAX),
easing: Easing.out(Easing.poly(BURST_SPEED)),
isInteraction: false,
useNativeDriver: true
}
).start(() => {
loop.current && animate();
});
}, [animValue]);
const renderStars = stars.map((star, index) =>
<FireworkStar starSize={star.starSize} end={star.end} animValue={animValue} key={"" + index} />
);
return (
<Animated.View ref={viewRef} style={{ position: "absolute" }}>
{renderStars}
</Animated.View>
)
}
const FireworkStar = ({ starSize, end, animValue }) => {
const translateX = animValue.interpolate({
inputRange: [0, 1],
outputRange: [0, end.x]
})
const translateY = animValue.interpolate({
inputRange: [0, 1],
outputRange: [0, end.y]
})
const opacity = animValue.interpolate({
inputRange: [0, 0.9, 1],
outputRange: [1, 1, 0]
});
const hue = random(0, 255);
const color = `hsl(${hue},100%,70%)`;
return (
<Animated.View
style={{
position: "absolute", top: 0, left: 0,
height: starSize, width: starSize, borderRadius: starSize,
justifyContent: "center", alignItems: "center",
backgroundColor: color,
transform: [
{ translateX },
{ translateY }
]
}}
opacity={opacity}
>
</Animated.View>
)
}

Related

React Native Oval Scroll

How can I do a similar oval scroll?
What can I use for this?
Based on the assumption that you want something like this, I wrote a simple example
If someday the link turns out to be broken, below I attach the code additionally
import React, { useCallback, useState, useRef } from "react";
import {
FlatList,
Text,
View,
StyleSheet,
Dimensions,
Animated
} from "react-native";
const { height } = Dimensions.get("window");
const screenMiddle = height / 2;
const itemScaleOffset = height / 3;
const DATA = new Array(20).fill(0).map((...args) => ({
id: args[1],
title: args[1]
}));
// args[1] is an index, just I hate warnings
const Item = ({ title, offsetY }) => {
const [scrollEdges, setScrollEdges] = useState({
top: 0,
middle: 0,
bottom: 0
});
const onLayout = useCallback(
({
nativeEvent: {
layout: { top, height }
}
}) =>
setScrollEdges((edges) => ({
...edges,
top: top - itemScaleOffset - screenMiddle,
middle: top + height / 2 - screenMiddle,
bottom: top + height + itemScaleOffset - screenMiddle
})),
[]
);
const scale = offsetY.interpolate({
inputRange: [scrollEdges.top, scrollEdges.middle, scrollEdges.bottom],
outputRange: [0.66, 1, 0.66],
extrapolate: "clamp"
});
return (
<Animated.View
onLayout={onLayout}
style={[
{
transform: [
{
scale
}
]
},
styles.item
]}
>
<Text style={styles.title}>{title}</Text>
</Animated.View>
);
};
const keyExtractor = ({ id }) => id.toString();
const App = () => {
const offsetY = useRef(new Animated.Value(0)).current;
const renderItem = useCallback(
({ item: { title } }) => <Item title={title} offsetY={offsetY} />,
[offsetY]
);
return (
<View style={styles.app}>
<FlatList
data={DATA}
renderItem={renderItem}
keyExtractor={keyExtractor}
onScroll={Animated.event(
[
{
nativeEvent: {
contentOffset: {
y: offsetY
}
}
}
],
{
useNativeDriver: false
}
)}
/>
</View>
);
};
const styles = StyleSheet.create({
app: {
flex: 1
},
item: {
backgroundColor: "#f9c2ff",
padding: 20,
marginVertical: 8,
marginHorizontal: 16
},
title: {
fontSize: 32
}
});
export default App;
I think you should use Reanimated 2 who has a very easy sintaxis and also very powerful. Maybe in combination with RNGestureHandler.

react-native gradual transition from background color a to b

When clicking a button the color is supposed to transition gradually to another color along with the sliding animation. instead of gradually transitioning the color switches instantly. Is there any way i can get this animation to be executed slowly easing from color a to b ?
here is the code i have so far
const DriveMenuFooterAnimatedButton = () => {
const {t} = useTranslation();
const {colors} = useContext(ThemeContext);
const buttonStyle = replaceAllStyles(style, colors);
const scaledButtonWidth = scale(BUTTON_WIDTH);
const opacityAnimation = useRef(new Animated.Value(0)).current;
const translateAnimation = useRef(new Animated.Value(scaledButtonWidth * -1)).current;
const colorAnimation = useRef(new Animated.Value(0)).current;
const colorAnimationInterpolation = colorAnimation.interpolate({
inputRange: [0,300],
outputRange: ["#00AA91", "#0A0082"]
});
const {setTrainDriveState, trainDriveState} = useContext(TrainStateContext);
const restartLabel = t("drive:menu:restart");
const pauseLabel = t("drive:menu:pause");
const startLabel = t("drive:menu:start");
console.log("trainDriveState", trainDriveState);
const onPressHandler = useCallback(
() => {
const isPlay = trainDriveState === paused || trainDriveState === start;
setTrainDriveState(isPlay ? play : paused);
},[setTrainDriveState, trainDriveState]
);
useEffect(() => {
let translateValue = scaledButtonWidth * -1;
let colorValue = 0;
if (trainDriveState === paused){
translateValue = scaledButtonWidth;
} else if (trainDriveState === play){
translateValue = 0;
colorValue = 300;
}
Animated.parallel([
Animated.timing(colorAnimation,{
toValue: colorValue,
duration: ANIMATION_DURATION,
delay: 0,
useNativeDriver:false
}),
Animated.timing(translateAnimation, {
toValue: translateValue ,
delay: 0,
duration: ANIMATION_DURATION,
easing: Easing.inOut(Easing.ease),
useNativeDriver:true
})
]).start();
return () => {
};
},[translateAnimation, trainDriveState, scaledButtonWidth, colorAnimation, opacityAnimation]);
return (
<Animated.View
style={[buttonStyle["animation-container"], {backgroundColor: colorAnimationInterpolation}]}
>
<DigiPlanButton
onPress={onPressHandler}
activeOpacity={1}
customStyle={style}
>
<Animated.View
style={[
buttonStyle["text-animation-container"],
{transform: [{translateX: translateAnimation}]}
]}
>
<DigiPlanText customStyle={buttonStyle["button-text"]}>{restartLabel}</DigiPlanText>
<DigiPlanText customStyle={buttonStyle["button-text"]}>{pauseLabel}</DigiPlanText>
<DigiPlanText customStyle={buttonStyle["button-text"]}>{startLabel}</DigiPlanText>
</Animated.View>
</DigiPlanButton>
</Animated.View>
);
};
const style = {
"button-touchable": {
width: scale(BUTTON_WIDTH),
paddingHorizontal: 0,
overflow: "hidden",
backgroundColor: "transparent"
},
"text-animation-container": {
display:"flex",
flexDirection: "row",
textAlign: "center"
},
"animation-container": {
borderRadius: 4
},
"button-text": {
width: "100%",
backgroundColor: "transparent",
fontSize: "font-size-l",
textAlign: "center",
color: "primary-light"
}
};
export default DriveMenuFooterAnimatedButton;
for those facing a similar problem i managed to figure out the following solution
(props to William Candillon on youtube with his slider video on youtube)
....
const AnimatedButton = () => {
const { colors } = useContext(ThemeContext);
const mainStyle = replaceAllStyles(styles, colors);
const {t} = useTranslation();
const {setTrainDriveState, trainDriveState} = useContext(TrainStateContext);
// annimation vals
const scrollViewRef = useRef();
const x = useValue(0);
const onScroll = onScrollEvent({ x });
const backgroundColor = interpolateColor(x,{
inputRange: [0, BUTTON_WIDTH, BUTTON_WIDTH * 2],
outputRange: [ "#0A0082", "#0A0082", "#00AA91"]
});
// button labels
const restartLabel = t("drive:menu:restart");
const pauseLabel = t("drive:menu:pause");
const startLabel = t("drive:menu:start");
// scroll to label corresponding to trainDriveState
useEffect(() => {
if (scrollViewRef.current && scrollViewRef.current.getNode){
const node = scrollViewRef.current.getNode();
let labelPosition;
if (node){
if (trainDriveState === paused){
labelPosition = BUTTON_WIDTH * 2;
} else if (trainDriveState === play){
labelPosition = BUTTON_WIDTH;
} else if (trainDriveState === start){
labelPosition = 0;
}
node.scrollTo({x: labelPosition, animated: true});
}
}
}, [scrollViewRef, trainDriveState]);
const handlePress = useCallback(
() => {
const isPlay = trainDriveState === paused || trainDriveState === start;
setTrainDriveState(isPlay ? play : paused);
},
[setTrainDriveState, trainDriveState],
);
return (
<TouchableOpacity style={mainStyle.container} onPress={handlePress} activeOpacity={1} >
<Animated.View style={[mainStyle.slider, {backgroundColor} ]}>
<Animated.ScrollView
ref={scrollViewRef}
horizontal
snapToInterval={BUTTON_WIDTH}
decelerationRate="fast"
showsHorizontalScrollIndicator={false}
scrollEventThrottle={1}
scrollToOverflowEnabled={true}
scrollEnabled={false}
// bounces={false}
{...{onScroll}}
>
<Slide label={startLabel}/>
<Slide label={pauseLabel}/>
<Slide label={restartLabel}/>
</Animated.ScrollView>
</Animated.View>
</TouchableOpacity>
);
};
const styles = {
container: {
height: BUTTON_HEIGHT,
width: BUTTON_WIDTH
},
slider: {
height: BUTTON_HEIGHT,
width: BUTTON_WIDTH,
borderRadius: scale(4)
}
};
export default AnimatedButton;
...
export const BUTTON_WIDTH = scale(272);
export const BUTTON_HEIGHT = verticalScale(48);
const Slide = ({label}) => {
return (
<View style={styles.container}>
<DigiPlanText customStyle={styles.label}>{label}</DigiPlanText>
</View>
);
};
const styles = {
container: {
borderRadius: scale(4),
width: BUTTON_WIDTH,
height: BUTTON_HEIGHT,
display: "flex",
alignItems: "center",
justifyContent: "center"
},
label: {
textAlign: "center",
fontSize: "font-size-l",
color: "primary-light"
}
};
export default Slide;

React native gesture handler - element jumping when adjusting the dragging multiple times

I build slider for my project, the slider works fine but every time that I adjusting the dragging (with the Circle element) the circle jumps to his initial state let translateX = sub(x, 50 / 2);
Please help me understand what I need to add in order that the circle will continue from his last place.
I tried to overwrite translateX in onDrop function but it's not working ->
() =>
cond(
eq(state, State.ACTIVE),
call([translateX], ([v]) => {
onDrop(v);
}),
),
[state, value],
);
const onDrop = (e) => {
const diff = e - -firstNumber;
const lastOption = diff * offSet;
setTextValue((lastOption / 10).toFixed(2));
};
my code:
const width = Dimensions.get('window').width
const SLIDER_WIDTH = width - 200;
let offSet = 1000 / SLIDER_WIDTH;
let firstNumber = SLIDER_WIDTH - 25;
const [state, translationX] = useValues(
State.UNDETERMINED,
(-SLIDER_WIDTH + 100) / 2,
);
const gestureHandler = onGestureEvent({state, translationX});
const x = diffClamp(withOffset(translationX, state), -SLIDER_WIDTH + 50, 50);
let translateX = sub(x, 50 / 2);
const [textValue, setTextValue] = useState(50);
const value = round(multiply(divide(x, SLIDER_WIDTH), 100));
useCode(
() =>
cond(
eq(state, State.ACTIVE),
call([translateX], ([v]) => {
onDrop(v);
}),
),
[state, value],
);
const onDrop = (e) => {
const diff = e - -firstNumber;
const lastOption = diff * offSet;
setTextValue((lastOption / 10).toFixed(2));
};
return (
<View>
<PanGestureHandler
minDist={0}
{...gestureHandler}
onHandlerStateChange={chosenValue}>
<Animated.View
style={{
position: 'absolute',
top: 0,
left: 0,
width: CIRCLE_SIZE,
height: CIRCLE_SIZE,
transform: [{translateX}],
}}>
<Animated.View
style={{
...StyleSheet.absoluteFillObject,
}}>
<Circle {...{state}} />
</Animated.View>
</Animated.View>
</PanGestureHandler>
</View>
);
Thanks in advance.

Flatlist reference error when leaving the screen in a Carousel view

I have an autoscrolling carousel in React Native and everything is working fine with images scrolling through both automatically every X seconds and manually.
The problem is when I move away from that screen that I get the following error:
Here's is my full code...
const { width, height } = Dimensions.get("window");
let flatList;
function infiniteScroll(dataList) {
const numberOfData = dataList.length;
let scrollValue = 0,
scrolled = 0;
setInterval(function () {
scrolled++;
if (scrolled < numberOfData) scrollValue = scrollValue + width;
else {
scrollValue = 0;
scrolled = 0;
}
this.flatList.scrollToOffset({ animated: true, offset: scrollValue });
}, 3000);
}
const Carousel = (props) => {
const topTenVideos = useSelector(getTopTenVideos);
const dispatch = useDispatch();
const scrollX = new Animated.Value(0);
let position = Animated.divide(scrollX, width);
const [dataList, setDataList] = useState(topTenVideos);
const isFocused = useIsFocused();
useEffect(() => {
if (isFocused) {
setDataList(topTenVideos);
infiniteScroll(dataList);
}
}, [isFocused]);
const renderRow = (itemData) => {
return (
<CarouselItem
id={itemData.item.id}
img={itemData.item.poster}
title={itemData.item.title}
/>
);
};
return (
<View style={styles.screen}>
<FlatList
ref={(flatList) => {
this.flatList = flatList;
}}
horizontal
data={dataList}
pagingEnabled
scrollEnabled
snapToAlignment="center"
scrollEventThrottle={16}
decelerationRate={"fast"}
showsHorizontalScrollIndicator={false}
onScroll={Animated.event([
{ nativeEvent: { contentOffset: { x: scrollX } } },
])}
keyExtractor={(item, index) => "key" + index}
renderItem={renderRow}
/>
<View style={styles.dotView}>
{dataList.map((_, i) => {
let opacity = position.interpolate({
inputRange: [i - 1, i, i + 1],
outputRange: [0.3, 1, 0.3],
extrapolate: "clamp",
});
return (
<Animated.View
key={i}
style={{
opacity,
height: 8,
width: 8,
borderRadius: 6,
backgroundColor: "white",
margin: 8,
}}
/>
);
})}
</View>
</View>
);
};
It's complaining about this line this.flatList.scrollToOffset({ animated: true, offset: scrollValue });
}, 3000); which is inside my infiniteScroll function.
It looks like that when the screen loses focus, it is still searching for this.flatList.scrollToOffset.
You are not creating your ref properly, You have to use useRef hook if you are using functional component or createRef in the case of class component for making refs in your component
Have a look at this.
https://reactjs.org/docs/hooks-reference.html#useref
Create your ref like below.
const flatListRef = useRef(null)
<FlatList
ref={flatListRef}
// other props
/>
flatListRef.current.scrollToOffset({ animated: true, offset: scrollValue }) // access like this.

How to zoom in/out in react-native-map?

I am using react-native to build a map application. The api I am using is from this link: https://github.com/lelandrichardson/react-native-maps.
Below is the code I bring the map on my app. I wonder how I can give a zoom value on that map. And how I can change the zoom value when the user clicks a button on the map.
What is the zoom API I should use to achieve this?
import React, { Component, StyleSheet, View, TextInput } from "react-native";
import MapView from "react-native-maps";
class MapPage extends Component {
constructor(props) {
super(props);
this.state = {
region: {
latitude: 4.21048,
longitude: 101.97577,
latitudeDelta: 10,
longitudeDelta: 5,
},
};
}
render() {
return (
<View style={styles.container}>
<TextInput style={styles.inputText}>Map</TextInput>
<MapView
style={styles.map}
mapType={"standard"}
region={this.state.region}
zoomEnabled={true}
scrollEnabled={true}
showsScale={true}
/>
</View>
);
}
}
module.exports = MapPage;
const styles = StyleSheet.create({
map: {
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
},
container: {
flexDirection: "row",
justifyContent: "space-between",
padding: 30,
flex: 1,
alignItems: "center",
},
inputText: {
height: 36,
padding: 4,
marginRight: 5,
flex: 4,
fontSize: 18,
borderWidth: 1,
borderColor: "#48BBEC",
borderRadius: 8,
color: "#48BBEC",
},
});
You should use the animateToRegion method (see here)
It takes a region object which has latitudeDelta and longitudeDelta. Use these to set the zoom level.
Updated:
in a Region object the latitude and longitude specify the center location and latitudeDelta and longitudeDelta specify the span of the viewable map area.
This image from this blog post illustrates it well (LatΔ and LngΔ).
New React Native Maps API gives you option to call animateCamera method with zoom parameter.
const MapComponent= (props: any) => {
const map: LegacyRef<MapView> = useRef(null);
const onZoomInPress = () => {
map?.current?.getCamera().then((cam: Camera) => {
cam.zoom += 1;
map?.current?.animateCamera(cam);
});
};
return (
<View>
<MapView
ref={map}
provider={PROVIDER_GOOGLE}
region={region}>
</MapView>
<ButtonComponent
style={{position: 'absolute', bottom: 400, left: 0}}
onPress={onZoomInPress}>
Zoom In
</MainButtonBlue>
</View>
);
}
I was able to make this work using Dimensions.get('window');
const window = Dimensions.get('window');
const { width, height } = window
LONGITUDE_DELTA = LATITUD_DELTA + (width / height)
and by default set LATITUD_DELTA = 0.0922.
Then just update this values with the prop onRegionChangeComplete in the <MapView>
This is what I did and it worked out for me very well:
function getRegion(origin, destination, zoom) {
const oLat = Math.abs(origin.latitude);
const oLng = Math.abs(origin.longitude);
const dLat = Math.abs(destination.latitude);
const dLng = Math.abs(destination.longitude);
return {
latitude: (origin.latitude + destination.latitude) / 2,
longitude: (origin.longitude + destination.longitude) / 2,
latitudeDelta: Math.abs(oLat - dLat) + zoom,
longitudeDelta: Math.abs(oLng - dLng) + zoom,
};
}
I created the following based on the mercator math in https://github.com/tuupola/php_google_maps
The key function is mercatorDegreeDeltas(latitude, longitude, width, height, zoom) which returns { latitudeDelta, longitudeDelta } for the specified latitude/longitude center point, map dimensions, and zoom level (1-20).
import React from 'react';
import { useWindowDimensions } from 'react-native';
import MapView from 'react-native-maps';
import { useBottomTabBarHeight } from '#react-navigation/bottom-tabs';
import { useHeaderHeight } from '#react-navigation/elements';
const MERCATOR_OFFSET = Math.pow(2, 28);
const MERCATOR_RADIUS = MERCATOR_OFFSET / Math.PI;
function mercatorLatitudeToY(latitude) {
return Math.round(
MERCATOR_OFFSET -
(
(
MERCATOR_RADIUS *
Math.log(
(1 + Math.sin(latitude * (Math.PI / 180))) /
(1 - Math.sin(latitude * (Math.PI / 180)))
)
) / 2
)
);
}
function mercatorLongitudeToX(longitude) {
return Math.round(
MERCATOR_OFFSET +
(
(
(MERCATOR_RADIUS * longitude) * Math.PI
) / 180
)
);
}
function mercatorXToLongitude(x) {
return (
(
(x - MERCATOR_OFFSET) / MERCATOR_RADIUS
) * 180
) / Math.PI;
}
function mercatorYToLatitude(y) {
return (
(
(
Math.PI / 2
) -
(2 * Math.atan(
Math.exp(
(
y - MERCATOR_OFFSET
) / MERCATOR_RADIUS
)
)
)
) * 180
) / Math.PI;
}
function mercatorAdjustLatitudeByOffsetAndZoom(latitude, offset, zoom) {
return mercatorYToLatitude(mercatorLatitudeToY(latitude) + (offset << (21 - zoom)));
}
function mercatorAdjustLongitudeByOffsetAndZoom(longitude, offset, zoom) {
return mercatorXToLongitude(mercatorLongitudeToX(longitude) + (offset << (21 - zoom)));
}
function mercatorDegreeDeltas(latitude, longitude, width, height, zoom) {
if (!zoom) {
zoom = 20;
}
const deltaX = width / 2;
const deltaY = height / 2;
const northLatitude = mercatorAdjustLatitudeByOffsetAndZoom(latitude, deltaY * -1, zoom);
const westLongitude = mercatorAdjustLongitudeByOffsetAndZoom(longitude, deltaX * -1, zoom);
const southLatitude = mercatorAdjustLatitudeByOffsetAndZoom(latitude, deltaY, zoom);
const eastLongitude = mercatorAdjustLongitudeByOffsetAndZoom(longitude, deltaY, zoom);
const latitudeDelta = Math.abs(northLatitude - southLatitude);
const longitudeDelta = Math.abs(eastLongitude - westLongitude);
return { latitudeDelta, longitudeDelta };
}
// Somewhat arbitrarily, Riverside Park, Independence, KS 67301
const CENTER_UNITED_STATES = {
latitude: 37.24435373025407,
longitude: -95.70234410503208,
};
export default function MapViewWrapper() {
const { width, height } = useWindowDimensions();
const tabBarHeight = useBottomTabBarHeight();
const headerHeight = useHeaderHeight();
const initialRegion = React.useRef(null);
const availableHeight = height - tabBarHeight - headerHeight;
// Only calculate initial region once
if (!initialRegion.current) {
const { latitudeDelta, longitudeDelta } = mercatorDegreeDeltas(
CENTER_UNITED_STATES.latitude,
CENTER_UNITED_STATES.longitude,
width,
availableHeight,
4,
);
initialRegion.current = {
latitude: CENTER_UNITED_STATES.latitude,
longitude: CENTER_UNITED_STATES.longitude,
latitudeDelta: latitudeDelta,
longitudeDelta: longitudeDelta,
};
}
return (
<MapView
initialRegion={initialRegion.current}
style={{ width: width, height: availableHeight }}
/>
);
}
There is at least one issue: if you change the zoom from 4 to 3, it isn't centered properly, but larger zoom values work. I don't need the lower zoom values right now, so I haven't investigated the math any further (maybe some sort of overflow?).
const handleZoomIn = () => {
map.current?.getCamera().then((cam: Camera) => {
if (Platform.OS === 'android') {
cam.zoom += 1;
} else {
cam.altitude /= 2;
}
map.current?.animateCamera(cam);
});
};
const handleZoomOut = () => {
map.current?.getCamera().then((cam: Camera) => {
if (Platform.OS === 'android') {
cam.zoom -= 1;
} else {
cam.altitude *= 2;
}
map.current?.animateCamera(cam);
});
};
//Example of Pinch to Zoom Image in React Native
//https://aboutreact.com/react-native-pinch-to-zoom-image/
//import React in our code
import React from 'react';
//import all the components we are going to use
import { SafeAreaView, StyleSheet, View } from 'react-native';
//import ImageViewer which will help us to zoom Image
import ImageViewer from 'react-native-image-zoom-viewer';
const App = () => {
const images = [
{
url:
'https://raw.githubusercontent.com/AboutReact/sampleresource/master/sample_img.png',
},
{
url:
'https://raw.githubusercontent.com/AboutReact/sampleresource/master/old_logo.png',
},
];
return (
<SafeAreaView style={{ flex: 1 }}>
<View style={styles.container}>
<ImageViewer imageUrls={images} renderIndicator={() => null} />
</View>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
backgroundColor: '#F5FCFF',
flex: 1,
},
});
export default App;