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

Custom Location Pulsing Symbol

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

1
package ai.nextbillion;
2
3
import android.annotation.SuppressLint;
4
import android.graphics.Color;
5
import android.location.Location;
6
import android.os.Bundle;
7
import android.view.Menu;
8
import android.view.MenuItem;
9
import android.view.animation.DecelerateInterpolator;
10
import android.view.animation.Interpolator;
11
import android.widget.ArrayAdapter;
12
import android.widget.Button;
13
import android.widget.Toast;
14
15
import java.util.ArrayList;
16
import java.util.List;
17
18
import ai.nextbillion.maps.camera.CameraUpdateFactory;
19
import ai.nextbillion.maps.core.MapView;
20
import ai.nextbillion.maps.core.NextbillionMap;
21
import ai.nextbillion.maps.core.OnMapReadyCallback;
22
import ai.nextbillion.maps.core.Style;
23
import ai.nextbillion.maps.location.LocationComponent;
24
import ai.nextbillion.maps.location.LocationComponentActivationOptions;
25
import ai.nextbillion.maps.location.LocationComponentOptions;
26
import ai.nextbillion.maps.location.engine.LocationEngineRequest;
27
import ai.nextbillion.maps.location.modes.CameraMode;
28
import ai.nextbillion.maps.location.permissions.PermissionsListener;
29
import ai.nextbillion.maps.location.permissions.PermissionsManager;
30
import androidx.annotation.NonNull;
31
import androidx.appcompat.app.AppCompatActivity;
32
import androidx.appcompat.widget.ListPopupWindow;
33
34
35
/**
36
* This activity shows how to customize the LocationComponent's pulsing circle.
37
*/
38
public 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.

© 2024 NextBillion.ai all rights reserved.