Skip to content

Commit 73d27a9

Browse files
committed
fix(android): dynamically update map overlay properties
1 parent e02718e commit 73d27a9

4 files changed

Lines changed: 345 additions & 0 deletions

File tree

android/src/main/java/com/luggmaps/LuggMarkerView.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import com.facebook.react.views.view.ReactViewGroup
1010
import com.google.android.gms.maps.model.AdvancedMarker
1111
import com.google.android.gms.maps.model.BitmapDescriptor
1212
import com.google.android.gms.maps.model.BitmapDescriptorFactory
13+
import com.google.android.gms.maps.model.LatLng
1314
import com.luggmaps.events.MarkerDragEvent
1415
import com.luggmaps.events.MarkerPressEvent
1516
import com.luggmaps.extensions.dispatchEvent
@@ -246,6 +247,13 @@ class LuggMarkerView(context: Context) : ReactViewGroup(context) {
246247
fun setCoordinate(latitude: Double, longitude: Double) {
247248
this.latitude = latitude
248249
this.longitude = longitude
250+
val m = marker ?: return
251+
if (!isDragging) {
252+
m.position = LatLng(latitude, longitude)
253+
if (!rasterize) {
254+
onUpdate?.invoke()
255+
}
256+
}
249257
}
250258

251259
fun setTitle(title: String?) {
Lines changed: 334 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,334 @@
1+
package com.luggmaps
2+
3+
import android.content.Context
4+
import android.graphics.Canvas
5+
import android.view.View
6+
import android.view.accessibility.AccessibilityEvent
7+
import androidx.core.graphics.createBitmap
8+
import androidx.core.view.isNotEmpty
9+
import com.facebook.react.views.view.ReactViewGroup
10+
import com.google.android.gms.maps.model.AdvancedMarker
11+
import com.google.android.gms.maps.model.BitmapDescriptor
12+
import com.google.android.gms.maps.model.BitmapDescriptorFactory
13+
import com.luggmaps.events.MarkerDragEvent
14+
import com.luggmaps.events.MarkerPressEvent
15+
import com.luggmaps.extensions.dispatchEvent
16+
17+
interface LuggMarkerViewDelegate {
18+
fun markerViewDidUpdate(markerView: LuggMarkerView)
19+
fun markerViewDidLayout(markerView: LuggMarkerView)
20+
fun showCalloutForMarkerView(markerView: LuggMarkerView)
21+
fun hideCalloutForMarkerView(markerView: LuggMarkerView)
22+
}
23+
24+
class LuggMarkerView(context: Context) : ReactViewGroup(context) {
25+
private var scaleUpdateRunnable: Runnable? = null
26+
27+
var name: String? = null
28+
private set
29+
30+
var latitude: Double = 0.0
31+
private set
32+
var longitude: Double = 0.0
33+
private set
34+
35+
var title: String? = null
36+
private set
37+
var description: String? = null
38+
private set
39+
40+
var delegate: LuggMarkerViewDelegate? = null
41+
var marker: AdvancedMarker? = null
42+
43+
var anchorX: Float = 0.5f
44+
private set
45+
var anchorY: Float = 1.0f
46+
private set
47+
48+
var zIndex: Float = 0f
49+
private set
50+
51+
var rotate: Float = 0f
52+
private set
53+
54+
var scale: Float = 1f
55+
private set
56+
57+
var scaleChanged: Boolean = false
58+
private set
59+
60+
var rasterize: Boolean = true
61+
private set
62+
63+
var centerOnPress: Boolean = true
64+
private set
65+
66+
var draggable: Boolean = false
67+
private set
68+
69+
var isDragging: Boolean = false
70+
71+
var didLayout: Boolean = false
72+
private set
73+
74+
val hasCustomView: Boolean
75+
get() = contentView.isNotEmpty()
76+
77+
val contentView: ReactViewGroup = ReactViewGroup(context)
78+
79+
var onUpdate: (() -> Unit)? = null
80+
81+
var calloutView: LuggCalloutView? = null
82+
private set
83+
84+
val scaledContentHeight: Float
85+
get() {
86+
val (_, height) = measureContentBounds()
87+
return height * scale
88+
}
89+
90+
private fun measureContentBounds(): Pair<Int, Int> {
91+
var maxWidth = 0
92+
var maxHeight = 0
93+
for (i in 0 until contentView.childCount) {
94+
val child = contentView.getChildAt(i)
95+
val childRight = child.left + child.width
96+
val childBottom = child.top + child.height
97+
if (childRight > maxWidth) maxWidth = childRight
98+
if (childBottom > maxHeight) maxHeight = childBottom
99+
}
100+
return Pair(maxWidth, maxHeight)
101+
}
102+
103+
private fun createContentBitmap(): BitmapDescriptor? {
104+
val (width, height) = measureContentBounds()
105+
if (width <= 0 || height <= 0) return null
106+
107+
val scaledWidth = (width * scale).toInt()
108+
val scaledHeight = (height * scale).toInt()
109+
110+
val bitmap = createBitmap(scaledWidth, scaledHeight)
111+
val canvas = Canvas(bitmap)
112+
canvas.scale(scale, scale)
113+
contentView.draw(canvas)
114+
return BitmapDescriptorFactory.fromBitmap(bitmap)
115+
}
116+
117+
fun layoutContentView() {
118+
val (width, height) = measureContentBounds()
119+
val scaledWidth = (width * scale).toInt()
120+
val scaledHeight = (height * scale).toInt()
121+
122+
contentView.scaleX = scale
123+
contentView.scaleY = scale
124+
contentView.pivotX = 0f
125+
contentView.pivotY = 0f
126+
127+
contentView.measure(
128+
View.MeasureSpec.makeMeasureSpec(scaledWidth, View.MeasureSpec.EXACTLY),
129+
View.MeasureSpec.makeMeasureSpec(scaledHeight, View.MeasureSpec.EXACTLY)
130+
)
131+
contentView.layout(0, 0, scaledWidth, scaledHeight)
132+
}
133+
134+
fun applyIconToMarker() {
135+
val m = marker ?: return
136+
if (!hasCustomView) return
137+
createContentBitmap()?.let { m.setIcon(it) }
138+
}
139+
140+
fun applyScaleToMarker() {
141+
val m = marker ?: return
142+
if (!hasCustomView) return
143+
144+
scaleUpdateRunnable?.let { removeCallbacks(it) }
145+
scaleUpdateRunnable = Runnable {
146+
if (rasterize) {
147+
createContentBitmap()?.let { m.setIcon(it) }
148+
} else {
149+
layoutContentView()
150+
onUpdate?.invoke()
151+
}
152+
}
153+
post(scaleUpdateRunnable)
154+
}
155+
156+
fun updateIcon(onAddMarker: () -> Unit) {
157+
if (!hasCustomView) return
158+
post {
159+
if (marker == null) {
160+
onAddMarker()
161+
} else if (rasterize) {
162+
applyIconToMarker()
163+
} else {
164+
layoutContentView()
165+
onUpdate?.invoke()
166+
}
167+
}
168+
}
169+
170+
init {
171+
visibility = GONE
172+
}
173+
174+
override fun addView(child: View, index: Int) {
175+
if (child is LuggCalloutView) {
176+
calloutView = child
177+
} else {
178+
contentView.addView(child, index)
179+
}
180+
didLayout = false
181+
}
182+
183+
override fun removeView(child: View) {
184+
if (child is LuggCalloutView) {
185+
calloutView = null
186+
} else {
187+
contentView.removeView(child)
188+
}
189+
didLayout = false
190+
}
191+
192+
override fun removeViewAt(index: Int) {
193+
val child = getChildAt(index)
194+
if (child is LuggCalloutView) {
195+
calloutView = null
196+
} else {
197+
contentView.removeViewAt(index)
198+
}
199+
didLayout = false
200+
}
201+
202+
override fun removeViews(start: Int, count: Int) {
203+
for (i in (start until start + count).reversed()) {
204+
val child = getChildAt(i)
205+
if (child is LuggCalloutView) {
206+
calloutView = null
207+
} else if (i < contentView.childCount) {
208+
contentView.removeViewAt(i)
209+
}
210+
}
211+
didLayout = false
212+
}
213+
214+
override fun getChildCount(): Int = contentView.childCount + if (calloutView != null) 1 else 0
215+
216+
override fun getChildAt(index: Int): View? {
217+
if (index < contentView.childCount) return contentView.getChildAt(index)
218+
if (index == contentView.childCount && calloutView != null) return calloutView
219+
return null
220+
}
221+
222+
// no-op: avoid accessibility traversal of detached marker children
223+
override fun addChildrenForAccessibility(outChildren: ArrayList<View>) {}
224+
override fun dispatchPopulateAccessibilityEvent(event: AccessibilityEvent): Boolean = false
225+
226+
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
227+
super.onSizeChanged(w, h, oldw, oldh)
228+
didLayout = false
229+
}
230+
231+
override fun onLayout(
232+
changed: Boolean,
233+
left: Int,
234+
top: Int,
235+
right: Int,
236+
bottom: Int
237+
) {
238+
super.onLayout(changed, left, top, right, bottom)
239+
240+
if (changed && !didLayout) {
241+
didLayout = true
242+
delegate?.markerViewDidLayout(this)
243+
}
244+
}
245+
246+
fun setCoordinate(latitude: Double, longitude: Double) {
247+
this.latitude = latitude
248+
this.longitude = longitude
249+
}
250+
251+
fun setTitle(title: String?) {
252+
this.title = title
253+
}
254+
255+
fun setDescription(description: String?) {
256+
this.description = description
257+
}
258+
259+
fun setAnchor(x: Double, y: Double) {
260+
anchorX = x.toFloat()
261+
anchorY = y.toFloat()
262+
}
263+
264+
fun setZIndex(zIndex: Float) {
265+
this.zIndex = zIndex
266+
}
267+
268+
fun setRotate(rotate: Float) {
269+
this.rotate = rotate
270+
}
271+
272+
fun setScale(scale: Float) {
273+
scaleChanged = this.scale != scale
274+
this.scale = scale
275+
}
276+
277+
fun clearScaleChanged() {
278+
scaleChanged = false
279+
}
280+
281+
fun setRasterize(rasterize: Boolean) {
282+
this.rasterize = rasterize
283+
}
284+
285+
fun setCenterOnPress(centerOnPress: Boolean) {
286+
this.centerOnPress = centerOnPress
287+
}
288+
289+
fun setDraggable(draggable: Boolean) {
290+
this.draggable = draggable
291+
}
292+
293+
fun emitPressEvent(x: Float, y: Float) {
294+
dispatchEvent(MarkerPressEvent(this, latitude, longitude, x, y))
295+
}
296+
297+
fun emitDragStartEvent(x: Float, y: Float) {
298+
dispatchEvent(MarkerDragEvent(this, MarkerDragEvent.DRAG_START, latitude, longitude, x, y))
299+
}
300+
301+
fun emitDragChangeEvent(x: Float, y: Float) {
302+
dispatchEvent(MarkerDragEvent(this, MarkerDragEvent.DRAG_CHANGE, latitude, longitude, x, y))
303+
}
304+
305+
fun emitDragEndEvent(x: Float, y: Float) {
306+
dispatchEvent(MarkerDragEvent(this, MarkerDragEvent.DRAG_END, latitude, longitude, x, y))
307+
}
308+
309+
fun showCallout() {
310+
delegate?.showCalloutForMarkerView(this)
311+
}
312+
313+
fun hideCallout() {
314+
delegate?.hideCalloutForMarkerView(this)
315+
}
316+
317+
fun setName(name: String?) {
318+
this.name = name
319+
}
320+
321+
fun onAfterUpdateTransaction() {
322+
delegate?.markerViewDidUpdate(this)
323+
}
324+
325+
fun onDropViewInstance() {
326+
scaleUpdateRunnable?.let { removeCallbacks(it) }
327+
scaleUpdateRunnable = null
328+
onUpdate = null
329+
didLayout = false
330+
calloutView = null
331+
delegate = null
332+
contentView.removeAllViews()
333+
}
334+
}

android/src/main/java/com/luggmaps/LuggPolygonView.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,12 @@ class LuggPolygonView(context: Context) : ReactViewGroup(context) {
4343

4444
fun setCoordinates(coords: List<LatLng>) {
4545
coordinates = coords
46+
polygon?.points = coords
4647
}
4748

4849
fun setHoles(value: List<List<LatLng>>) {
4950
holes = value
51+
polygon?.holes = value
5052
}
5153

5254
fun setStrokeColor(color: Int) {

android/src/main/java/com/luggmaps/LuggPolylineView.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ class LuggPolylineView(context: Context) : ReactViewGroup(context) {
4040

4141
fun setCoordinates(coords: List<LatLng>) {
4242
coordinates = coords
43+
polyline?.points = coords
4344
}
4445

4546
fun setStrokeColors(colors: List<Int>) {

0 commit comments

Comments
 (0)