All posts
Android, Blog, UI, UX

Workcation App – Part 2. Animating Markers with MapOverlayLayout

Mariusz Brona

Mariusz Brona

Welcome to the second of series of posts about my R&D (Research & Development) project I’ve made a while ago. In this blog posts, I want to share my solutions for problems I encountered during the development of an animation idea you’ll see below.

Part 1: Fragment’s custom transition
Part 2: Animating Markers with MapOverlayLayout
Part 3: RecyclerView interaction with Animated Markers
Part 4: Shared Element Transition with RecyclerView and Scenes

Link for animation on Dribbble: https://dribbble.com/shots/2881299-Workcation-App-Map-Animation

Prelude

A few months back we’ve had a company meeting, where my friend Paweł Szymankiewicz showed the animation he’d done during his Research & Development. And I loved it. After the meeting, I decided that I will code it. I never knew what I’m going to struggle with…

GIF 1 “The animation”

Let’s start!

As we can see in the GIF above, there is a lot of going on.

  1. After clicking on the bottom menu item, we are moving to the next screen, where we can see the map being loaded with some scale/fade animation from the top, RecyclerView items loaded with translation from the bottom, markers added to the map with scale/fade animation.
  2. While scrolling the items in RecyclerView, the markers are pulsing to show their position on the map.
  3. After clicking on the item, we are transferred to the next screen, the map is animated below to show the route and start/finish marker. The RecyclerView’s item is transitioned to show some description, bigger picture, trip details and button.
  4. While returning, the transition happens again back to the RecyclerView’s item, all of the markers are shown again, the route disappears.

Pretty much. That’s why I’ve decided to show you all of the things in the series of posts. In this article, I will cover the map loading and mysterious MapWrapperLayout. Stay tuned!

The Problem

So the next step in my development was to load the map to show all of the markers provided by the “API” (simple singleton parsing JSON from assets).  Fortunately, it’s been already described in the previous post. The second thing we need to do is to load markers with fade/scale animation. Easy, right? Not really.

Unfortunately, the Google Maps API only allows us to pass BitmapDescriptor as an icon of the Marker. This is how it’s done:

As you can see on the animation gif, we have to implement scale/fade in animation on loading, scale up/scale down animation while scrolling RecyclerView and fade out when entering details layout. It would be much easier with the Animation/ViewPropertyAnimator API available. Do we have a solution for that? Yes we have!

MapOverlayLayout

So what is the solution? It’s rather simple, however, it took me a while to figure this out. We need to add a MapOverlayLayout over the SupportMapFragment from GoogleMapsApi. With a projection pulled out of the map (a projection is used to translate between on-screen location and geographic coordinates on the surface of the Earth, via

MapOverlayLayout is a custom FrameLayout with the same dimensions as the SupportMapFragment. When the map is loaded, we can pass a reference to the MapOverlayLayout and use it to add custom views with animation, move them along with the screen gestures, etc. And of course, we can do what we need – add Markers (now the custom views) with scale/fade animation, hide them, make the “pulse” animation while scrolling RecyclerView.

MapOverlayLayout – the setup

So how to setup the MapOverlayLayout to cooperate with SupportMapFragment and GoogleMap?

First, let’s look at the DetailsFragment XML:

As we can see above, there is a PulseOverlayLayout with same dimensions as SupportMapFragment placed underneath. The PulseOverlayLayout inherits from the MapOverlayLayout and adds some specific logic for the app purpose (for example adding start and finish markers to the layout after clicking on the RecyclerView item, creating PulseMarkerView – custom views I will describe later in the post).  There is also an ImageView inside the layout – that’s the placeholder we use to create fragment enter transition I described here. And that’s all the work for XML! Let’s move on to another piece 0f code – the DetailsFragment itself.

Let’s move on to another piece 0f code – the DetailsFragment itself.

As we can see above – the map is loaded exactly the same as in the previous article, with onMapReady method. After receiving this callback, we are able to update maps bounds, add markers to MapOverlayLayout and set proper listeners.

In the following code, we are moving the camera to the bounds that will show us all of the markers. Next, when camera finishes moving, we are creating markers and showing them on the map. After that, we set OnCameraIdleListener to null. It is because we don’t want to add markers when we move the camera. In the last line of code, we are setting OnCameraMoveListener to refresh all of the Markers positions on the screen.

MapOverlayLayout – how does it work?

So how it actually works?

With a projection pulled out of the map (a projection is used to translate between on-screen location and geographic coordinates on the surface of the Earth, via documentation) we are able to get x and y values of the Marker and use them to place Custom View in the place of the Marker on the MapOverlayLayout.

This approach allows us to use f.e. ViewPropertyAnimator API with custom views to animate them.

The methods used in the moveMapAndAddMarker method in DetailsFragment are visible above. We can see setters for CameraListeners, refresh method for updating the Marker position on the MapOverlayLayout; addMarker and removeMarker methods,  which are adding MarkerView to the layout and also to the list. With this approach, the MapOverlayLayout have references to all of the views added to the MapOverlayLayout. At the top of the class, we can see that we have to make our custom views to inherit from the MarkerView. It is an abstract class that inherits from View class and looks like this:

With show, hide and refresh abstract methods we can specify the way the Marker will appear, disappear or refresh. It also needs the Context, LatLng and Point. Let’s look into our implementation:

 

This is PulseMarkerView class which inherits from MarkerView. In the constructor, we are setting up the AnimatorSets for showing, hiding and “pulsing”. In overridden methods from MarkerView, we are simply starting specific AnimatorSet. There is also updatePulseViewLayoutParams method which updates the position of the PulseViewMarker on the screen. The rest is drawing on the canvas with Paints created in the constructor.

This is the effect:

Loading Markers and scaling on scroll

Refreshing the markers position while moving the map

Zooming the map

Zooming the map and scaling Markers while scrolling

Conclusion

As we can see, there is a big advantage from this approach – we can use the power of the Custom Views widely. Also, there is a very little delay when we move the map and refresh Markers position. I think it is a little price we have to pay compared to the advantages we have from this solution.

Thanks for reading! Next part will be published on Tuesday 14.03. Feel free to leave a comment if you have any questions, and if you found this blog post helpful – don’t forget to share it!

Newsletter

The post is created by Droids On Roids Team Member.
We would love to take care of your business.

Leave comment

  • Android Developer

    Nice.
    Have they added a way, though, to show the entire world map ?
    I tried various zoom values, and none worked.
    It could be useful to show entire world with markers on it, showing global events.

    • Mariusz Brona

      Hey!
      Sorry for waiting. I haven’t tried it but my first guess is to update camera to LatLng which are extreme coordinates for Earth. But it’s just wild guess.

      • Android Developer

        I’m pretty sure I tried it. Can you please try too?