Skip to content

Commit 3ae52f3

Browse files
[maps-android] MAPSAND-2520 Create new LineCutoutExample in Compose (#15529)
Task: MAPSAND-2520 Similar iOS task: MAPSIOS-2096 was implemented in this PR: mapbox/mapbox-sdk#15455 ## Why is this PR needed? The line cutout API (`lineCutoutOpacity`, `lineCutoutFadeWidth`) has no dedicated example. This PR adds one to the Experimental APIs section so developers can see how to use it. ### Demo Video 📺 https://github.com/user-attachments/assets/18674a53-94f5-4e59-ab8c-1efa678e53c4 ### Demo Comparison 🖼️ #### iOS VS Android <img width="777" height="863" alt="Demo iOS:Android" src="https://github.com/user-attachments/assets/93016e63-47ba-475a-89af-5f73657cccf9" /> ## What changed? - Added `LineCutoutActivity.kt` in `examples/experimental/` - new Compose example showing a route line from BMW Headquarters through Olympiapark in Munich. - Two sliders let you interact with `lineCutoutOpacity` and `lineCutoutFadeWidth` in real time. - Registered `LineCutoutActivity` in `AndroidManifest.xml`. - Logo and attribution ornaments are pushed above the controls panel using `contentPadding` offset. - Controls panel respects navigation bar insets via `navigationBarsPadding()`. cc @mapbox/maps-android GitOrigin-RevId: 6efb7f4d74a578ca6a55b9a251bc53fd30f474d9
1 parent dde857f commit 3ae52f3

5 files changed

Lines changed: 206 additions & 3 deletions

File tree

compose-app/src/main/AndroidManifest.xml

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@
5555
android:value="@string/category_basic" />
5656
</activity>
5757
<activity
58-
android:name=".examples.basic.AccessibilityScaleActivity"
58+
android:name=".examples.lab.AccessibilityScaleActivity"
5959
android:configChanges="orientation|screenSize|screenLayout"
6060
android:description="@string/description_accessibility_scale"
6161
android:exported="true"
@@ -463,6 +463,16 @@
463463
android:name="@string/category"
464464
android:value="@string/category_styles" />
465465
</activity>
466+
<activity
467+
android:name=".examples.lab.LineCutoutActivity"
468+
android:description="@string/description_line_cutout"
469+
android:exported="true"
470+
android:label="@string/activity_line_cutout"
471+
android:parentActivityName=".ExampleOverviewActivity">
472+
<meta-data
473+
android:name="@string/category"
474+
android:value="@string/category_lab" />
475+
</activity>
466476
<activity
467477
android:name=".examples.model.Interactive3DModelFeatureStateActivity"
468478
android:description="@string/description_3d_model_feature_state_interactions"
@@ -495,4 +505,4 @@
495505
</activity>
496506
</application>
497507

498-
</manifest>
508+
</manifest>

compose-app/src/main/java/com/mapbox/maps/compose/testapp/examples/basic/AccessibilityScaleActivity.kt renamed to compose-app/src/main/java/com/mapbox/maps/compose/testapp/examples/lab/AccessibilityScaleActivity.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.mapbox.maps.compose.testapp.examples.basic
1+
package com.mapbox.maps.compose.testapp.examples.lab
22

33
import android.os.Bundle
44
import androidx.activity.ComponentActivity
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
package com.mapbox.maps.compose.testapp.examples.lab
2+
3+
import android.os.Bundle
4+
import androidx.activity.ComponentActivity
5+
import androidx.activity.compose.setContent
6+
import androidx.compose.foundation.layout.Column
7+
import androidx.compose.foundation.layout.PaddingValues
8+
import androidx.compose.foundation.layout.Row
9+
import androidx.compose.foundation.layout.fillMaxSize
10+
import androidx.compose.foundation.layout.fillMaxWidth
11+
import androidx.compose.foundation.layout.navigationBarsPadding
12+
import androidx.compose.foundation.layout.padding
13+
import androidx.compose.foundation.shape.RoundedCornerShape
14+
import androidx.compose.material.Slider
15+
import androidx.compose.material.Surface
16+
import androidx.compose.material.Text
17+
import androidx.compose.runtime.Composable
18+
import androidx.compose.runtime.getValue
19+
import androidx.compose.runtime.mutableStateOf
20+
import androidx.compose.runtime.remember
21+
import androidx.compose.runtime.setValue
22+
import androidx.compose.ui.Alignment
23+
import androidx.compose.ui.Modifier
24+
import androidx.compose.ui.graphics.Color
25+
import androidx.compose.ui.unit.dp
26+
import com.mapbox.geojson.Feature
27+
import com.mapbox.geojson.LineString
28+
import com.mapbox.geojson.Point
29+
import com.mapbox.maps.MapboxDelicateApi
30+
import com.mapbox.maps.MapboxExperimental
31+
import com.mapbox.maps.compose.testapp.ExampleScaffold
32+
import com.mapbox.maps.compose.testapp.ui.theme.MapboxMapComposeTheme
33+
import com.mapbox.maps.dsl.cameraOptions
34+
import com.mapbox.maps.extension.compose.MapboxMap
35+
import com.mapbox.maps.extension.compose.animation.viewport.rememberMapViewportState
36+
import com.mapbox.maps.extension.compose.style.ColorValue
37+
import com.mapbox.maps.extension.compose.style.DoubleValue
38+
import com.mapbox.maps.extension.compose.style.layers.generated.LineCapValue
39+
import com.mapbox.maps.extension.compose.style.layers.generated.LineJoinValue
40+
import com.mapbox.maps.extension.compose.style.layers.generated.LineLayer
41+
import com.mapbox.maps.extension.compose.style.sources.GeoJSONData
42+
import com.mapbox.maps.extension.compose.style.sources.generated.rememberGeoJsonSourceState
43+
import com.mapbox.maps.extension.compose.style.standard.MapboxStandardStyle
44+
45+
/**
46+
* Example demonstrating the line cutout API.
47+
*
48+
* Shows a route from BMW Headquarters through Olympiapark to Olympiastadion in Munich.
49+
* Two sliders let you interact with lineCutoutOpacity and lineCutoutFadeWidth in real time.
50+
*/
51+
@OptIn(MapboxExperimental::class)
52+
public class LineCutoutActivity : ComponentActivity() {
53+
override fun onCreate(savedInstanceState: Bundle?) {
54+
super.onCreate(savedInstanceState)
55+
setContent {
56+
var cutoutOpacity by remember { mutableStateOf(0.5f) }
57+
var fadeWidth by remember { mutableStateOf(0.5f) }
58+
59+
MapboxMapComposeTheme {
60+
ExampleScaffold(
61+
bottomBar = {
62+
Surface(
63+
modifier = Modifier
64+
.fillMaxWidth()
65+
.navigationBarsPadding()
66+
.padding(horizontal = 20.dp, vertical = 8.dp),
67+
shape = RoundedCornerShape(14.dp),
68+
elevation = 4.dp
69+
) {
70+
Column(modifier = Modifier.padding(16.dp)) {
71+
LineCutoutSlider(
72+
title = "Cutout opacity",
73+
value = cutoutOpacity,
74+
onValueChange = { cutoutOpacity = it }
75+
)
76+
LineCutoutSlider(
77+
title = "Fade width",
78+
value = fadeWidth,
79+
onValueChange = { fadeWidth = it }
80+
)
81+
}
82+
}
83+
}
84+
) { scaffoldPadding ->
85+
LineCutoutExample(
86+
cutoutOpacity = cutoutOpacity,
87+
fadeWidth = fadeWidth,
88+
scaffoldPadding = scaffoldPadding
89+
)
90+
}
91+
}
92+
}
93+
}
94+
95+
@OptIn(MapboxExperimental::class, MapboxDelicateApi::class)
96+
@Composable
97+
private fun LineCutoutExample(
98+
cutoutOpacity: Float,
99+
fadeWidth: Float,
100+
scaffoldPadding: PaddingValues
101+
) {
102+
val routeSourceState = rememberGeoJsonSourceState {
103+
data = GeoJSONData(
104+
Feature.fromGeometry(LineString.fromLngLats(ROUTE_COORDINATES))
105+
)
106+
}
107+
108+
MapboxMap(
109+
modifier = Modifier.fillMaxSize(),
110+
mapViewportState = rememberMapViewportState {
111+
setCameraOptions(CAMERA_OPTIONS)
112+
},
113+
logo = {
114+
Logo(modifier = Modifier.padding(bottom = scaffoldPadding.calculateBottomPadding()))
115+
},
116+
attribution = {
117+
Attribution(modifier = Modifier.padding(bottom = scaffoldPadding.calculateBottomPadding()))
118+
},
119+
style = {
120+
MapboxStandardStyle(
121+
middleSlot = {
122+
// Visible route line
123+
LineLayer(
124+
sourceState = routeSourceState,
125+
layerId = "route-line"
126+
) {
127+
lineCap = LineCapValue.ROUND
128+
lineJoin = LineJoinValue.ROUND
129+
lineWidth = DoubleValue(8.0)
130+
lineColor = ColorValue(Color.Blue)
131+
lineEmissiveStrength = DoubleValue(1.0)
132+
}
133+
134+
// Cutout layer — transparent wide line that makes 3D buildings see-through
135+
LineLayer(
136+
sourceState = routeSourceState,
137+
layerId = "cutout-line"
138+
) {
139+
lineWidth = DoubleValue(40.0)
140+
lineColor = ColorValue(Color.Transparent)
141+
lineCutoutOpacity = DoubleValue(cutoutOpacity.toDouble())
142+
lineCutoutFadeWidth = DoubleValue(fadeWidth.toDouble())
143+
}
144+
}
145+
)
146+
}
147+
)
148+
}
149+
150+
@Composable
151+
private fun LineCutoutSlider(
152+
title: String,
153+
value: Float,
154+
onValueChange: (Float) -> Unit
155+
) {
156+
Row(
157+
verticalAlignment = Alignment.CenterVertically,
158+
modifier = Modifier.fillMaxWidth()
159+
) {
160+
Text(title, modifier = Modifier.weight(1.2f))
161+
Slider(
162+
value = value,
163+
onValueChange = onValueChange,
164+
valueRange = 0f..1f,
165+
modifier = Modifier.weight(2f)
166+
)
167+
Text(
168+
"%.2f".format(value),
169+
modifier = Modifier.padding(start = 8.dp)
170+
)
171+
}
172+
}
173+
174+
private companion object {
175+
// Route from BMW Headquarters through Olympiapark to Olympiastadion, Munich
176+
val ROUTE_COORDINATES = listOf(
177+
Point.fromLngLat(11.56024, 48.17691),
178+
Point.fromLngLat(11.55910, 48.17660),
179+
Point.fromLngLat(11.55376, 48.17620),
180+
Point.fromLngLat(11.55153, 48.17355),
181+
Point.fromLngLat(11.55004, 48.17493),
182+
)
183+
184+
val CAMERA_OPTIONS = cameraOptions {
185+
center(Point.fromLngLat(11.55670, 48.17664))
186+
zoom(15.48)
187+
bearing(48.6)
188+
pitch(76.5)
189+
}
190+
}
191+
}

compose-app/src/main/res/values/example_descriptions.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
<string name="description_3d_model_feature_state_interactions">Use a style-driven method for overriding the materials and orientation of model parts using the model layer and model-source.</string>
4242
<string name="description_animated_3d_model">Animate a 3D airplane model along a flight path with animated propellers, landing gear, and lights using feature state.</string>
4343
<string name="description_accessibility_scale">Automatic map symbol scaling based on system font size preferences</string>
44+
<string name="description_line_cutout">Make route lines visible through 3D buildings using the line cutout API.</string>
4445
<string name="description_edge_to_edge">Showcase edge-to-edge layout using Jetpack Compose with proper handling of system insets (system bars, navigation bars, and display cutouts).</string>
4546
<string name="description_lazy_column_map">Showcase MapboxMap inside a LazyColumn with correct scroll lifecycle management.</string>
4647
<string name="description_fling_mode">Toggle native OverScroller fling deceleration and scroll mode via GesturesSettings.</string>

compose-app/src/main/res/values/example_titles.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
<string name="activity_3d_model_feature_state_interactions">3D model with style-driven interactions</string>
4242
<string name="activity_animated_3d_model">Animated 3D airplane model</string>
4343
<string name="activity_accessibility_scale">Accessibility scale</string>
44+
<string name="activity_line_cutout">Line Cutout</string>
4445
<string name="activity_edge_to_edge">Edge-to-Edge layout</string>
4546
<string name="activity_lazy_column_map">Map in LazyColumn</string>
4647
<string name="activity_fling_mode">Fling mode</string>

0 commit comments

Comments
 (0)