Adding pulsing circle to LocationComponent options (#172)
* [location] added pulsing locationcomponent circle * adjusting style switching to roll through all styles * refactored methods and variables for prefix continuity * refactoring to after making start and stop private methods * refactoring to stop pulsing when LocationComponent is disabled and color/speed is then selected * refactoring to swap interpolator string paramete for interpolator interface parameter * LocationLayerControllerTest adjustments * removing unneeded test * more rebase adjustments * [location] added pulsing locationcomponent circle * refactoring to swap interpolator string paramete for interpolator interface parameter * consolidating pulse ui update methods * gl-native vendor bump * refactor to check for activation with locationcomponent * Update MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationComponentOptions.java Co-Authored-By: Łukasz Paczos <lukasz.paczos@mapbox.com> * Update MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationComponent.java Co-Authored-By: Łukasz Paczos <lukasz.paczos@mapbox.com> * Update MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationComponentOptions.java Co-Authored-By: Łukasz Paczos <lukasz.paczos@mapbox.com> * Update MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationComponentOptions.java Co-Authored-By: Łukasz Paczos <lukasz.paczos@mapbox.com> * Update MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/location/LocationComponentOptions.java Co-Authored-By: Łukasz Paczos <lukasz.paczos@mapbox.com> * Added incompatibility docs * Added pulse alpha range explanation * Do not wrap pulsing circle animator with an animator set to avoid leaks Co-authored-by: Łukasz Paczos <lukasz.paczos@mapbox.com>
This commit is contained in:
@@ -2,6 +2,7 @@ package com.mapbox.mapboxsdk.location
|
||||
|
||||
import android.Manifest
|
||||
import android.content.Context
|
||||
import android.graphics.Color
|
||||
import android.location.Location
|
||||
import androidx.test.annotation.UiThreadTest
|
||||
import androidx.test.espresso.Espresso.onView
|
||||
@@ -123,11 +124,49 @@ class LocationLayerControllerTest : EspressoTest() {
|
||||
assertThat(mapboxMap.isLayerVisible(SHADOW_LAYER), `is`(true))
|
||||
assertThat(mapboxMap.isLayerVisible(ACCURACY_LAYER), `is`(true))
|
||||
assertThat(mapboxMap.isLayerVisible(BEARING_LAYER), `is`(false))
|
||||
assertThat(mapboxMap.isLayerVisible(PULSING_CIRCLE_LAYER), `is`(false))
|
||||
}
|
||||
}
|
||||
executeComponentTest(componentAction)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onMapChange_locationComponentPulsingCircleLayerGetsRedrawn() {
|
||||
val componentAction = object : LocationComponentAction.OnPerformLocationComponentAction {
|
||||
override fun onLocationComponentAction(
|
||||
component: LocationComponent,
|
||||
mapboxMap: MapboxMap,
|
||||
style: Style,
|
||||
uiController: UiController,
|
||||
context: Context
|
||||
) {
|
||||
component.activateLocationComponent(
|
||||
LocationComponentActivationOptions
|
||||
.builder(context, style)
|
||||
.locationComponentOptions(LocationComponentOptions.builder(context)
|
||||
.pulseEnabled(true)
|
||||
.build())
|
||||
.useDefaultLocationEngine(false)
|
||||
.build()
|
||||
)
|
||||
component.isLocationComponentEnabled = true
|
||||
component.renderMode = RenderMode.NORMAL
|
||||
component.forceLocationUpdate(location)
|
||||
styleChangeIdlingResource.waitForStyle(mapboxMap, Style.LIGHT)
|
||||
TestingAsyncUtils.waitForLayer(uiController, mapView)
|
||||
|
||||
assertThat(component.renderMode, `is`(equalTo(RenderMode.NORMAL)))
|
||||
|
||||
// Check that the Source has been re-added to the new map style
|
||||
val source: GeoJsonSource? = mapboxMap.style!!.getSourceAs(LOCATION_SOURCE)
|
||||
assertThat(source, notNullValue())
|
||||
|
||||
// Check that the pulsing circle layer visibilities is set to visible
|
||||
assertThat(mapboxMap.isLayerVisible(PULSING_CIRCLE_LAYER), `is`(true))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun renderModeCompass_bearingLayersDoGetAdded() {
|
||||
val componentAction = object : LocationComponentAction.OnPerformLocationComponentAction {
|
||||
@@ -159,6 +198,137 @@ class LocationLayerControllerTest : EspressoTest() {
|
||||
executeComponentTest(componentAction)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun pulsingCircle_enableLocationComponent_pulsingLayerVisibility() {
|
||||
val componentAction = object : LocationComponentAction.OnPerformLocationComponentAction {
|
||||
override fun onLocationComponentAction(
|
||||
component: LocationComponent,
|
||||
mapboxMap: MapboxMap,
|
||||
style: Style,
|
||||
uiController: UiController,
|
||||
context: Context
|
||||
) {
|
||||
component.activateLocationComponent(
|
||||
LocationComponentActivationOptions
|
||||
.builder(context, style)
|
||||
.useDefaultLocationEngine(false)
|
||||
.build()
|
||||
)
|
||||
component.isLocationComponentEnabled = true
|
||||
component.forceLocationUpdate(location)
|
||||
TestingAsyncUtils.waitForLayer(uiController, mapView)
|
||||
|
||||
component.applyStyle(LocationComponentOptions.builder(context)
|
||||
.pulseEnabled(true).build())
|
||||
|
||||
assertThat(mapboxMap.isLayerVisible(PULSING_CIRCLE_LAYER), `is`(true))
|
||||
}
|
||||
}
|
||||
executeComponentTest(componentAction)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun pulsingCircle_disableLocationComponent_pulsingLayerVisibility() {
|
||||
val componentAction = object : LocationComponentAction.OnPerformLocationComponentAction {
|
||||
override fun onLocationComponentAction(
|
||||
component: LocationComponent,
|
||||
mapboxMap: MapboxMap,
|
||||
style: Style,
|
||||
uiController: UiController,
|
||||
context: Context
|
||||
) {
|
||||
component.activateLocationComponent(
|
||||
LocationComponentActivationOptions
|
||||
.builder(context, style)
|
||||
.useDefaultLocationEngine(false)
|
||||
.build()
|
||||
)
|
||||
component.isLocationComponentEnabled = false
|
||||
component.forceLocationUpdate(location)
|
||||
TestingAsyncUtils.waitForLayer(uiController, mapView)
|
||||
|
||||
assertThat(mapboxMap.isLayerVisible(PULSING_CIRCLE_LAYER), `is`(false))
|
||||
}
|
||||
}
|
||||
executeComponentTest(componentAction)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun pulsingCircle_changeColorCheck() {
|
||||
val componentAction = object : LocationComponentAction.OnPerformLocationComponentAction {
|
||||
override fun onLocationComponentAction(
|
||||
component: LocationComponent,
|
||||
mapboxMap: MapboxMap,
|
||||
style: Style,
|
||||
uiController: UiController,
|
||||
context: Context
|
||||
) {
|
||||
component.activateLocationComponent(
|
||||
LocationComponentActivationOptions
|
||||
.builder(context, style)
|
||||
.useDefaultLocationEngine(false)
|
||||
.build()
|
||||
)
|
||||
component.isLocationComponentEnabled = true
|
||||
component.forceLocationUpdate(location)
|
||||
TestingAsyncUtils.waitForLayer(uiController, mapView)
|
||||
|
||||
component.applyStyle(LocationComponentOptions.builder(context)
|
||||
.pulseEnabled(true)
|
||||
.pulseColor(Color.RED)
|
||||
.build())
|
||||
|
||||
component.applyStyle(LocationComponentOptions.builder(context)
|
||||
.pulseEnabled(true)
|
||||
.pulseColor(Color.BLUE)
|
||||
.build())
|
||||
|
||||
mapboxMap.style.apply {
|
||||
assertThat(component.locationComponentOptions.pulseColor(), `is`(Color.BLUE))
|
||||
}
|
||||
}
|
||||
}
|
||||
executeComponentTest(componentAction)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun pulsingCircle_changeSpeedCheck() {
|
||||
val componentAction = object : LocationComponentAction.OnPerformLocationComponentAction {
|
||||
override fun onLocationComponentAction(
|
||||
component: LocationComponent,
|
||||
mapboxMap: MapboxMap,
|
||||
style: Style,
|
||||
uiController: UiController,
|
||||
context: Context
|
||||
) {
|
||||
component.activateLocationComponent(
|
||||
LocationComponentActivationOptions
|
||||
.builder(context, style)
|
||||
.useDefaultLocationEngine(false)
|
||||
.build()
|
||||
)
|
||||
component.isLocationComponentEnabled = true
|
||||
component.forceLocationUpdate(location)
|
||||
TestingAsyncUtils.waitForLayer(uiController, mapView)
|
||||
|
||||
component.applyStyle(LocationComponentOptions.builder(context)
|
||||
.pulseEnabled(true)
|
||||
.pulseSingleDuration(8000f)
|
||||
.build())
|
||||
|
||||
component.applyStyle(LocationComponentOptions.builder(context)
|
||||
.pulseEnabled(true)
|
||||
.pulseSingleDuration(400f)
|
||||
.build())
|
||||
|
||||
mapboxMap.style.apply {
|
||||
assertThat(component.locationComponentOptions.pulseSingleDuration(), `is`(400f))
|
||||
}
|
||||
}
|
||||
}
|
||||
executeComponentTest(componentAction)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun renderModeGps_navigationLayersDoGetAdded() {
|
||||
val componentAction = object : LocationComponentAction.OnPerformLocationComponentAction {
|
||||
|
||||
@@ -33,7 +33,7 @@ class StyleChangeIdlingResource : IdlingResource {
|
||||
|
||||
fun waitForStyle(mapboxMap: MapboxMap, styleUrl: String) {
|
||||
isIdle = false
|
||||
mapboxMap.setStyle(Style.Builder().fromUrl(styleUrl)) {
|
||||
mapboxMap.setStyle(Style.Builder().fromUri(styleUrl)) {
|
||||
setIdle()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1015,6 +1015,28 @@
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value=".activity.FeatureOverviewActivity" />
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".activity.location.BasicLocationPulsingCircleActivity"
|
||||
android:description="@string/description_location_basic_pulsing_circle"
|
||||
android:label="@string/activity_basic_location_pulsing_circle">
|
||||
<meta-data
|
||||
android:name="@string/category"
|
||||
android:value="@string/category_location" />
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value=".activity.FeatureOverviewActivity" />
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".activity.location.CustomizedLocationPulsingCircleActivity"
|
||||
android:description="@string/description_location_customized_pulsing_circle"
|
||||
android:label="@string/activity_customized_location_pulsing_circle">
|
||||
<meta-data
|
||||
android:name="@string/category"
|
||||
android:value="@string/category_location" />
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value=".activity.FeatureOverviewActivity" />
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".activity.location.LocationFragmentActivity"
|
||||
android:description="@string/description_location_fragment"
|
||||
|
||||
@@ -0,0 +1,215 @@
|
||||
package com.mapbox.mapboxsdk.testapp.activity.location;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.location.Location;
|
||||
import android.os.Bundle;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.mapbox.android.core.location.LocationEngineRequest;
|
||||
import com.mapbox.android.core.permissions.PermissionsListener;
|
||||
import com.mapbox.android.core.permissions.PermissionsManager;
|
||||
import com.mapbox.mapboxsdk.location.LocationComponent;
|
||||
import com.mapbox.mapboxsdk.location.LocationComponentActivationOptions;
|
||||
import com.mapbox.mapboxsdk.location.LocationComponentOptions;
|
||||
import com.mapbox.mapboxsdk.location.modes.CameraMode;
|
||||
import com.mapbox.mapboxsdk.maps.MapView;
|
||||
import com.mapbox.mapboxsdk.maps.MapboxMap;
|
||||
import com.mapbox.mapboxsdk.maps.OnMapReadyCallback;
|
||||
import com.mapbox.mapboxsdk.maps.Style;
|
||||
import com.mapbox.mapboxsdk.testapp.R;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import static com.mapbox.mapboxsdk.testapp.activity.location.Utils.getNextStyle;
|
||||
|
||||
/**
|
||||
* This activity shows a basic usage of the LocationComponent's pulsing circle. There's no
|
||||
* customization of the pulsing circle's color, radius, speed, etc.
|
||||
*/
|
||||
public class BasicLocationPulsingCircleActivity extends AppCompatActivity implements OnMapReadyCallback {
|
||||
|
||||
private static final String SAVED_STATE_LOCATION = "saved_state_location";
|
||||
private static final String TAG = "Mbgl-BasicLocationPulsingCircleActivity";
|
||||
|
||||
private Location lastLocation;
|
||||
private MapView mapView;
|
||||
private PermissionsManager permissionsManager;
|
||||
private LocationComponent locationComponent;
|
||||
private MapboxMap mapboxMap;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_location_layer_basic_pulsing_circle);
|
||||
|
||||
mapView = findViewById(R.id.mapView);
|
||||
|
||||
if (savedInstanceState != null) {
|
||||
lastLocation = savedInstanceState.getParcelable(SAVED_STATE_LOCATION);
|
||||
}
|
||||
mapView.onCreate(savedInstanceState);
|
||||
|
||||
checkPermissions();
|
||||
}
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
@Override
|
||||
public void onMapReady(@NonNull MapboxMap mapboxMap) {
|
||||
this.mapboxMap = mapboxMap;
|
||||
|
||||
mapboxMap.setStyle(Style.MAPBOX_STREETS, style -> {
|
||||
locationComponent = mapboxMap.getLocationComponent();
|
||||
|
||||
LocationComponentOptions locationComponentOptions =
|
||||
LocationComponentOptions.builder(BasicLocationPulsingCircleActivity.this)
|
||||
.pulseEnabled(true)
|
||||
.build();
|
||||
|
||||
LocationComponentActivationOptions locationComponentActivationOptions =
|
||||
buildLocationComponentActivationOptions(style,locationComponentOptions);
|
||||
|
||||
locationComponent.activateLocationComponent(locationComponentActivationOptions);
|
||||
locationComponent.setLocationComponentEnabled(true);
|
||||
locationComponent.setCameraMode(CameraMode.TRACKING);
|
||||
locationComponent.forceLocationUpdate(lastLocation);
|
||||
});
|
||||
}
|
||||
|
||||
private LocationComponentActivationOptions buildLocationComponentActivationOptions(@NonNull Style style,
|
||||
@NonNull LocationComponentOptions
|
||||
locationComponentOptions) {
|
||||
return LocationComponentActivationOptions
|
||||
.builder(this, style)
|
||||
.locationComponentOptions(locationComponentOptions)
|
||||
.useDefaultLocationEngine(true)
|
||||
.locationEngineRequest(new LocationEngineRequest.Builder(750)
|
||||
.setFastestInterval(750)
|
||||
.setPriority(LocationEngineRequest.PRIORITY_HIGH_ACCURACY)
|
||||
.build())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.menu_pulsing_location_mode, menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (locationComponent == null) {
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
int id = item.getItemId();
|
||||
if (id == R.id.action_map_style_change) {
|
||||
loadNewStyle();
|
||||
return true;
|
||||
} else if (id == R.id.action_component_disable) {
|
||||
locationComponent.setLocationComponentEnabled(false);
|
||||
return true;
|
||||
} else if (id == R.id.action_component_enabled) {
|
||||
locationComponent.setLocationComponentEnabled(true);
|
||||
return true;
|
||||
} else if (id == R.id.action_stop_pulsing) {
|
||||
locationComponent.applyStyle(LocationComponentOptions.builder(
|
||||
BasicLocationPulsingCircleActivity.this)
|
||||
.pulseEnabled(false)
|
||||
.build());
|
||||
return true;
|
||||
} else if (id == R.id.action_start_pulsing) {
|
||||
locationComponent.applyStyle(LocationComponentOptions.builder(
|
||||
BasicLocationPulsingCircleActivity.this)
|
||||
.pulseEnabled(true)
|
||||
.build());
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
private void loadNewStyle() {
|
||||
mapboxMap.setStyle(new Style.Builder().fromUri(getNextStyle()));
|
||||
}
|
||||
|
||||
private void checkPermissions() {
|
||||
if (PermissionsManager.areLocationPermissionsGranted(this)) {
|
||||
mapView.getMapAsync(this);
|
||||
} else {
|
||||
permissionsManager = new PermissionsManager(new PermissionsListener() {
|
||||
@Override
|
||||
public void onExplanationNeeded(List<String> permissionsToExplain) {
|
||||
Toast.makeText(BasicLocationPulsingCircleActivity.this, "You need to accept location permissions.",
|
||||
Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPermissionResult(boolean granted) {
|
||||
if (granted) {
|
||||
mapView.getMapAsync(BasicLocationPulsingCircleActivity.this);
|
||||
} else {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
});
|
||||
permissionsManager.requestLocationPermissions(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
permissionsManager.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
mapView.onStart();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
mapView.onResume();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
mapView.onPause();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
mapView.onStop();
|
||||
}
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
@Override
|
||||
protected void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
mapView.onSaveInstanceState(outState);
|
||||
if (locationComponent != null) {
|
||||
outState.putParcelable(SAVED_STATE_LOCATION, locationComponent.getLastKnownLocation());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
mapView.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLowMemory() {
|
||||
super.onLowMemory();
|
||||
mapView.onLowMemory();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,360 @@
|
||||
package com.mapbox.mapboxsdk.testapp.activity.location;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.graphics.Color;
|
||||
import android.location.Location;
|
||||
import android.os.Bundle;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.animation.DecelerateInterpolator;
|
||||
import android.view.animation.Interpolator;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Button;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.mapbox.android.core.location.LocationEngineRequest;
|
||||
import com.mapbox.android.core.permissions.PermissionsListener;
|
||||
import com.mapbox.android.core.permissions.PermissionsManager;
|
||||
import com.mapbox.mapboxsdk.location.LocationComponent;
|
||||
import com.mapbox.mapboxsdk.location.LocationComponentActivationOptions;
|
||||
import com.mapbox.mapboxsdk.location.LocationComponentOptions;
|
||||
import com.mapbox.mapboxsdk.location.modes.CameraMode;
|
||||
import com.mapbox.mapboxsdk.maps.MapView;
|
||||
import com.mapbox.mapboxsdk.maps.MapboxMap;
|
||||
import com.mapbox.mapboxsdk.maps.OnMapReadyCallback;
|
||||
import com.mapbox.mapboxsdk.maps.Style;
|
||||
import com.mapbox.mapboxsdk.testapp.R;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.ListPopupWindow;
|
||||
|
||||
import static com.mapbox.mapboxsdk.testapp.activity.location.Utils.getNextStyle;
|
||||
|
||||
/**
|
||||
* This activity shows how to customize the LocationComponent's pulsing circle.
|
||||
*/
|
||||
public class CustomizedLocationPulsingCircleActivity extends AppCompatActivity implements OnMapReadyCallback {
|
||||
|
||||
//region
|
||||
|
||||
// Adjust these variables to customize the example's pulsing circle UI
|
||||
private static final float DEFAULT_LOCATION_CIRCLE_PULSE_DURATION_MS = 2300;
|
||||
private static final float SECOND_LOCATION_CIRCLE_PULSE_DURATION_MS = 800;
|
||||
private static final float THIRD_LOCATION_CIRCLE_PULSE_DURATION_MS = 8000;
|
||||
private static final float DEFAULT_LOCATION_CIRCLE_PULSE_RADIUS = 35;
|
||||
private static final float DEFAULT_LOCATION_CIRCLE_PULSE_ALPHA = .55f;
|
||||
private static final Interpolator DEFAULT_LOCATION_CIRCLE_INTERPOLATOR_PULSE_MODE
|
||||
= new DecelerateInterpolator();
|
||||
private static final boolean DEFAULT_LOCATION_CIRCLE_PULSE_FADE_MODE = true;
|
||||
//endregion
|
||||
|
||||
//region
|
||||
private static int LOCATION_CIRCLE_PULSE_COLOR;
|
||||
private static float LOCATION_CIRCLE_PULSE_DURATION = DEFAULT_LOCATION_CIRCLE_PULSE_DURATION_MS;
|
||||
private static final String SAVED_STATE_LOCATION = "saved_state_location";
|
||||
private static final String SAVED_STATE_LOCATION_CIRCLE_PULSE_COLOR = "saved_state_color";
|
||||
private static final String SAVED_STATE_LOCATION_CIRCLE_PULSE_DURATION = "saved_state_duration";
|
||||
private static final String LAYER_BELOW_ID = "waterway-label";
|
||||
|
||||
private Location lastLocation;
|
||||
private MapView mapView;
|
||||
private Button pulsingCircleDurationButton;
|
||||
private Button pulsingCircleColorButton;
|
||||
private PermissionsManager permissionsManager;
|
||||
private LocationComponent locationComponent;
|
||||
private MapboxMap mapboxMap;
|
||||
private float currentPulseDuration;
|
||||
//endregion
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_location_layer_customized_pulsing_circle);
|
||||
|
||||
LOCATION_CIRCLE_PULSE_COLOR = Color.BLUE;
|
||||
|
||||
mapView = findViewById(R.id.mapView);
|
||||
|
||||
if (savedInstanceState != null) {
|
||||
lastLocation = savedInstanceState.getParcelable(SAVED_STATE_LOCATION);
|
||||
LOCATION_CIRCLE_PULSE_COLOR = savedInstanceState.getInt(SAVED_STATE_LOCATION_CIRCLE_PULSE_COLOR);
|
||||
LOCATION_CIRCLE_PULSE_DURATION = savedInstanceState.getFloat(SAVED_STATE_LOCATION_CIRCLE_PULSE_DURATION);
|
||||
}
|
||||
|
||||
pulsingCircleDurationButton = findViewById(R.id.button_location_circle_duration);
|
||||
pulsingCircleDurationButton.setText(String.format("%sms",
|
||||
String.valueOf(LOCATION_CIRCLE_PULSE_DURATION)));
|
||||
pulsingCircleDurationButton.setOnClickListener(v -> {
|
||||
if (locationComponent == null) {
|
||||
return;
|
||||
}
|
||||
showDurationListDialog();
|
||||
});
|
||||
|
||||
pulsingCircleColorButton = findViewById(R.id.button_location_circle_color);
|
||||
pulsingCircleColorButton.setOnClickListener(v -> {
|
||||
if (locationComponent == null) {
|
||||
return;
|
||||
}
|
||||
showColorListDialog();
|
||||
});
|
||||
|
||||
mapView.onCreate(savedInstanceState);
|
||||
|
||||
checkPermissions();
|
||||
}
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
@Override
|
||||
public void onMapReady(@NonNull MapboxMap mapboxMap) {
|
||||
this.mapboxMap = mapboxMap;
|
||||
|
||||
mapboxMap.setStyle(Style.MAPBOX_STREETS, style -> {
|
||||
locationComponent = mapboxMap.getLocationComponent();
|
||||
|
||||
LocationComponentOptions locationComponentOptions =
|
||||
buildLocationComponentOptions(
|
||||
LOCATION_CIRCLE_PULSE_COLOR,
|
||||
LOCATION_CIRCLE_PULSE_DURATION)
|
||||
.pulseEnabled(true)
|
||||
.build();
|
||||
|
||||
LocationComponentActivationOptions locationComponentActivationOptions =
|
||||
buildLocationComponentActivationOptions(style,locationComponentOptions);
|
||||
|
||||
locationComponent.activateLocationComponent(locationComponentActivationOptions);
|
||||
locationComponent.setLocationComponentEnabled(true);
|
||||
locationComponent.setCameraMode(CameraMode.TRACKING);
|
||||
locationComponent.forceLocationUpdate(lastLocation);
|
||||
});
|
||||
}
|
||||
|
||||
private LocationComponentOptions.Builder buildLocationComponentOptions(int pulsingCircleColor,
|
||||
float pulsingCircleDuration
|
||||
) {
|
||||
currentPulseDuration = pulsingCircleDuration;
|
||||
return LocationComponentOptions.builder(this)
|
||||
.layerBelow(LAYER_BELOW_ID)
|
||||
.pulseFadeEnabled(DEFAULT_LOCATION_CIRCLE_PULSE_FADE_MODE)
|
||||
.pulseInterpolator(DEFAULT_LOCATION_CIRCLE_INTERPOLATOR_PULSE_MODE)
|
||||
.pulseColor(pulsingCircleColor)
|
||||
.pulseAlpha(DEFAULT_LOCATION_CIRCLE_PULSE_ALPHA)
|
||||
.pulseSingleDuration(pulsingCircleDuration)
|
||||
.pulseMaxRadius(DEFAULT_LOCATION_CIRCLE_PULSE_RADIUS);
|
||||
}
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
private void setNewLocationComponentOptions(float newPulsingDuration,
|
||||
int newPulsingColor) {
|
||||
mapboxMap.getStyle(style -> locationComponent.applyStyle(
|
||||
buildLocationComponentOptions(
|
||||
newPulsingColor,
|
||||
newPulsingDuration)
|
||||
.pulseEnabled(true)
|
||||
.build()));
|
||||
}
|
||||
|
||||
private LocationComponentActivationOptions buildLocationComponentActivationOptions(
|
||||
@NonNull Style style,
|
||||
@NonNull LocationComponentOptions locationComponentOptions) {
|
||||
return LocationComponentActivationOptions
|
||||
.builder(this, style)
|
||||
.locationComponentOptions(locationComponentOptions)
|
||||
.useDefaultLocationEngine(true)
|
||||
.locationEngineRequest(new LocationEngineRequest.Builder(750)
|
||||
.setFastestInterval(750)
|
||||
.setPriority(LocationEngineRequest.PRIORITY_HIGH_ACCURACY)
|
||||
.build())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.menu_pulsing_location_mode, menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (locationComponent == null) {
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
int id = item.getItemId();
|
||||
if (id == R.id.action_map_style_change) {
|
||||
loadNewStyle();
|
||||
return true;
|
||||
} else if (id == R.id.action_component_disable) {
|
||||
locationComponent.setLocationComponentEnabled(false);
|
||||
return true;
|
||||
} else if (id == R.id.action_component_enabled) {
|
||||
locationComponent.setLocationComponentEnabled(true);
|
||||
return true;
|
||||
} else if (id == R.id.action_stop_pulsing) {
|
||||
locationComponent.applyStyle(LocationComponentOptions.builder(
|
||||
CustomizedLocationPulsingCircleActivity.this)
|
||||
.pulseEnabled(false)
|
||||
.build());
|
||||
return true;
|
||||
} else if (id == R.id.action_start_pulsing) {
|
||||
locationComponent.applyStyle(buildLocationComponentOptions(
|
||||
LOCATION_CIRCLE_PULSE_COLOR,
|
||||
LOCATION_CIRCLE_PULSE_DURATION)
|
||||
.pulseEnabled(true)
|
||||
.build());
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
private void loadNewStyle() {
|
||||
mapboxMap.setStyle(new Style.Builder().fromUri(getNextStyle()));
|
||||
}
|
||||
|
||||
private void checkPermissions() {
|
||||
if (PermissionsManager.areLocationPermissionsGranted(this)) {
|
||||
mapView.getMapAsync(this);
|
||||
} else {
|
||||
permissionsManager = new PermissionsManager(new PermissionsListener() {
|
||||
@Override
|
||||
public void onExplanationNeeded(List<String> permissionsToExplain) {
|
||||
Toast.makeText(CustomizedLocationPulsingCircleActivity.this, "You need to accept location permissions.",
|
||||
Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPermissionResult(boolean granted) {
|
||||
if (granted) {
|
||||
mapView.getMapAsync(CustomizedLocationPulsingCircleActivity.this);
|
||||
} else {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
});
|
||||
permissionsManager.requestLocationPermissions(this);
|
||||
}
|
||||
}
|
||||
|
||||
private void showDurationListDialog() {
|
||||
List<String> modes = new ArrayList<>();
|
||||
modes.add(String.format("%sms", String.valueOf(DEFAULT_LOCATION_CIRCLE_PULSE_DURATION_MS)));
|
||||
modes.add(String.format("%sms", String.valueOf(SECOND_LOCATION_CIRCLE_PULSE_DURATION_MS)));
|
||||
modes.add(String.format("%sms", String.valueOf(THIRD_LOCATION_CIRCLE_PULSE_DURATION_MS)));
|
||||
ArrayAdapter<String> profileAdapter = new ArrayAdapter<>(this,
|
||||
android.R.layout.simple_list_item_1, modes);
|
||||
ListPopupWindow listPopup = new ListPopupWindow(this);
|
||||
listPopup.setAdapter(profileAdapter);
|
||||
listPopup.setAnchorView(pulsingCircleDurationButton);
|
||||
listPopup.setOnItemClickListener((parent, itemView, position, id) -> {
|
||||
String selectedMode = modes.get(position);
|
||||
pulsingCircleDurationButton.setText(selectedMode);
|
||||
if (selectedMode.contentEquals(String.format("%sms",
|
||||
String.valueOf(DEFAULT_LOCATION_CIRCLE_PULSE_DURATION_MS)))) {
|
||||
LOCATION_CIRCLE_PULSE_DURATION = DEFAULT_LOCATION_CIRCLE_PULSE_DURATION_MS;
|
||||
setNewLocationComponentOptions(DEFAULT_LOCATION_CIRCLE_PULSE_DURATION_MS, LOCATION_CIRCLE_PULSE_COLOR);
|
||||
} else if (selectedMode.contentEquals(String.format("%sms",
|
||||
String.valueOf(SECOND_LOCATION_CIRCLE_PULSE_DURATION_MS)))) {
|
||||
LOCATION_CIRCLE_PULSE_DURATION = SECOND_LOCATION_CIRCLE_PULSE_DURATION_MS;
|
||||
setNewLocationComponentOptions(SECOND_LOCATION_CIRCLE_PULSE_DURATION_MS, LOCATION_CIRCLE_PULSE_COLOR);
|
||||
} else if (selectedMode.contentEquals(String.format("%sms",
|
||||
String.valueOf(THIRD_LOCATION_CIRCLE_PULSE_DURATION_MS)))) {
|
||||
LOCATION_CIRCLE_PULSE_DURATION = THIRD_LOCATION_CIRCLE_PULSE_DURATION_MS;
|
||||
setNewLocationComponentOptions(THIRD_LOCATION_CIRCLE_PULSE_DURATION_MS, LOCATION_CIRCLE_PULSE_COLOR);
|
||||
}
|
||||
listPopup.dismiss();
|
||||
});
|
||||
listPopup.show();
|
||||
}
|
||||
|
||||
private void showColorListDialog() {
|
||||
List<String> trackingTypes = new ArrayList<>();
|
||||
trackingTypes.add("Blue");
|
||||
trackingTypes.add("Red");
|
||||
trackingTypes.add("Green");
|
||||
trackingTypes.add("Gray");
|
||||
ArrayAdapter<String> profileAdapter = new ArrayAdapter<>(this,
|
||||
android.R.layout.simple_list_item_1, trackingTypes);
|
||||
ListPopupWindow listPopup = new ListPopupWindow(this);
|
||||
listPopup.setAdapter(profileAdapter);
|
||||
listPopup.setAnchorView(pulsingCircleColorButton);
|
||||
listPopup.setOnItemClickListener((parent, itemView, position, id) -> {
|
||||
String selectedTrackingType = trackingTypes.get(position);
|
||||
pulsingCircleColorButton.setText(selectedTrackingType);
|
||||
if (selectedTrackingType.contentEquals("Blue")) {
|
||||
LOCATION_CIRCLE_PULSE_COLOR = Color.BLUE;
|
||||
setNewLocationComponentOptions(currentPulseDuration, Color.BLUE);
|
||||
} else if (selectedTrackingType.contentEquals("Red")) {
|
||||
LOCATION_CIRCLE_PULSE_COLOR = Color.RED;
|
||||
setNewLocationComponentOptions(currentPulseDuration, Color.RED);
|
||||
} else if (selectedTrackingType.contentEquals("Green")) {
|
||||
LOCATION_CIRCLE_PULSE_COLOR = Color.GREEN;
|
||||
setNewLocationComponentOptions(currentPulseDuration, Color.GREEN);
|
||||
} else if (selectedTrackingType.contentEquals("Gray")) {
|
||||
LOCATION_CIRCLE_PULSE_COLOR = Color.parseColor("#4a4a4a");
|
||||
setNewLocationComponentOptions(currentPulseDuration, Color.parseColor("#4a4a4a"));
|
||||
}
|
||||
listPopup.dismiss();
|
||||
});
|
||||
listPopup.show();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
permissionsManager.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
mapView.onStart();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
mapView.onResume();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
mapView.onPause();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
mapView.onStop();
|
||||
}
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
@Override
|
||||
protected void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
mapView.onSaveInstanceState(outState);
|
||||
if (locationComponent != null) {
|
||||
outState.putParcelable(SAVED_STATE_LOCATION, locationComponent.getLastKnownLocation());
|
||||
outState.putInt(SAVED_STATE_LOCATION_CIRCLE_PULSE_COLOR, LOCATION_CIRCLE_PULSE_COLOR);
|
||||
outState.putFloat(SAVED_STATE_LOCATION_CIRCLE_PULSE_DURATION, LOCATION_CIRCLE_PULSE_DURATION);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
mapView.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLowMemory() {
|
||||
super.onLowMemory();
|
||||
mapView.onLowMemory();
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ import java.util.Random;
|
||||
import timber.log.Timber;
|
||||
|
||||
/**
|
||||
* Useful utilities used throughout the testapp.
|
||||
* Useful utilities used throughout the test app.
|
||||
*/
|
||||
public class Utils {
|
||||
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<com.mapbox.mapboxsdk.maps.MapView
|
||||
android:id="@+id/mapView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:mapbox_uiAttribution="false"/>
|
||||
|
||||
</FrameLayout>
|
||||
@@ -0,0 +1,78 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<com.mapbox.mapboxsdk.maps.MapView
|
||||
android:id="@+id/mapView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginBottom="0dp"
|
||||
app:layout_constraintBottom_toTopOf="@+id/linearLayout"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:mapbox_uiAttribution="false" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/linearLayout"
|
||||
style="?android:attr/buttonBarStyle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/primary"
|
||||
android:orientation="horizontal"
|
||||
android:weightSum="4"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
tools:layout_constraintBottom_creator="1"
|
||||
tools:layout_constraintLeft_creator="1"
|
||||
tools:layout_constraintRight_creator="1">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_frequency"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight=".75"
|
||||
android:gravity="center"
|
||||
android:text="Duration:"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/button_location_circle_duration"
|
||||
style="?android:attr/buttonBarButtonStyle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1.25"
|
||||
android:gravity="center"
|
||||
tools:text="400ms"
|
||||
android:textColor="@android:color/white" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_color"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight=".85"
|
||||
android:gravity="center"
|
||||
android:text="Color:"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/button_location_circle_color"
|
||||
style="?android:attr/buttonBarButtonStyle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1.15"
|
||||
android:gravity="center"
|
||||
android:text="None"
|
||||
android:textColor="@android:color/white" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item android:id="@+id/action_map_style_change"
|
||||
android:title="Toggle custom Map style"
|
||||
app:showAsAction="never"/>
|
||||
|
||||
<item android:id="@+id/action_component_disable"
|
||||
android:title="Disable Component"
|
||||
app:showAsAction="never"/>
|
||||
|
||||
<item android:id="@+id/action_component_enabled"
|
||||
android:title="Enable Component"
|
||||
app:showAsAction="never"/>
|
||||
|
||||
<item android:id="@+id/action_stop_pulsing"
|
||||
android:title="Stop pulsing"
|
||||
app:showAsAction="never"/>
|
||||
|
||||
<item android:id="@+id/action_start_pulsing"
|
||||
android:title="Start pulsing"
|
||||
app:showAsAction="never"/>
|
||||
|
||||
</menu>
|
||||
@@ -85,6 +85,8 @@
|
||||
<string name="description_location_map_change">Change map\'s style while location is displayed</string>
|
||||
<string name="description_location_modes">Showcases location render and tracking modes</string>
|
||||
<string name="description_location_fragment">Uses LocationComponent in a Fragment</string>
|
||||
<string name="description_location_basic_pulsing_circle">Display the LocationComponent\'s default pulsing circle</string>
|
||||
<string name="description_location_customized_pulsing_circle">Display and customize the LocationComponent\'s pulsing circle</string>
|
||||
<string name="description_location_manual">Force location updates and don\'t rely on the engine</string>
|
||||
<string name="description_location_activation_builder">Use LocationComponentActivationOptions to set options</string>
|
||||
<string name="description_recyclerview_textureview">Show multiple TextureView MapViews as recyclerView items</string>
|
||||
|
||||
@@ -81,6 +81,8 @@
|
||||
<string name="activity_draggable_maker">Draggable marker</string>
|
||||
<string name="activity_location_map_change">Simple Location Activity</string>
|
||||
<string name="activity_location_modes">Location Modes Activity</string>
|
||||
<string name="activity_basic_location_pulsing_circle">Basic Pulsing Circle Activity</string>
|
||||
<string name="activity_customized_location_pulsing_circle">Customized Pulsing Circle Activity</string>
|
||||
<string name="activity_location_fragment">Location Fragment</string>
|
||||
<string name="activity_location_manual">Manual Location updates</string>
|
||||
<string name="activity_location_activation_builder">Build Location Activation</string>
|
||||
|
||||
Reference in New Issue
Block a user