Select platform: [Android](https://developers.google.com/maps/documentation/android-sdk/events.md.txt "View this page for the Android platform docs.") [iOS](https://developers.google.com/maps/documentation/ios-sdk/events.md.txt "View this page for the iOS platform docs.") [JavaScript](https://developers.google.com/maps/documentation/javascript/events.md.txt "View this page for the JavaScript platform docs.")

<br />

Using the Maps SDK for Android, you can listen to events on the map.

## Code samples

The [ApiDemos repository](https://github.com/googlemaps-samples/android-samples/blob/main/ApiDemos) on GitHub includes
samples that demonstrates events and listeners:

**Kotlin**

- [EventsDemoActivity](https://github.com/googlemaps-samples/android-samples/blob/main/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/EventsDemoActivity.kt): Map click and camera change events
- [CameraDemoActivity](https://github.com/googlemaps-samples/android-samples/blob/main/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/CameraDemoActivity.kt): Camera change events
- [CircleDemoActivity](https://github.com/googlemaps-samples/android-samples/blob/main/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/CircleDemoActivity.kt): Marker click and drag events
- [GroundOverlayDemoActivity](https://github.com/googlemaps-samples/android-samples/blob/main/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/GroundOverlayDemoActivity.kt): Ground overlay click events
- [IndoorDemoActivity](https://github.com/googlemaps-samples/android-samples/blob/main/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/IndoorDemoActivity.kt): Indoor map events
- [MarkerDemoActivity](https://github.com/googlemaps-samples/android-samples/blob/main/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/MarkerDemoActivity.kt): Marker and info window events
- [PolygonDemoActivity](https://github.com/googlemaps-samples/android-samples/blob/main/ApiDemos/project/kotlin-app/src/main/java/com/example/kotlindemos/PolygonDemoActivity.kt): Polygon events

**Java**

- [EventsDemoActivity](https://github.com/googlemaps-samples/android-samples/blob/main/ApiDemos/project/java-app/src/main/java/com/example/mapdemo/EventsDemoActivity.java): Map click and camera change events
- [CameraDemoActivity](https://github.com/googlemaps-samples/android-samples/blob/main/ApiDemos/project/java-app/src/main/java/com/example/mapdemo/CameraDemoActivity.java): Camera change events
- [CircleDemoActivity](https://github.com/googlemaps-samples/android-samples/blob/main/ApiDemos/project/java-app/src/main/java/com/example/mapdemo/CircleDemoActivity.java): Marker click and drag events
- [GroundOverlayDemoActivity](https://github.com/googlemaps-samples/android-samples/blob/main/ApiDemos/project/java-app/src/main/java/com/example/mapdemo/GroundOverlayDemoActivity.java): Ground overlay click events
- [IndoorDemoActivity](https://github.com/googlemaps-samples/android-samples/blob/main/ApiDemos/project/java-app/src/main/java/com/example/mapdemo/IndoorDemoActivity.java): Indoor map events
- [MarkerDemoActivity](https://github.com/googlemaps-samples/android-samples/blob/main/ApiDemos/project/java-app/src/main/java/com/example/mapdemo/MarkerDemoActivity.java): Marker and info window events
- [PolygonDemoActivity](https://github.com/googlemaps-samples/android-samples/blob/main/ApiDemos/project/java-app/src/main/java/com/example/mapdemo/PolygonDemoActivity.java): Polygon events

## Map click / long click events

If you want to respond to a user tapping on a point on the map, you can use an
[`OnMapClickListener`](https://developers.google.com/android/reference/com/google/android/gms/maps/GoogleMap.OnMapClickListener.md.txt) which you can set on the map by
calling `GoogleMap.setOnMapClickListener(OnMapClickListener)`. When a user
clicks (taps) somewhere on the map, you will receive an `onMapClick(LatLng)`
event that indicates the location on the map that the user clicked. Note that
if you need the corresponding location on the screen (in pixels), you can
obtain a [`Projection`](https://developers.google.com/android/reference/com/google/android/gms/maps/Projection.md.txt) from the map which allows you to convert
between latitude/longitude coordinates and screen pixel coordinates.

You can also listen for long click events with an
[`OnMapLongClickListener`](https://developers.google.com/android/reference/com/google/android/gms/maps/GoogleMap.OnMapLongClickListener.md.txt) which you can set on the
map by calling `GoogleMap.setOnMapLongClickListener(OnMapLongClickListener)`.
This listener behaves similarly to the click listener and will be notified on
long click events with an `onMapLongClick(LatLng)` callback.

## Disabling click events in lite mode

To disable click events on a map in [lite mode](https://developers.google.com/maps/documentation/android-sdk/lite.md.txt), call `setClickable()`
on the view that contains the `MapView` or `MapFragment`. This is useful,
for example, when displaying a map or maps in a list view, where you want
the click event to invoke an action unrelated to the map.

The option to disable click events is available in **lite mode only**. Disabling
click events will also make markers non-clickable. It will not affect other
controls on the map.

For a `MapView`:


### Kotlin

```kotlin
mapView?.isClickable = false

      
```

### Java

```java
if (mapView != null) {
    mapView.setClickable(false);
}

      
```

<br />

For a `MapFragment`:


### Kotlin

```kotlin
if (context is FragmentActivity) {
    val mapFragment = context.supportFragmentManager
        .findFragmentById(R.id.map) as? SupportMapFragment
    val view = mapFragment?.view
    view?.isClickable = false
}

      
```

### Java

```java
if (context instanceof FragmentActivity) {
    SupportMapFragment mapFragment =
            (SupportMapFragment)
                    ((FragmentActivity) context)
                            .getSupportFragmentManager()
                            .findFragmentById(R.id.map);
    if (mapFragment != null) {
        View view = mapFragment.getView();
        if (view != null) {
            view.setClickable(false);
        }
    }
}

      
```

<br />

## Camera change events

The map view is modeled as a *camera* looking down on a flat plane. You can
change the properties of the camera to affect the zoom level, view port and
perspective of the map. See the guide to the [camera](https://developers.google.com/maps/documentation/android-sdk/views.md.txt#the_camera_position). Users
can also affect the camera by making [gestures](https://developers.google.com/maps/documentation/android-sdk/controls.md.txt#map_gestures).

Using camera change listeners, you can keep track of camera movements. Your app
can receive notifications for camera motion start, ongoing, and end events. You
can also see *why* the camera is moving, whether it's caused by user gestures,
built-in API animations or developer-controlled movements.

The following sample illustrates all the available camera event listeners:

### Kotlin


```kotlin
/*
 * Copyright 2018 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.example.kotlindemos

import android.graphics.Color
import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.CompoundButton
import android.widget.SeekBar
import android.widget.Toast
import com.example.common_ui.R
import com.example.common_ui.catalog.Complexity
import com.example.common_ui.catalog.Framework
import com.example.common_ui.catalog.Sample
import com.example.common_ui.databinding.CameraDemoBinding
import com.google.android.gms.maps.CameraUpdate
import com.google.android.gms.maps.CameraUpdateFactory
import com.google.android.gms.maps.GoogleMap
import com.google.android.gms.maps.GoogleMap.CancelableCallback
import com.google.android.gms.maps.GoogleMap.OnCameraIdleListener
import com.google.android.gms.maps.GoogleMap.OnCameraMoveCanceledListener
import com.google.android.gms.maps.GoogleMap.OnCameraMoveListener
import com.google.android.gms.maps.GoogleMap.OnCameraMoveStartedListener
import com.google.android.gms.maps.OnMapReadyCallback
import com.google.android.gms.maps.SupportMapFragment
import com.google.android.gms.maps.model.CameraPosition
import com.google.android.gms.maps.model.LatLng
import com.google.android.gms.maps.model.MarkerOptions
import com.google.android.gms.maps.model.PolylineOptions

/**
 * This shows how to change the camera position for the map.
 */
@Sample(
    id = "com.example.kotlindemos.CameraDemoActivity",
    title = "Camera Controls & Animation",
    description = "Programmatic camera panning, zooming, tilt, bearing, and smooth animations.",
    category = "Camera Controls",
    complexity = Complexity.SIMPLE,
    tags = ["#camera", "#animation", "#bearing", "#tilt", "#zoom", "#pan"],
    apiCalls = [
        "GoogleMap.animateCamera(CameraUpdate)",
        "GoogleMap.moveCamera(CameraUpdate)",
        "GoogleMap.addMarker(MarkerOptions)",
        "CameraUpdateFactory.newCameraPosition(CameraPosition)",
    ],
    purpose = "Demonstrates programmatic camera movements, animated transitions, tilt angles, and bearing rotations.",
    successCriteria = "Buttons animate camera smoothly with custom durations, stops, and rotation angles.",
    failureIndicators = "Jerky animations, unexpected camera jumps, or tilt angle exceeding platform constraints.",
    framework = Framework.KOTLIN_VIEWS,
)
class CameraDemoActivity :
    SamplesBaseActivity(),
    OnCameraMoveStartedListener,
    OnCameraMoveListener,
    OnCameraMoveCanceledListener,
    OnCameraIdleListener,
    OnMapReadyCallback {
    /**
     * The amount by which to scroll the camera. Note that this amount is in raw pixels, not dp
     * (density-independent pixels).
     */
    private val SCROLL_BY_PX = 100
    private val TAG = CameraDemoActivity::class.java.name
    private val sydneyLatLng = LatLng(-33.87365, 151.20689)
    private val bondiLocation: CameraPosition = CameraPosition.Builder()
        .target(LatLng(-33.891614, 151.276417))
        .zoom(15.5f)
        .bearing(300f)
        .tilt(50f)
        .build()

    private val sydneyLocation: CameraPosition = CameraPosition.Builder()
        .target(LatLng(-33.87365, 151.20689))
        .zoom(15.5f)
        .bearing(0f)
        .tilt(25f)
        .build()

    private lateinit var map: GoogleMap

    private lateinit var animateToggle: CompoundButton
    private lateinit var customDurationToggle: CompoundButton
    private lateinit var customDurationBar: SeekBar
    private var currPolylineOptions: PolylineOptions? = null
    private var isCanceled = false
    private lateinit var binding: CameraDemoBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = CameraDemoBinding.inflate(layoutInflater)
        setContentView(binding.root)
        animateToggle = binding.animate
        customDurationToggle = binding.durationToggle
        customDurationBar = binding.durationBar

        updateEnabledState()

        val mapFragment = supportFragmentManager.findFragmentById(R.id.map) as SupportMapFragment
        mapFragment.getMapAsync(this)
        applyInsets(binding.mapContainer)

        binding.bondi.setOnClickListener(this::onGoToBondi)
        binding.sydney.setOnClickListener(this::onGoToSydney)
        binding.stopAnimation.setOnClickListener(this::onStopAnimation)
        binding.animate.setOnClickListener(this::onToggleAnimate)
        binding.scrollLeft.setOnClickListener(this::onScrollLeft)
        binding.scrollUp.setOnClickListener(this::onScrollUp)
        binding.scrollDown.setOnClickListener(this::onScrollDown)
        binding.scrollRight.setOnClickListener(this::onScrollRight)
        binding.zoomIn.setOnClickListener(this::onZoomIn)
        binding.zoomOut.setOnClickListener(this::onZoomOut)
        binding.tiltMore.setOnClickListener(this::onTiltMore)
        binding.tiltLess.setOnClickListener(this::onTiltLess)
        binding.durationToggle.setOnClickListener(this::onToggleCustomDuration)
    }

    override fun onResume() {
        super.onResume()
        updateEnabledState()
    }

    override fun onMapReady(googleMap: GoogleMap) {
        map = googleMap
        // return early if the map was not initialised properly
        with(googleMap) {
            setOnCameraIdleListener(this@CameraDemoActivity)
            setOnCameraMoveStartedListener(this@CameraDemoActivity)
            setOnCameraMoveListener(this@CameraDemoActivity)
            setOnCameraMoveCanceledListener(this@CameraDemoActivity)
            // We will provide our own zoom controls.
            uiSettings.isZoomControlsEnabled = false
            uiSettings.isMyLocationButtonEnabled = true

            // Show Sydney
            moveCamera(CameraUpdateFactory.newLatLngZoom(sydneyLatLng, 10f))
            addMarker(MarkerOptions().position(sydneyLatLng).title("Sydney"))
            addMarker(MarkerOptions().position(LatLng(-33.891614, 151.276417)).title("Bondi Beach"))
        }
    }


    /**
     * When the map is not ready the CameraUpdateFactory cannot be used. This should be used to wrap
     * all entry points that call methods on the Google Maps API.
     *
     * @param stuffToDo the code to be executed if the map is initialised
     */
    private fun checkReadyThen(stuffToDo: () -> Unit) {
        if (!::map.isInitialized) {
            Toast.makeText(this, R.string.map_not_ready, Toast.LENGTH_SHORT).show()
        } else {
            stuffToDo()
        }
    }

    /**
     * Called when the Go To Bondi button is clicked.
     */
    @Suppress("UNUSED_PARAMETER")
    fun onGoToBondi(view: View) {
        checkReadyThen {
            changeCamera(CameraUpdateFactory.newCameraPosition(bondiLocation))
        }
    }

    /**
     * Called when the Animate To Sydney button is clicked.
     */
    @Suppress("UNUSED_PARAMETER")
    fun onGoToSydney(view: View) {
        checkReadyThen {
            changeCamera(
                CameraUpdateFactory.newCameraPosition(sydneyLocation),
                object : CancelableCallback {
                    override fun onFinish() {
                        Toast.makeText(
                            baseContext,
                            "Animation to Sydney complete",
                            Toast.LENGTH_SHORT,
                        ).show()
                    }

                    override fun onCancel() {
                        Toast.makeText(
                            baseContext,
                            "Animation to Sydney canceled",
                            Toast.LENGTH_SHORT,
                        ).show()
                    }
                },
            )
        }
    }

    /**
     * Called when the stop button is clicked.
     */
    @Suppress("UNUSED_PARAMETER")
    fun onStopAnimation(view: View) = checkReadyThen { map.stopAnimation() }

    /**
     * Called when the zoom in button (the one with the +) is clicked.
     */
    @Suppress("UNUSED_PARAMETER")
    fun onZoomIn(view: View) = checkReadyThen { changeCamera(CameraUpdateFactory.zoomIn()) }

    /**
     * Called when the zoom out button (the one with the -) is clicked.
     */
    @Suppress("UNUSED_PARAMETER")
    fun onZoomOut(view: View) = checkReadyThen { changeCamera(CameraUpdateFactory.zoomOut()) }

    /**
     * Called when the tilt more button (the one with the /) is clicked.
     */
    @Suppress("UNUSED_PARAMETER")
    fun onTiltMore(view: View) {
        checkReadyThen {
            val newTilt = Math.min(map.cameraPosition.tilt + 10, 90F)
            val cameraPosition = CameraPosition.Builder(map.cameraPosition).tilt(newTilt).build()

            changeCamera(CameraUpdateFactory.newCameraPosition(cameraPosition))
        }
    }

    /**
     * Called when the tilt less button (the one with the \) is clicked.
     */
    @Suppress("UNUSED_PARAMETER")
    fun onTiltLess(view: View) {
        checkReadyThen {
            val newTilt = Math.max(map.cameraPosition.tilt - 10, 0F)
            val cameraPosition = CameraPosition.Builder(map.cameraPosition).tilt(newTilt).build()

            changeCamera(CameraUpdateFactory.newCameraPosition(cameraPosition))
        }
    }

    /**
     * Called when the left arrow button is clicked. This causes the camera to move to the left
     */
    @Suppress("UNUSED_PARAMETER")
    fun onScrollLeft(view: View) {
        checkReadyThen {
            changeCamera(CameraUpdateFactory.scrollBy((-SCROLL_BY_PX).toFloat(), 0f))
        }
    }

    /**
     * Called when the right arrow button is clicked. This causes the camera to move to the right.
     */
    @Suppress("UNUSED_PARAMETER")
    fun onScrollRight(view: View) {
        checkReadyThen {
            changeCamera(CameraUpdateFactory.scrollBy(SCROLL_BY_PX.toFloat(), 0f))
        }
    }

    /**
     * Called when the up arrow button is clicked. The causes the camera to move up.
     */
    @Suppress("UNUSED_PARAMETER")
    fun onScrollUp(view: View) {
        checkReadyThen {
            changeCamera(CameraUpdateFactory.scrollBy(0f, (-SCROLL_BY_PX).toFloat()))
        }
    }

    /**
     * Called when the down arrow button is clicked. This causes the camera to move down.
     */
    @Suppress("UNUSED_PARAMETER")
    fun onScrollDown(view: View) {
        checkReadyThen {
            changeCamera(CameraUpdateFactory.scrollBy(0f, SCROLL_BY_PX.toFloat()))
        }
    }

    /**
     * Called when the animate button is toggled
     */
    @Suppress("UNUSED_PARAMETER")
    fun onToggleAnimate(view: View) = updateEnabledState()

    /**
     * Called when the custom duration checkbox is toggled
     */
    @Suppress("UNUSED_PARAMETER")
    fun onToggleCustomDuration(view: View) = updateEnabledState()

    /**
     * Update the enabled state of the custom duration controls.
     */
    private fun updateEnabledState() {
        customDurationToggle.isEnabled = animateToggle.isChecked
        customDurationBar.isEnabled = animateToggle.isChecked && customDurationToggle.isChecked
    }

    /**
     * Change the camera position by moving or animating the camera depending on the state of the
     * animate toggle button.
     */
    private fun changeCamera(update: CameraUpdate, callback: CancelableCallback? = null) {
        if (animateToggle.isChecked) {
            if (customDurationToggle.isChecked) {
                // The duration must be strictly positive so we make it at least 1.
                map.animateCamera(update, Math.max(customDurationBar.progress, 1), callback)
            } else {
                map.animateCamera(update, callback)
            }
        } else {
            map.moveCamera(update)
        }
    }

    override fun onCameraMoveStarted(reason: Int) {
        if (!isCanceled) map.clear()

        var reasonText = "UNKNOWN_REASON"
        currPolylineOptions = PolylineOptions().width(5f)
        when (reason) {
            OnCameraMoveStartedListener.REASON_GESTURE -> {
                currPolylineOptions?.color(Color.BLUE)
                reasonText = "GESTURE"
            }

            OnCameraMoveStartedListener.REASON_API_ANIMATION -> {
                currPolylineOptions?.color(Color.RED)
                reasonText = "API_ANIMATION"
            }

            OnCameraMoveStartedListener.REASON_DEVELOPER_ANIMATION -> {
                currPolylineOptions?.color(Color.GREEN)
                reasonText = "DEVELOPER_ANIMATION"
            }
        }
        Log.d(TAG, "onCameraMoveStarted($reasonText)")
        addCameraTargetToPath()
    }


    /**
     * Ensures that currPolyLine options is not null before accessing it
     *
     * @param stuffToDo the code to be executed if currPolylineOptions is not null
     */
    private fun checkPolylineThen(stuffToDo: () -> Unit) {
        if (currPolylineOptions != null) stuffToDo()
    }

    override fun onCameraMove() {
        Log.d(TAG, "onCameraMove")
        // When the camera is moving, add its target to the current path we'll draw on the map.
        checkPolylineThen { addCameraTargetToPath() }
    }

    override fun onCameraMoveCanceled() {
        // When the camera stops moving, add its target to the current path, and draw it on the map.
        checkPolylineThen {
            addCameraTargetToPath()
            map.addPolyline(currPolylineOptions!!)
        }

        isCanceled = true // Set to clear the map when dragging starts again.
        currPolylineOptions = null
        Log.d(TAG, "onCameraMoveCancelled")
    }

    override fun onCameraIdle() {
        checkPolylineThen {
            addCameraTargetToPath()
            map.addPolyline(currPolylineOptions!!)
        }

        currPolylineOptions = null
        isCanceled = false // Set to *not* clear the map when dragging starts again.
        Log.d(TAG, "onCameraIdle")
    }

    private fun addCameraTargetToPath() {
        currPolylineOptions?.add(map.cameraPosition.target)
    }
}
```

<br />

### Java


```java
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package com.example.mapdemo;

import android.graphics.Color;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.CompoundButton;
import android.widget.SeekBar;
import android.widget.Toast;
import com.example.common_ui.catalog.Complexity;
import com.example.common_ui.catalog.Framework;
import com.example.common_ui.catalog.Sample;
import com.example.common_ui.databinding.CameraDemoBinding;
import com.google.android.gms.maps.CameraUpdate;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.GoogleMap.CancelableCallback;
import com.google.android.gms.maps.GoogleMap.OnCameraIdleListener;
import com.google.android.gms.maps.GoogleMap.OnCameraMoveCanceledListener;
import com.google.android.gms.maps.GoogleMap.OnCameraMoveListener;
import com.google.android.gms.maps.GoogleMap.OnCameraMoveStartedListener;
import com.google.android.gms.maps.OnMapReadyCallback;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.model.CameraPosition;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.MarkerOptions;
import com.google.android.gms.maps.model.PolylineOptions;

/** This shows how to change the camera position for the map. */
@Sample(
        id = "camera_demo",
        title = "Camera Controls & Animation",
        description = "Programmatic camera panning, zooming, tilt, bearing, and smooth animations.",
        category = "Camera Controls",
        complexity = Complexity.SIMPLE,
        tags = {"#camera", "#animation", "#bearing", "#tilt", "#zoom", "#pan"},
        apiCalls = {
            "GoogleMap.animateCamera(CameraUpdate)",
            "GoogleMap.moveCamera(CameraUpdate)",
            "GoogleMap.addMarker(MarkerOptions)",
            "CameraUpdateFactory.newCameraPosition(CameraPosition)"
        },
        purpose =
                "Demonstrates programmatic camera movements, animated transitions, tilt angles, and bearing rotations.",
        successCriteria =
                "Buttons animate camera smoothly with custom durations, stops, and rotation angles.",
        failureIndicators =
                "Jerky animations, unexpected camera jumps, or tilt angle exceeding platform constraints.",
        framework = Framework.JAVA_VIEWS)
public class CameraDemoActivity extends SamplesBaseActivity
        implements OnCameraMoveStartedListener,
                OnCameraMoveListener,
                OnCameraMoveCanceledListener,
                OnCameraIdleListener,
                OnMapReadyCallback {
    private static final String TAG = CameraDemoActivity.class.getName();

    /**
     * The amount by which to scroll the camera. Note that this amount is in raw pixels, not dp
     * (density-independent pixels).
     */
    private static final int SCROLL_BY_PX = 100;

    public static final CameraPosition BONDI =
            new CameraPosition.Builder()
                    .target(new LatLng(-33.891614, 151.276417))
                    .zoom(15.5f)
                    .bearing(300)
                    .tilt(50)
                    .build();

    public static final CameraPosition SYDNEY =
            new CameraPosition.Builder()
                    .target(new LatLng(-33.87365, 151.20689))
                    .zoom(15.5f)
                    .bearing(0)
                    .tilt(25)
                    .build();

    private GoogleMap map;
    private CompoundButton animateToggle;
    private CompoundButton customDurationToggle;
    private SeekBar customDurationBar;
    private PolylineOptions currPolylineOptions;
    private boolean isCanceled = false;
    private CameraDemoBinding binding;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = CameraDemoBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());
        animateToggle = binding.animate;
        customDurationToggle = binding.durationToggle;
        customDurationBar = binding.durationBar;

        updateEnabledState();

        SupportMapFragment mapFragment =
                (SupportMapFragment)
                        getSupportFragmentManager()
                                .findFragmentById(com.example.common_ui.R.id.map);
        mapFragment.getMapAsync(this);
        applyInsets(binding.mapContainer);

        binding.bondi.setOnClickListener(this::onGoToBondi);
        binding.sydney.setOnClickListener(this::onGoToSydney);
        binding.stopAnimation.setOnClickListener(this::onStopAnimation);
        binding.animate.setOnClickListener(this::onToggleAnimate);
        binding.scrollLeft.setOnClickListener(this::onScrollLeft);
        binding.scrollUp.setOnClickListener(this::onScrollUp);
        binding.scrollDown.setOnClickListener(this::onScrollDown);
        binding.scrollRight.setOnClickListener(this::onScrollRight);
        binding.zoomIn.setOnClickListener(this::onZoomIn);
        binding.zoomOut.setOnClickListener(this::onZoomOut);
        binding.tiltMore.setOnClickListener(this::onTiltMore);
        binding.tiltLess.setOnClickListener(this::onTiltLess);
        binding.durationToggle.setOnClickListener(this::onToggleCustomDuration);
    }

    @Override
    protected void onResume() {
        super.onResume();
        updateEnabledState();
    }


    @Override
    public void onMapReady(GoogleMap googleMap) {
        map = googleMap;

        map.setOnCameraIdleListener(this);
        map.setOnCameraMoveStartedListener(this);
        map.setOnCameraMoveListener(this);
        map.setOnCameraMoveCanceledListener(this);
        // We will provide our own zoom controls.
        map.getUiSettings().setZoomControlsEnabled(false);
        map.getUiSettings().setMyLocationButtonEnabled(true);

        // Show Sydney
        LatLng sydneyLatLng = new LatLng(-33.87365, 151.20689);
        LatLng bondiLatLng = new LatLng(-33.891614, 151.276417);
        map.moveCamera(CameraUpdateFactory.newLatLngZoom(sydneyLatLng, 10));
        map.addMarker(new MarkerOptions().position(sydneyLatLng).title("Sydney"));
        map.addMarker(new MarkerOptions().position(bondiLatLng).title("Bondi Beach"));
    }

    public GoogleMap getMap() {
        return map;
    }


    /**
     * When the map is not ready the CameraUpdateFactory cannot be used. This should be called on
     * all entry points that call methods on the Google Maps API.
     */
    private boolean checkReady() {
        if (map == null) {
            Toast.makeText(this, com.example.common_ui.R.string.map_not_ready, Toast.LENGTH_SHORT)
                    .show();
            return false;
        }
        return true;
    }

    /** Called when the Go To Bondi button is clicked. */
    public void onGoToBondi(View view) {
        if (!checkReady()) {
            return;
        }

        changeCamera(CameraUpdateFactory.newCameraPosition(BONDI));
    }

    /** Called when the Animate To Sydney button is clicked. */
    public void onGoToSydney(View view) {
        if (!checkReady()) {
            return;
        }

        changeCamera(
                CameraUpdateFactory.newCameraPosition(SYDNEY),
                new CancelableCallback() {
                    @Override
                    public void onFinish() {
                        Toast.makeText(
                                        getBaseContext(),
                                        "Animation to Sydney complete",
                                        Toast.LENGTH_SHORT)
                                .show();
                    }

                    @Override
                    public void onCancel() {
                        Toast.makeText(
                                        getBaseContext(),
                                        "Animation to Sydney canceled",
                                        Toast.LENGTH_SHORT)
                                .show();
                    }
                });
    }

    /** Called when the stop button is clicked. */
    public void onStopAnimation(View view) {
        if (!checkReady()) {
            return;
        }

        map.stopAnimation();
    }

    /** Called when the zoom in button (the one with the +) is clicked. */
    public void onZoomIn(View view) {
        if (!checkReady()) {
            return;
        }

        changeCamera(CameraUpdateFactory.zoomIn());
    }

    /** Called when the zoom out button (the one with the -) is clicked. */
    public void onZoomOut(View view) {
        if (!checkReady()) {
            return;
        }

        changeCamera(CameraUpdateFactory.zoomOut());
    }

    /** Called when the tilt more button (the one with the /) is clicked. */
    public void onTiltMore(View view) {
        if (!checkReady()) {
            return;
        }

        CameraPosition currentCameraPosition = map.getCameraPosition();
        float currentTilt = currentCameraPosition.tilt;
        float newTilt = currentTilt + 10;

        newTilt = (newTilt > 90) ? 90 : newTilt;

        CameraPosition cameraPosition =
                new CameraPosition.Builder(currentCameraPosition).tilt(newTilt).build();

        changeCamera(CameraUpdateFactory.newCameraPosition(cameraPosition));
    }

    /** Called when the tilt less button (the one with the \) is clicked. */
    public void onTiltLess(View view) {
        if (!checkReady()) {
            return;
        }

        CameraPosition currentCameraPosition = map.getCameraPosition();

        float currentTilt = currentCameraPosition.tilt;

        float newTilt = currentTilt - 10;
        newTilt = (newTilt > 0) ? newTilt : 0;

        CameraPosition cameraPosition =
                new CameraPosition.Builder(currentCameraPosition).tilt(newTilt).build();

        changeCamera(CameraUpdateFactory.newCameraPosition(cameraPosition));
    }

    /** Called when the left arrow button is clicked. This causes the camera to move to the left */
    public void onScrollLeft(View view) {
        if (!checkReady()) {
            return;
        }

        changeCamera(CameraUpdateFactory.scrollBy(-SCROLL_BY_PX, 0));
    }

    /**
     * Called when the right arrow button is clicked. This causes the camera to move to the right.
     */
    public void onScrollRight(View view) {
        if (!checkReady()) {
            return;
        }

        changeCamera(CameraUpdateFactory.scrollBy(SCROLL_BY_PX, 0));
    }

    /** Called when the up arrow button is clicked. The causes the camera to move up. */
    public void onScrollUp(View view) {
        if (!checkReady()) {
            return;
        }

        changeCamera(CameraUpdateFactory.scrollBy(0, -SCROLL_BY_PX));
    }

    /** Called when the down arrow button is clicked. This causes the camera to move down. */
    public void onScrollDown(View view) {
        if (!checkReady()) {
            return;
        }

        changeCamera(CameraUpdateFactory.scrollBy(0, SCROLL_BY_PX));
    }

    /** Called when the animate button is toggled */
    public void onToggleAnimate(View view) {
        updateEnabledState();
    }

    /** Called when the custom duration checkbox is toggled */
    public void onToggleCustomDuration(View view) {
        updateEnabledState();
    }

    /** Update the enabled state of the custom duration controls. */
    private void updateEnabledState() {
        customDurationToggle.setEnabled(animateToggle.isChecked());
        customDurationBar.setEnabled(animateToggle.isChecked() && customDurationToggle.isChecked());
    }

    private void changeCamera(CameraUpdate update) {
        changeCamera(update, null);
    }

    /**
     * Change the camera position by moving or animating the camera depending on the state of the
     * animate toggle button.
     */
    private void changeCamera(CameraUpdate update, CancelableCallback callback) {
        if (animateToggle.isChecked()) {
            if (customDurationToggle.isChecked()) {
                int duration = customDurationBar.getProgress();
                // The duration must be strictly positive so we make it at least 1.
                map.animateCamera(update, Math.max(duration, 1), callback);
            } else {
                map.animateCamera(update, callback);
            }
        } else {
            map.moveCamera(update);
        }
    }


    @Override
    public void onCameraMoveStarted(int reason) {
        if (!isCanceled) {
            map.clear();
        }

        String reasonText = "UNKNOWN_REASON";
        currPolylineOptions = new PolylineOptions().width(5);
        switch (reason) {
            case OnCameraMoveStartedListener.REASON_GESTURE:
                currPolylineOptions.color(Color.BLUE);
                reasonText = "GESTURE";
                break;
            case OnCameraMoveStartedListener.REASON_API_ANIMATION:
                currPolylineOptions.color(Color.RED);
                reasonText = "API_ANIMATION";
                break;
            case OnCameraMoveStartedListener.REASON_DEVELOPER_ANIMATION:
                currPolylineOptions.color(Color.GREEN);
                reasonText = "DEVELOPER_ANIMATION";
                break;
        }
        Log.d(TAG, "onCameraMoveStarted(" + reasonText + ")");
        addCameraTargetToPath();
    }

    @Override
    public void onCameraMove() {
        // When the camera is moving, add its target to the current path we'll draw on the map.
        if (currPolylineOptions != null) {
            addCameraTargetToPath();
        }
        Log.d(TAG, "onCameraMove");
    }

    @Override
    public void onCameraMoveCanceled() {
        // When the camera stops moving, add its target to the current path, and draw it on the map.
        if (currPolylineOptions != null) {
            addCameraTargetToPath();
            map.addPolyline(currPolylineOptions);
        }
        isCanceled = true; // Set to clear the map when dragging starts again.
        currPolylineOptions = null;
        Log.d(TAG, "onCameraMoveCancelled");
    }

    @Override
    public void onCameraIdle() {
        if (currPolylineOptions != null) {
            addCameraTargetToPath();
            map.addPolyline(currPolylineOptions);
        }
        currPolylineOptions = null;
        isCanceled = false; // Set to *not* clear the map when dragging starts again.
        Log.d(TAG, "onCameraIdle");
    }

    private void addCameraTargetToPath() {
        LatLng target = map.getCameraPosition().target;
        currPolylineOptions.add(target);
    }
}
```

<br />

The following camera listeners are available:

- The `onCameraMoveStarted()` callback of the `OnCameraMoveStartedListener`
  is invoked when the camera starts moving. The callback method receives a
  `reason` for the camera motion. The reason can be one of the following:

  - `REASON_GESTURE` indicates that the camera moved in response to a user's gesture on the map, such as panning, tilting, pinching to zoom, or rotating the map.
  - `REASON_API_ANIMATION` indicates that the API has moved the camera in response to a non-gesture user action, such as tapping the zoom button, tapping the My Location button, or clicking a marker.
  - `REASON_DEVELOPER_ANIMATION` indicates that your app has initiated the camera movement.
- The `onCameraMove()` callback of the `OnCameraMoveListener` is invoked
  multiple times while the camera is moving or the user is interacting with
  the touch screen. As a guide to how often the callback is invoked, it's useful
  to know that the API invokes the callback once per frame. Note, however, that
  this callback is invoked asynchronously and is therefore out of synch with
  what is visible on the screen. Also note that it's possible for the camera
  position to remain unchanged between one `onCameraMove()` callback and the
  next.

- The `OnCameraIdle()` callback of the `OnCameraIdleListener` is invoked when
  the camera stops moving and the user has stopped interacting with the map.

- The `OnCameraMoveCanceled()` callback of the
  `OnCameraMoveCanceledListener` is invoked when the current camera movement
  has been interrupted. Immediately after the
  `OnCameraMoveCanceled()` callback, the `onCameraMoveStarted()` callback is
  invoked with the new `reason`.

  If your app explicitly calls
  [`GoogleMap.stopAnimation()`](https://developers.google.com/android/reference/com/google/android/gms/maps/GoogleMap.md.txt#stopAnimation()), the `OnCameraMoveCanceled()`
  callback is invoked, but the `onCameraMoveStarted()` callback is **not**
  invoked.

To set a listener on the map, call the relevant set-listener method.
For example, to request a callback from the `OnCameraMoveStartedListener`, call
`GoogleMap.setOnCameraMoveStartedListener()`.

You can get the camera's target (latitude/longitude), zoom, bearing and tilt
from the [`CameraPosition`](https://developers.google.com/android/reference/com/google/android/gms/maps/model/CameraPosition.md.txt). See the guide to
[camera position](https://developers.google.com/maps/documentation/android-sdk/views.md.txt#the_camera_position) for details about these properties.

> [!NOTE]
> **Deprecation notice:** The [`OnCameraChangeListener`](https://developers.google.com/android/reference/com/google/android/gms/maps/GoogleMap.OnCameraChangeListener.md.txt) is deprecated in favour of the listeners described above - that is, `OnCameraMoveStartedListener`, `OnCameraMoveListener`, `OnCameraMoveCanceledListener` and `OnCameraIdleListener`.

## Events on businesses and other points of interest

By default, points of interest (POIs) appear on the base map along with their
corresponding icons. POIs include parks, schools, government buildings, and
more, as well as business POIs such as shops, restaurants, and hotels.

You can respond to click events on a POI. See the guide to [businesses and other
points of interest](https://developers.google.com/maps/documentation/android-sdk/poi#poi-events).

## Indoor map events

You can use events to find and customize the active level of an indoor map. Use
the [`OnIndoorStateChangeListener`](https://developers.google.com/android/reference/com/google/android/gms/maps/GoogleMap.OnIndoorStateChangeListener.md.txt)
interface to set a listener to be called when
either a new building is focused or a new level is activated in a building.

Get the building that is currently in focus by calling
[`GoogleMap.getFocusedBuilding()`](https://developers.google.com/android/reference/com/google/android/gms/maps/GoogleMap.md.txt#getFocusedBuilding()).
Centering the map on a specific lat/long will
generally give you the building at that lat/long, but this is not guaranteed.

You can then find the currently active level by calling
[`IndoorBuilding.getActiveLevelIndex()`](https://developers.google.com/android/reference/com/google/android/gms/maps/model/IndoorBuilding.md.txt#getActiveLevelIndex()).


### Kotlin

```kotlin
map.focusedBuilding?.let { building: IndoorBuilding ->
    val activeLevelIndex = building.activeLevelIndex
    val activeLevel = building.levels[activeLevelIndex]
}

      
```

### Java

```java
IndoorBuilding building = map.getFocusedBuilding();
if (building != null) {
    int activeLevelIndex = building.getActiveLevelIndex();
    IndoorLevel activeLevel = building.getLevels().get(activeLevelIndex);
}

      
```

<br />

This is useful if you want to show custom markup for the active level, such as
[markers](https://developers.google.com/maps/documentation/android-sdk/marker.md.txt), [ground overlays](https://developers.google.com/maps/documentation/android-sdk/groundoverlay.md.txt),
[tile overlays](https://developers.google.com/maps/documentation/android-sdk/tileoverlay.md.txt), polygons, polylines, and other [shapes](https://developers.google.com/maps/documentation/android-sdk/shapes.md.txt).

Hint: To go back to street level, get the default level via
`IndoorBuilding.getDefaultLevelIndex()`, and set it as the active level via
`IndoorLevel.activate()`.

## Marker and info window events

You can listen and respond to marker events, including marker click and drag
events, by setting the corresponding listener on the `GoogleMap` object to which
the marker belongs. See the guide to [marker events](https://developers.google.com/maps/documentation/android-sdk/marker.md.txt#handle_marker_events).

You can also listen to events on [info windows](https://developers.google.com/maps/documentation/android-sdk/infowindows.md.txt#info_window_events).

## Shape and overlay events

You can listen and respond to click events on [polylines](https://developers.google.com/maps/documentation/android-sdk/shapes.md.txt#polyline-events),
[polygons](https://developers.google.com/maps/documentation/android-sdk/shapes.md.txt#polygon-events), [circles](https://developers.google.com/maps/documentation/android-sdk/shapes.md.txt#circle-events), and
[ground overlays](https://developers.google.com/maps/documentation/android-sdk/groundoverlay.md.txt#overlay-events).

> [!NOTE]
> **Note:** If multiple overlays or shapes (markers, polylines, polygons, circles and/or ground overlays) are overlaid on top of each other, the click event is cycled through the cluster of markers first, then triggered for other clickable overlays or shapes based on their z-index values. At most one event is triggered per click. In other words, the click is not passed down to the overlays or shapes with lower z-index values. Read more about [marker z-index
> and click events](https://developers.google.com/maps/documentation/android-sdk/marker#click-index).

## Location events

Your app can respond to the following events related to the My Location layer:

- If the user clicks the My Location button, your app receives an `onMyLocationButtonClick()` callback from the [`GoogleMap.OnMyLocationButtonClickListener`](https://developers.google.com/android/reference/com/google/android/gms/maps/GoogleMap.OnMyLocationButtonClickListener.md.txt).
- If the user clicks the My Location blue dot, your app receives an `onMyLocationClick()` callback from the [`GoogleMap.OnMyLocationClickListener`](https://developers.google.com/android/reference/com/google/android/gms/maps/GoogleMap.OnMyLocationClickListener.md.txt).

For details, see the guide to the [My Location layer](https://developers.google.com/maps/documentation/android-sdk/location.md.txt#my-location).