Custom Location Pulsing Symbol
This example shows how to Custom Location Pulsing Style
-
Location Permissions Handling
-
Tracking current location automatically when Map ready
-
Custom location pulsing style
-
pulseColor
-
pulseSingleDuration
-
Enable/Disable pulsing
-
pulseMaxRadius
-
pulseAlpha
-

For all code examples, refer to Android Maps SDK Code Examples
activity_customized_location_pulsing_circle.xml view source
1<?xml version="1.0" encoding="utf-8"?>
2<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
3 xmlns:app="http://schemas.android.com/apk/res-auto"
4 xmlns:tools="http://schemas.android.com/tools"
5 android:layout_width="match_parent"
6 android:layout_height="match_parent">
7
8 <ai.nextbillion.maps.core.MapView
9 android:id="@+id/mapView"
10 android:layout_width="match_parent"
11 android:layout_height="match_parent"
12 android:layout_marginBottom="0dp"
13 app:layout_constraintBottom_toTopOf="@+id/linearLayout"
14 app:layout_constraintLeft_toLeftOf="parent"
15 app:layout_constraintRight_toRightOf="parent"
16 app:layout_constraintTop_toTopOf="parent"
17 app:nbmap_uiAttribution="false" />
18
19 <LinearLayout
20 android:id="@+id/linearLayout"
21 style="?android:attr/buttonBarStyle"
22 android:layout_width="0dp"
23 android:layout_height="wrap_content"
24 android:background="@color/palette_mint_100"
25 android:orientation="horizontal"
26 android:weightSum="4"
27 app:layout_constraintBottom_toBottomOf="parent"
28 app:layout_constraintLeft_toLeftOf="parent"
29 app:layout_constraintRight_toRightOf="parent"
30 tools:layout_constraintBottom_creator="1"
31 tools:layout_constraintLeft_creator="1"
32 tools:layout_constraintRight_creator="1">
33
34 <TextView
35 android:id="@+id/tv_frequency"
36 android:layout_width="0dp"
37 android:layout_height="match_parent"
38 android:layout_weight=".75"
39 android:gravity="center"
40 android:text="Duration:"
41 android:textColor="@color/white"
42 android:textSize="18sp"
43 android:textStyle="bold" />
44
45 <Button
46 android:id="@+id/button_location_circle_duration"
47 style="?android:attr/buttonBarButtonStyle"
48 android:layout_width="0dp"
49 android:layout_height="wrap_content"
50 android:layout_weight="1.25"
51 android:gravity="center"
52 tools:text="400ms"
53 android:textColor="@android:color/white" />
54
55 <TextView
56 android:id="@+id/tv_color"
57 android:layout_width="0dp"
58 android:layout_height="match_parent"
59 android:layout_weight=".85"
60 android:gravity="center"
61 android:text="Color:"
62 android:textColor="@color/white"
63 android:textSize="18sp"
64 android:textStyle="bold" />
65
66 <Button
67 android:id="@+id/button_location_circle_color"
68 style="?android:attr/buttonBarButtonStyle"
69 android:layout_width="0dp"
70 android:layout_height="wrap_content"
71 android:layout_weight="1.15"
72 android:gravity="center"
73 android:text="None"
74 android:textColor="@android:color/white" />
75
76 </LinearLayout>
77
78</androidx.constraintlayout.widget.ConstraintLayout>
CustomizedLocationPulsingCircleActivity view source
1package ai.nextbillion;
2
3import android.annotation.SuppressLint;
4import android.graphics.Color;
5import android.location.Location;
6import android.os.Bundle;
7import android.view.Menu;
8import android.view.MenuItem;
9import android.view.animation.DecelerateInterpolator;
10import android.view.animation.Interpolator;
11import android.widget.ArrayAdapter;
12import android.widget.Button;
13import android.widget.Toast;
14
15import java.util.ArrayList;
16import java.util.List;
17
18import ai.nextbillion.maps.camera.CameraUpdateFactory;
19import ai.nextbillion.maps.core.MapView;
20import ai.nextbillion.maps.core.NextbillionMap;
21import ai.nextbillion.maps.core.OnMapReadyCallback;
22import ai.nextbillion.maps.core.Style;
23import ai.nextbillion.maps.location.LocationComponent;
24import ai.nextbillion.maps.location.LocationComponentActivationOptions;
25import ai.nextbillion.maps.location.LocationComponentOptions;
26import ai.nextbillion.maps.location.engine.LocationEngineRequest;
27import ai.nextbillion.maps.location.modes.CameraMode;
28import ai.nextbillion.maps.location.permissions.PermissionsListener;
29import ai.nextbillion.maps.location.permissions.PermissionsManager;
30import androidx.annotation.NonNull;
31import androidx.appcompat.app.AppCompatActivity;
32import androidx.appcompat.widget.ListPopupWindow;
33
34
35/**
36 * This activity shows how to customize the LocationComponent's pulsing circle.
37 */
38public class CustomizedLocationPulsingCircleActivity extends AppCompatActivity implements OnMapReadyCallback {
39
40 //region
41
42 // Adjust these variables to customize the example's pulsing circle UI
43 private static final float DEFAULT_LOCATION_CIRCLE_PULSE_DURATION_MS = 2300;
44 private static final float SECOND_LOCATION_CIRCLE_PULSE_DURATION_MS = 800;
45 private static final float THIRD_LOCATION_CIRCLE_PULSE_DURATION_MS = 8000;
46 private static final float DEFAULT_LOCATION_CIRCLE_PULSE_RADIUS = 35;
47 private static final float DEFAULT_LOCATION_CIRCLE_PULSE_ALPHA = .55f;
48 private static final Interpolator DEFAULT_LOCATION_CIRCLE_INTERPOLATOR_PULSE_MODE
49 = new DecelerateInterpolator();
50 private static final boolean DEFAULT_LOCATION_CIRCLE_PULSE_FADE_MODE = true;
51 //endregion
52
53 //region
54 private static int LOCATION_CIRCLE_PULSE_COLOR;
55 private static float LOCATION_CIRCLE_PULSE_DURATION = DEFAULT_LOCATION_CIRCLE_PULSE_DURATION_MS;
56 private static final String SAVED_STATE_LOCATION = "saved_state_location";
57 private static final String SAVED_STATE_LOCATION_CIRCLE_PULSE_COLOR = "saved_state_color";
58 private static final String SAVED_STATE_LOCATION_CIRCLE_PULSE_DURATION = "saved_state_duration";
59 private static final String LAYER_BELOW_ID = "waterway-label";
60
61 private Location lastLocation;
62 private MapView mapView;
63 private Button pulsingCircleDurationButton;
64 private Button pulsingCircleColorButton;
65 private PermissionsManager permissionsManager;
66 private LocationComponent locationComponent;
67 private NextbillionMap nextbillionMap;
68 private float currentPulseDuration;
69 //endregion
70
71 @Override
72 protected void onCreate(Bundle savedInstanceState) {
73 super.onCreate(savedInstanceState);
74 setContentView(R.layout.activity_customized_location_pulsing_circle);
75
76 LOCATION_CIRCLE_PULSE_COLOR = Color.BLUE;
77
78 mapView = findViewById(R.id.mapView);
79
80 if (savedInstanceState != null) {
81 lastLocation = savedInstanceState.getParcelable(SAVED_STATE_LOCATION);
82 LOCATION_CIRCLE_PULSE_COLOR = savedInstanceState.getInt(SAVED_STATE_LOCATION_CIRCLE_PULSE_COLOR);
83 LOCATION_CIRCLE_PULSE_DURATION = savedInstanceState.getFloat(SAVED_STATE_LOCATION_CIRCLE_PULSE_DURATION);
84 }
85
86 pulsingCircleDurationButton = findViewById(R.id.button_location_circle_duration);
87 pulsingCircleDurationButton.setText(String.format("%sms",
88 String.valueOf(LOCATION_CIRCLE_PULSE_DURATION)));
89 pulsingCircleDurationButton.setOnClickListener(v -> {
90 if (locationComponent == null) {
91 return;
92 }
93 showDurationListDialog();
94 });
95
96 pulsingCircleColorButton = findViewById(R.id.button_location_circle_color);
97 pulsingCircleColorButton.setOnClickListener(v -> {
98 if (locationComponent == null) {
99 return;
100 }
101 showColorListDialog();
102 });
103
104 mapView.onCreate(savedInstanceState);
105
106 checkPermissions();
107 }
108
109 @SuppressLint("MissingPermission")
110 @Override
111 public void onMapReady(@NonNull NextbillionMap nextbillionMap) {
112 this.nextbillionMap = nextbillionMap;
113 nextbillionMap.animateCamera(CameraUpdateFactory.zoomBy(13));
114
115 nextbillionMap.setStyle(StyleConstants.LIGHT, style -> {
116 locationComponent = nextbillionMap.getLocationComponent();
117
118 LocationComponentOptions locationComponentOptions =
119 buildLocationComponentOptions(
120 LOCATION_CIRCLE_PULSE_COLOR,
121 LOCATION_CIRCLE_PULSE_DURATION)
122 .pulseEnabled(true)
123 .build();
124
125 LocationComponentActivationOptions locationComponentActivationOptions =
126 buildLocationComponentActivationOptions(style,locationComponentOptions);
127
128 locationComponent.activateLocationComponent(locationComponentActivationOptions);
129 locationComponent.setLocationComponentEnabled(true);
130 locationComponent.setCameraMode(CameraMode.TRACKING);
131 locationComponent.forceLocationUpdate(lastLocation);
132 });
133 }
134
135 private LocationComponentOptions.Builder buildLocationComponentOptions(int pulsingCircleColor,
136 float pulsingCircleDuration
137 ) {
138 currentPulseDuration = pulsingCircleDuration;
139 return LocationComponentOptions.builder(this)
140 .layerBelow(LAYER_BELOW_ID)
141 .pulseFadeEnabled(DEFAULT_LOCATION_CIRCLE_PULSE_FADE_MODE)
142 .pulseInterpolator(DEFAULT_LOCATION_CIRCLE_INTERPOLATOR_PULSE_MODE)
143 .pulseColor(pulsingCircleColor)
144 .pulseAlpha(DEFAULT_LOCATION_CIRCLE_PULSE_ALPHA)
145 .pulseSingleDuration(pulsingCircleDuration)
146 .pulseMaxRadius(DEFAULT_LOCATION_CIRCLE_PULSE_RADIUS);
147 }
148
149 @SuppressLint("MissingPermission")
150 private void setNewLocationComponentOptions(float newPulsingDuration,
151 int newPulsingColor) {
152 nextbillionMap.getStyle(style -> locationComponent.applyStyle(
153 buildLocationComponentOptions(
154 newPulsingColor,
155 newPulsingDuration)
156 .pulseEnabled(true)
157 .build()));
158 }
159
160 private LocationComponentActivationOptions buildLocationComponentActivationOptions(
161 @NonNull Style style,
162 @NonNull LocationComponentOptions locationComponentOptions) {
163 return LocationComponentActivationOptions
164 .builder(this, style)
165 .locationComponentOptions(locationComponentOptions)
166 .useDefaultLocationEngine(true)
167 .locationEngineRequest(new LocationEngineRequest.Builder(750)
168 .setFastestInterval(750)
169 .setPriority(LocationEngineRequest.PRIORITY_HIGH_ACCURACY)
170 .build())
171 .build();
172 }
173
174 @Override
175 public boolean onCreateOptionsMenu(Menu menu) {
176 getMenuInflater().inflate(R.menu.menu_pulsing_location_mode, menu);
177 return true;
178 }
179
180 @SuppressLint("MissingPermission")
181 @Override
182 public boolean onOptionsItemSelected(MenuItem item) {
183 if (locationComponent == null) {
184 return super.onOptionsItemSelected(item);
185 }
186
187 int id = item.getItemId();
188 if (id == R.id.action_component_disable) {
189 locationComponent.setLocationComponentEnabled(false);
190 return true;
191 } else if (id == R.id.action_component_enabled) {
192 locationComponent.setLocationComponentEnabled(true);
193 return true;
194 } else if (id == R.id.action_stop_pulsing) {
195 locationComponent.applyStyle(LocationComponentOptions.builder(
196 CustomizedLocationPulsingCircleActivity.this)
197 .pulseEnabled(false)
198 .build());
199 return true;
200 } else if (id == R.id.action_start_pulsing) {
201 locationComponent.applyStyle(buildLocationComponentOptions(
202 LOCATION_CIRCLE_PULSE_COLOR,
203 LOCATION_CIRCLE_PULSE_DURATION)
204 .pulseEnabled(true)
205 .build());
206 return true;
207 }
208 return super.onOptionsItemSelected(item);
209 }
210
211 private void checkPermissions() {
212 if (PermissionsManager.areLocationPermissionsGranted(this)) {
213 mapView.getMapAsync(this);
214 } else {
215 permissionsManager = new PermissionsManager(new PermissionsListener() {
216 @Override
217 public void onExplanationNeeded(List<String> permissionsToExplain) {
218 Toast.makeText(CustomizedLocationPulsingCircleActivity.this, "You need to accept location permissions.",
219 Toast.LENGTH_SHORT).show();
220 }
221
222 @Override
223 public void onPermissionResult(boolean granted) {
224 if (granted) {
225 mapView.getMapAsync(CustomizedLocationPulsingCircleActivity.this);
226 } else {
227 finish();
228 }
229 }
230 });
231 permissionsManager.requestLocationPermissions(this);
232 }
233 }
234
235 private void showDurationListDialog() {
236 List<String> modes = new ArrayList<>();
237 modes.add(String.format("%sms", String.valueOf(DEFAULT_LOCATION_CIRCLE_PULSE_DURATION_MS)));
238 modes.add(String.format("%sms", String.valueOf(SECOND_LOCATION_CIRCLE_PULSE_DURATION_MS)));
239 modes.add(String.format("%sms", String.valueOf(THIRD_LOCATION_CIRCLE_PULSE_DURATION_MS)));
240 ArrayAdapter<String> profileAdapter = new ArrayAdapter<>(this,
241 android.R.layout.simple_list_item_1, modes);
242 ListPopupWindow listPopup = new ListPopupWindow(this);
243 listPopup.setAdapter(profileAdapter);
244 listPopup.setAnchorView(pulsingCircleDurationButton);
245 listPopup.setOnItemClickListener((parent, itemView, position, id) -> {
246 String selectedMode = modes.get(position);
247 pulsingCircleDurationButton.setText(selectedMode);
248 if (selectedMode.contentEquals(String.format("%sms",
249 String.valueOf(DEFAULT_LOCATION_CIRCLE_PULSE_DURATION_MS)))) {
250 LOCATION_CIRCLE_PULSE_DURATION = DEFAULT_LOCATION_CIRCLE_PULSE_DURATION_MS;
251 setNewLocationComponentOptions(DEFAULT_LOCATION_CIRCLE_PULSE_DURATION_MS, LOCATION_CIRCLE_PULSE_COLOR);
252 } else if (selectedMode.contentEquals(String.format("%sms",
253 String.valueOf(SECOND_LOCATION_CIRCLE_PULSE_DURATION_MS)))) {
254 LOCATION_CIRCLE_PULSE_DURATION = SECOND_LOCATION_CIRCLE_PULSE_DURATION_MS;
255 setNewLocationComponentOptions(SECOND_LOCATION_CIRCLE_PULSE_DURATION_MS, LOCATION_CIRCLE_PULSE_COLOR);
256 } else if (selectedMode.contentEquals(String.format("%sms",
257 String.valueOf(THIRD_LOCATION_CIRCLE_PULSE_DURATION_MS)))) {
258 LOCATION_CIRCLE_PULSE_DURATION = THIRD_LOCATION_CIRCLE_PULSE_DURATION_MS;
259 setNewLocationComponentOptions(THIRD_LOCATION_CIRCLE_PULSE_DURATION_MS, LOCATION_CIRCLE_PULSE_COLOR);
260 }
261 listPopup.dismiss();
262 });
263 listPopup.show();
264 }
265
266 private void showColorListDialog() {
267 List<String> trackingTypes = new ArrayList<>();
268 trackingTypes.add("Blue");
269 trackingTypes.add("Red");
270 trackingTypes.add("Green");
271 trackingTypes.add("Gray");
272 ArrayAdapter<String> profileAdapter = new ArrayAdapter<>(this,
273 android.R.layout.simple_list_item_1, trackingTypes);
274 ListPopupWindow listPopup = new ListPopupWindow(this);
275 listPopup.setAdapter(profileAdapter);
276 listPopup.setAnchorView(pulsingCircleColorButton);
277 listPopup.setOnItemClickListener((parent, itemView, position, id) -> {
278 String selectedTrackingType = trackingTypes.get(position);
279 pulsingCircleColorButton.setText(selectedTrackingType);
280 if (selectedTrackingType.contentEquals("Blue")) {
281 LOCATION_CIRCLE_PULSE_COLOR = Color.BLUE;
282 setNewLocationComponentOptions(currentPulseDuration, Color.BLUE);
283 } else if (selectedTrackingType.contentEquals("Red")) {
284 LOCATION_CIRCLE_PULSE_COLOR = Color.RED;
285 setNewLocationComponentOptions(currentPulseDuration, Color.RED);
286 } else if (selectedTrackingType.contentEquals("Green")) {
287 LOCATION_CIRCLE_PULSE_COLOR = Color.GREEN;
288 setNewLocationComponentOptions(currentPulseDuration, Color.GREEN);
289 } else if (selectedTrackingType.contentEquals("Gray")) {
290 LOCATION_CIRCLE_PULSE_COLOR = Color.parseColor("#4a4a4a");
291 setNewLocationComponentOptions(currentPulseDuration, Color.parseColor("#4a4a4a"));
292 }
293 listPopup.dismiss();
294 });
295 listPopup.show();
296 }
297
298
299 @Override
300 public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
301 super.onRequestPermissionsResult(requestCode, permissions, grantResults);
302 permissionsManager.onRequestPermissionsResult(requestCode, permissions, grantResults);
303 }
304
305 @Override
306 protected void onStart() {
307 super.onStart();
308 mapView.onStart();
309 }
310
311 @Override
312 protected void onResume() {
313 super.onResume();
314 mapView.onResume();
315 }
316
317 @Override
318 protected void onPause() {
319 super.onPause();
320 mapView.onPause();
321 }
322
323 @Override
324 protected void onStop() {
325 super.onStop();
326 mapView.onStop();
327 }
328
329 @SuppressLint("MissingPermission")
330 @Override
331 protected void onSaveInstanceState(Bundle outState) {
332 super.onSaveInstanceState(outState);
333 mapView.onSaveInstanceState(outState);
334 if (locationComponent != null) {
335 outState.putParcelable(SAVED_STATE_LOCATION, locationComponent.getLastKnownLocation());
336 outState.putInt(SAVED_STATE_LOCATION_CIRCLE_PULSE_COLOR, LOCATION_CIRCLE_PULSE_COLOR);
337 outState.putFloat(SAVED_STATE_LOCATION_CIRCLE_PULSE_DURATION, LOCATION_CIRCLE_PULSE_DURATION);
338 }
339 }
340
341 @Override
342 protected void onDestroy() {
343 super.onDestroy();
344 mapView.onDestroy();
345 }
346
347 @Override
348 public void onLowMemory() {
349 super.onLowMemory();
350 mapView.onLowMemory();
351 }
352}
The given code is an Android activity that demonstrates how to customize the pulsing circle of the location component on a map. The activity allows the user to change the duration and color of the pulsing circle.
initMapView:
-
The onCreate method initializes the mapView by finding the view from the layout file and calling onCreate on it.
-
The onMapReady method is implemented to handle the map when it is ready for use.
-
The mapView is initialized by calling getMapAsync and passing the activity itself as the callback.
handle Location Permissions:
-
The checkPermissions method checks if location permissions are granted. If permissions are granted, it calls getMapAsync to initialize the map.
-
If permissions are not granted, it creates a PermissionsManager and requests location permissions.
-
The PermissionsListener interface is implemented to handle the result of the permission request.
track the current location automatically when the map is ready:
-
The onMapReady method is called when the map is ready.
-
It sets the map style using the setStyle method, passing the desired style and a callback for when the style is loaded.
-
The locationComponent is obtained from the nextbillionMap object, and location component options are built.
-
The location component is activated and enabled, and the camera mode is set to tracking.
-
The last known location is forced to update if available.
customize the location pulsing style:
-
The buildLocationComponentOptions method is used to build the options for the location component.
-
It takes the pulsing circle color and duration as parameters and sets the desired properties for the location component options.
-
The setNewLocationComponentOptions method is called to update the location component options with new pulsing duration and color.
-
The following properties are adjusted to customize the example's pulsing circle UI:
-
DEFAULT_LOCATION_CIRCLE_PULSE_DURATION_MS: The default duration in milliseconds for the pulsing circle animation.
-
SECOND_LOCATION_CIRCLE_PULSE_DURATION_MS: The duration in milliseconds for the second pulsing circle animation.
-
THIRD_LOCATION_CIRCLE_PULSE_DURATION_MS: The duration in milliseconds for the third pulsing circle animation.
-
DEFAULT_LOCATION_CIRCLE_PULSE_RADIUS: The default radius of the pulsing circle.
-
DEFAULT_LOCATION_CIRCLE_PULSE_ALPHA: The default alpha value of the pulsing circle.
-
DEFAULT_LOCATION_CIRCLE_INTERPOLATOR_PULSE_MODE: The default interpolator for the pulsing circle animation.
-
DEFAULT_LOCATION_CIRCLE_PULSE_FADE_MODE: The default fade mode for the pulsing circle animation.
-
The color, duration and other properties of the pulsing circle can be changed by interacting with buttons and dialog menus in the activity. The onOptionsItemSelected method handles the button clicks and updates the location component accordingly.