Create Animated Map Markers Linked to Scrolling Content in React Native

Jason Brown
InstructorJason Brown
Share this video with your friends

Social Share Links

Send Tweet
Published 7 years ago
Updated 5 years ago

We'll construct a map using react-native-maps and custom animated map makers. We'll build a scroll view that when scrolled horizontally it will animate the marker that it is associated with. Once an item is scrolled to the map will automatically animate and center the map at the markers coordinates.

This will be built with a single Animated.Value and heavy use of interpolate along with extrapolate: "clamp" to lock our outputRange values.

[00:00] Let's take a quick look at our current setup. We're React Native Maps for our maps. We have a series of images that we will render inside of our scroll view. We're pulling off the width and the height and determining the size of our card height based upon the height, so our card heights will be a quarter width of the screen.

[00:21] Then additionally, we will have some markers set up. These will coordinates on the screen, as well as we will putting our images there, as well as some text. Finally, we have our map view setup with our initial region, and then we are getting a reference to our map.

[00:37] Now, we'll start by looping over markers. We'll say this.state.markers.map. We will get our marker and our index, and then return a mapview.marker.

[01:01] We'll start by passing in key equals index, just to prevent warnings. Then also, we'll say coordinate is equal to marker.coordinate, which has the latitude and longitude. Now, in order to render custom markers on the screen rather than just the normal pen, you just render them as children of the map view marker.

[01:22] I'll first do an animated.view with a style of styles.marker wrap. This will help us do our animations later. This then will render animated.view again, and say style equals style.ring. This will be our expanding ring. Then we can just render our regular view, and say style equals styles.marker.

[01:50] Now, if we go ahead and refresh this, we can see that we are now rendering four different markers on the screen with custom views. Now, we'll set up our scroll view of content.

[02:02] Our scroll view of content will live outside of our map view We'll say animated.scrollview, and for our properties, we will say it's horizontal. We will say scroll event throttle is equal to one, because we'll be using native animations.

[02:28] We will say show horizontal scroll indicator is equal to false. Then we'll say snap to interval is equivalent of our card width. Now, this is an iOS-only property, so it'll just help the scroll view snap to the indices of each card, only on iOS, but not on Android.

[02:55] Then we'll add our two styles. We'll say style is equal to styles.scrollview that we have set up. Then additionally, we'll say content container style, which there is an inner view inside of the scroll view that wraps all of the content.

[03:09] We'll give that style.endpadding. This will just add some padding at the end so that you can actually scroll past the last item, otherwise you would never be able to select the last item.

[03:21] Now, we'll also loop over our markers here, because they contain all the information, so this.state.markers.map. Get our marker and our index. Here, we'll return a view, which is our wrapping view, which will have the key of our index, and the styles equal to styles.card.

[03:47] Then we'll render our image, and we'll say source is equal to our marker.image, give it our style. Style is equal to styles.cardimage. Then resize mode. We'll recover. Now, when we refresh, we can see that we are now setting up our scroll view at the bottom with our images and content.

[04:15] Now, we'll render our text content below that. We'll say view is equal to styles.textcontent. We'll then render our text, and we'll just say number of lines is equal to one. Because of the potential different screen sizes and stuff, we'll just limit everything to one line, and let ellipsize.

[04:38] Then we'll give our style of styles.cardtitle, and render our marker.title. Close that. Then we'll do the same thing for our description, where I'll limit it to one line. Say style is equal to card.description. Render our marker.description.

[05:10] Now, I'm going to refresh. We can see we have our card title description, and our text ellipsizing below our images that are covering the specified space that we've given it.

[05:24] Now, we need to go set up our animation. We'll go create a componentWillMount life cycle method and say this.animation is equal to a new animated value that we'll just set to zero. Now, on our animated scroll view, we'll need to set up onscroll, and use our animated.event, which'll automatically map the event to us.

[05:50] We'll say the array, the first argument is going to be the event. We'll then grab the native event, get the content offset of our native event, and get the X. Then pass that to this.animation, so every time that we scroll from left to right, it'll automatically update our this.animation.

[06:11] Then we will pass in useNativeDriver as true. That way, all of our animations will take place on the native side. To make our animations work, we're going to need to set up some interpolations based upon the scroll from left to right and the size of our card.

