Search code examples
androidreact-nativeanimationreact-native-maps

Using animateCamera in functional components in react native


Summary

I'm using react-native-map and trying to use animateCamera and animateMarkerToCoordinate both at the same time but couldn't animate the animateCamera at all.

The working part so far

The "red circle" in the gif I've shared below, should be moving from one location to another location with animation. I've taken advantage of this comment to be able to move marker with animation turning the code into functional component and it really worked. Moving marker gif

My aim

I want the camera also move besides marker, so that the red circle should always be felt like it is centered in the phone screen. All this should occur when I press "animate" button below.

I've tried to do the same thing to the mapview what I've done to marker.

I've created a state

const [mapRef, setMapRef] = useState(null);

and supplied connection between <MapView> and mapRef state using the below line beginning with ref

 return (
            <View style={...}>
                <MapView
                    ref= {mapRef => {setMapRef(mapRef);}}
                    initialRegion = {
                        ...
                      }
                    ...
                 />

So when the button is pressed it goes inside this function and try to work animateCamera function

function animateMarkerAndCamera() {

    let newCoordinate = {
        latitude: 32.601,
        longitude: 44.0172,
        latitudeDelta: 0.012,
        longitudeDelta: 0.012,
    };
    let Camera = {
        ...
    }
    if (myMarker) {
       
        myMarker.animateMarkerToCoordinate(newCoordinate, 4000);
        mapRef.animateCamera({Camera}, 4000)
    }

}

By the way Camera adjusted like it is mentioned in docs

 let Camera = {
        center: {
            latitude: newCoordinate.latitude,
            longitude: newCoordinate.longitude,
        },
        pitch: 2,
        heading: 20,
        zoom: 40
    }

So I'm expecting for it to animate just like it did for marker's animation, but not working.

Please tell me my fault.

Full code...

import React, {useState} from 'react'
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native'
import MapView, { AnimatedRegion, MarkerAnimated } from 'react-native-maps';

const PlayScreen = (props) => {

    const [myMarker, setMyMarker] = useState(null);
    const [mapRef, setMapRef] = useState(null);

    const [coordinate, setCoordinate] = useState(new AnimatedRegion({ 
        latitude: 32.5983,
        longitude: 44.0175,
        latitudeDelta: 0.012,
        longitudeDelta:0.012,
    }));
  
     function animateMarkerAndCamera() {
        
        let newCoordinate = {
            latitude: 32.601,
            longitude: 44.0172,
            latitudeDelta: 0.012,
            longitudeDelta: 0.012,
        };
        
        if(myMarker){
            myMarker.animateMarkerToCoordinate(newCoordinate,4000);
            mapRef.animateCamera(newCoordinate, 4000);
        }
        
    }
  
        return (
            <View style={styles.container}>
                <MapView
                    ref= {mapRef => {setMapRef(mapRef);}}
                    style={styles.map}
                    initialRegion={{
                        latitude: 32.5983,
                        longitude: 44.0175,
                        latitudeDelta: 0.012,
                        longitudeDelta: 0.012,
                    }}

                >
                    <MarkerAnimated 
                         ref={marker => {
                            setMyMarker(marker);
                        }} 
                        image={require('../../../Assets/Images/curlingStone.png')}
                        coordinate={coordinate}
                    />
                        
                </MapView>
                <View style={styles.buttonContainer}>
                    <TouchableOpacity
                        onPress={() => animateMarkerAndCamera()}
                        style={[styles.bubble, styles.button]}
                    >
                        <Text>Animate</Text>
                    </TouchableOpacity>
                </View>
            </View>
        );

}

const styles = StyleSheet.create({
    container: {
        ...StyleSheet.absoluteFillObject,
        justifyContent: 'flex-end',
        alignItems: 'center',
    },
    map: {
        ...StyleSheet.absoluteFillObject,
    },
    bubble: {
        flex: 1,
        backgroundColor: 'rgba(255,255,255,0.7)',
        paddingHorizontal: 18,
        paddingVertical: 12,
        borderRadius: 20,
    },
    latlng: {
        width: 200,
        alignItems: 'stretch',
    },
    button: {
        width: 80,
        paddingHorizontal: 12,
        alignItems: 'center',
        marginHorizontal: 10,
    },
    buttonContainer: {
        flexDirection: 'row',
        marginVertical: 20,
        backgroundColor: 'transparent',
    },
});
export default PlayScreen

Solution

  • So, as I've explained before, I was trying to use animateCamera and animateMarkerToCoordinate together so that it could feel like my marker, the red circle, is always at the center and only the coordinates seem to be changing.

    After a little research, I've found this and made mine similar to this code. Now I obtained what I wanted to achieve.Gif is here

    Changes I've made were written as comments

    The code is below

    import {useState, useRef} from 'react'
    import ... //same as before
    
    const PlayScreen = (props) => {
        const markerLatitude=32.5983
        const markerLongitude=44.0175
        //changed from useState to useRef
        const mapRef = useRef (null);
        const [myMarker, setMyMarker] = useState(null);
        const [coordinate, setCoordinate] = useState(new AnimatedRegion({
            latitude: markerLatitude,
            longitude: markerLongitude,
            latitudeDelta: 0.012,
            longitudeDelta: 0.012,
        }));
    
        function animateMarkerAndCamera() {
    
            let newCoordinate = {
                latitude: 32.601,
                longitude: 44.0172,
                latitudeDelta: 0.012,
                longitudeDelta: 0.012,
            };
            //camera will position itself to these coordinates.
            const newCamera= {
                center: {
                    latitude: 32.601,
                    longitude: 44.0172,
                },
                pitch: 0,
                heading: 0,
                //zoom: 17  --Use it when required
            }
            
            
            if (myMarker) {
                myMarker.animateMarkerToCoordinate(newCoordinate, 4000);
                //camera type, `newCamera`, used inside animateCamera
                mapRef.current.animateCamera(newCamera, {duration: 4000})
            }
            
    
        }
    
        return (
            <View style={styles.container}>
                <MapView
                    ref={ mapRef } //There is also change here
                    style={styles.map}
                    initialRegion={{
                        latitude: 32.5983,
                        longitude: 44.0175,
                        latitudeDelta: 0.012,
                        longitudeDelta: 0.012,
                    }}
                    //These are newly added
                    pitchEnabled ={false}
                    zoomEnabled ={false}
                >
                    <MarkerAnimated
                        ref={marker => {
                            setMyMarker(marker);
                        }}
                        {/*any kind of image can be replaced here */}
                        image={require('../../../Assets/Images/curlingStone.png')}
                        coordinate={coordinate}
                        
                    />
    
                </MapView>
                <View style={styles.buttonContainer}>
                    <TouchableOpacity
                        onPress={() => animateMarkerAndCamera()}
                        style={[styles.bubble, styles.button]}
                    >
                        <Text>Animate</Text>
                    </TouchableOpacity>
                </View>
            </View>
        );
    
    }
    export default PlayScreen
    
    const styles = StyleSheet.create({ ... //same as before