Skip to content

Multiple Surfaces

In this guide you will learn how to add/remove multiple fragment surfaces, each of which can contain an interactive map centered on a different desired location. The maps are fully 3D, supporting pan, pinch-zoom, rotate and tilt.

Setup

First, get an API key token, see the Getting Started guide.

Download the Maps & Navigation SDK for Android archive file

Download the MultipleSurfacesInFragment project archive file or clone the project with Git

See the Configure Android Example guide.

Run the example

In Android Studio, from the File menu, select Sync Project with Gradle Files

Multiple surfaces example Android screenshot

Multiple surfaces example Android screenshot

Multiple surfaces example Android screenshot

Multiple surfaces example Android screenshot

An android device should be connected via USB cable.
Press SHIFT+F10 to compile, install and run the example on the android device.
Click the envelope in the lower right corner to continue.

Click NEXT

Click the green + in the lower right corner to add a fragment with a map.

Each map can show a different location. Try to pan and zoom each map.
Click the red - in the lower left corner to remove the last added fragment.

How it works

Android example screenshot

You can open the MainActivity.kt file to see how to render multiple maps.

 1// Kotlin code
 2
 3class FirstFragment : Fragment()
 4{
 5 override fun onCreateView(
 6     inflater: LayoutInflater, container: ViewGroup?,
 7     savedInstanceState: Bundle?
 8 ): View? {
 9     return inflater.inflate(R.layout.fragment_first, container, false)
10 }
11 override fun onViewCreated(view: View, savedInstanceState: Bundle?)
12 {
13     super.onViewCreated(view, savedInstanceState)
14     view.findViewById<Button>(R.id.button_first).setOnClickListener
15     {
16         findNavController().navigate(R.id.action_FirstFragment_to_SecondFragment)
17     }
18 }
19}

The first fragment.

 1class SecondFragment : Fragment()
 2{
 3 private val maps = mutableMapOf<Long, MapView?>()
 4 private val maxSurfacesCount = 9
 5 override fun onCreateView(
 6     inflater: LayoutInflater, container: ViewGroup?,
 7     savedInstanceState: Bundle?
 8 ): View? {
 9     return inflater.inflate(R.layout.fragment_second, container, false)
10 }
11 override fun onViewCreated(view: View, savedInstanceState: Bundle?)
12 {
13     super.onViewCreated(view, savedInstanceState)
14     view.findViewById<Button>(R.id.button_second).setOnClickListener
15     {
16         findNavController().navigate(R.id.action_SecondFragment_to_FirstFragment)
17     }
18     val leftBtn = view.findViewById<FloatingActionButton>(R.id.bottomLeftButton)
19     leftBtn.visibility = View.VISIBLE
20     ButtonsDecorator.buttonAsDelete(requireContext(), leftBtn)
21     {
22         deleteLastSurface()
23     }
24     val rightBtn = view.findViewById<FloatingActionButton>(R.id.bottomRightButton)
25     rightBtn.visibility = View.VISIBLE
26     ButtonsDecorator.buttonAsAdd(requireContext(), rightBtn)
27     {
28         addSurface()
29     }
30     addSurface()
31 }
The second fragment.
Each of the 2 fragments has a button to navigate to the other fragment, see res/navigation/nav_graph.xml in the project.
The SecondFragment also has a green + and a red x button to add or remove maps, respectively, using the addSurface() and deleteLastSurface() functions.
 1 private fun addSurface()
 2 {
 3     val linearLayout = view?.findViewById<LinearLayout>(R.id.scrolledLinearLayout) ?: return
 4     if (linearLayout.childCount >= maxSurfacesCount)
 5     {
 6         return
 7     }
 8     val surface = SdkCall.execute { GemSurfaceView(requireContext()) }
 9     surface?.layoutParams = ViewGroup.LayoutParams(
10         ViewGroup.LayoutParams.MATCH_PARENT,
11         ViewGroup.LayoutParams.MATCH_PARENT
12     )
13     surface?.onScreenCreated = { screen ->
14         // Defines an action that should be done after the screen is created.
15         onScreenCreated(screen)
16     }
17     val params = FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, 400)
18     params.setMargins(50)
19     val frame = FrameLayout(requireContext())
20     frame.layoutParams = params
21     frame.addView(surface)
22     linearLayout.addView(frame)
23 }
24 private fun deleteLastSurface()
25 {
26     val linearLayout = view?.findViewById<LinearLayout>(R.id.scrolledLinearLayout) ?: return
27     if (linearLayout.childCount == 0)
28         return
29     val lastIndex = linearLayout.childCount - 1
30     val frame = (linearLayout[lastIndex] as FrameLayout)
31     val lastSurface = frame[0] as GemSurfaceView
32     SdkCall.execute
33     {
34         val mapsId = lastSurface.getScreen()?.address()
35         // Release the map view.
36         maps[mapsId]?.release()
37         // Remove the map view from the collection of displayed maps.
38         maps.remove(mapsId)
39     }
40     linearLayout.removeView(frame)
41 }
42 private fun onScreenCreated(screen: Screen)
43 {
44     SdkCall.checkCurrentThread() // Ensure we are on SDK thread.
45     /*
46     Define a rectangle in which the map view will expand.
47     Predefined value of the offsets is 0.
48     Value 1 means the offset will take 100% of available space.
49      */
50     val mainViewRect = RectF(0.0f, 0.0f, 1.0f, 1.0f)
51     // Produce a map view and establish that it is the main map view.
52     val mapView = MapView.produce(screen, mainViewRect) ?: return
53     // Add the map view to the collection of displayed maps.
54     maps[screen.address()] = mapView
55 }
56}
The addSurface() function calls onScreenCreated() which adds a map to the new surface, and also stores the map in a map container, like a list, to enable removing it later:
val mapView = MapView.produce(screen, mainViewRect)
maps[screen.address()] = mapView
The deleteLastSurface() function deletes the current map and removes it from the map container/list.
val mapsId = lastSurface.getScreen()?.address()
maps[mapsId]?.release()
maps.remove(mapsId)

Android Examples

Maps SDK for Android Examples can be downloaded or cloned with Git