[06:34] We're going to say const interpolations is equal to this.state.markers. We're just going to map over our markers. We'll also need the index. We'll need to construct our input range. Our input range will be from a negative value to the current value of when we want the thing to actually be active, and then also when we want it to be inactive.

[07:01] We'll say constant inputrange is equal to an array. We'll say index minus one times the card width. Then when we want our thing to be active is when the scroll view is at the index times the card width. That will be just index times cardwidth.

[07:21] Then when we want our marker to be inactive, we will index plus one times the card width. Now that we have our input range set up based upon each of our cards, we can say const scale is equal to this.animation.interpolate, pass in our input range, and then specify our output range.

[07:46] For scale, we'll say when it's inactive, it'll just be one. When it's active, it'll be 2.5. Then when it's inactive again, it'll be back to one scale. We'll be sure to extrapolate clamp, so that regardless of when we animate, or how far this animation value gets outside of our range, it will never go beyond one on either side.

[08:08] Then we'll set up our opacity. We'll say const opacity is equal to this.animation.interpolate, pass in our input range as well. Our output range will be when it's inactive, it will be 35. When it's active, it'll be a full opacity. Then it'll be back down to 35 percent opacity.

[08:27] Once again, we'll have to say extrapolateClamp here, otherwise as this animation continues, this will bypass the 35 percent animation, and go all the back to zero. Then we won't be able to see all of the markers. Then we'll just return scale and opacity here so we can use that later.

[08:46] Now that we have our interpolations, we need to now set up our styles based upon each particular marker. We'll say const scalestyle is equal to a transform, which is an array of transforms. In our case, we'll say scale, and say interpolations, the index of our marker.scale. Then we'll also set up our opacity style, and say opacity is equal to interpolations index.opacity.

[09:22] Now, we need to pass those into the appropriate views. The first thing we'll do is make this array, so we can combine styles. We'll just pass opacity style into our outer animated wrapping view, so that way, the whole entire marker will be opaque, and be controlled by that particular animation. Then for our ring, we'll say scale style, because we will just have the outer ring scale.

[09:48] Now, if we refresh, we can see that because we're on the first index of zero, then this is scaled up and fully opaque, and the rest are at the 35 percent opacity. As we scroll, we can see that as we transition in between each of the different indexes that the animated views and interpolation will respond accordingly.

[10:16] Now, it appears that we also still have our indicator. Let's go take a look at that. We can see that it should be shows horizontal indicator, rather than show horizontal scroll indicator. Now, when we refresh, we can see that there's no more horizontal indicator whenever we scroll.

[10:33] The final thing that we'll do is set up componentDidMount. We'll say this.index is equal to zero, so we can track our active index. Then we'll just paste this code, and we'll take a look at it. We'll attach a listener so that we can get access to the value, and respond as the particular scroll view is scrolling.

[10:52] We'll then get the value divided by the card width plus .3, so we have some threshold as it's scrolling. This will be able to calculate the index that we are currently look at based upon the amount that we've scrolled.

[11:07] If we are greater than the amount of markers that we have, then we'll just say we're at the last index. Same for if we bounce to the left here. We'll just always stay at this index of zero. Then we'll set up a pseudo-debounce, where we are setting a timeout every 10 milliseconds.

[11:25] Because this event listener is called every single time that this is scrolling, this will be able to wait until we're done scrolling to animate the region change between the markers. Then we'll just say if the previous index does not equal the index that we're currently at, then we will animate the region on the ref that we've accessed.

[11:44] This is the ref to the map view. Animate to the region of the coordinate, so the middle of the map will be the coordinate of the marker. Then we'll just access the same latitude delta and longitude delta, and animate over 350 milliseconds.

[12:02] When we refresh, we can drag this. Once we're done scrolling, or once that 10 milliseconds elapse from when the callback happens, we will focus on that particular map view marker.

Paul Barry
Paul Barry
~ 6 years ago

Any tips on if you want to implement the feature to also allow you to tap on any marker and have it scroll the list to the item corresponding to that marker as the map centers on that marker?

2enovate
2enovate
~ 6 years ago

hi I am getting an error!

Markdown supported.
Become a member to join the discussionEnroll Today