پیاده سازی نقشه در Jetpack Compose

مستندات جامع اندروید

نسخه 1.0.3

Jetpack Compose یک toolkit برای ساخت UI است که به ساخت UI سادگی و سرعت بخشیده است.
جهت کسب اطلاعات بیشتر در مورد Jetpack compose میتوانید به این صفحه مراجعه نمایید.

در این نسخه از اس دی کی نشان، امکان پیاده سازی و استفاده از نقشه در Jetpack Compose فراهم شده است.

ابتدا پس از پیاده سازی dependency های مربوط به Jetpack Compose از وجود dependency های زیر در فایل build.gradle خود مطمئن شوید:

implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'

سپس پس از طی کردن مراحل مربوط به راه اندازی اس دی کی در صفحه شروع کار با کیت توسعه اندروید، جهت پیاده سازی نقشه نشان در این Toolkit میتوانید به صورت زیر عمل نمایید:

            	override fun onStart() {
        super.onStart()
        // Initializing user location
        setContent {
            MyApplicationTheme {
                // A surface container using the 'background' color from the theme
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {
                    NeshanMapView(this@MainActivity)
                }
            }
        }
    }
    
@Composable
private fun NeshanMapView(context: Context) {
	Surface() {
		Row() {
			Column() {
				AndroidView(
					factory = { context ->
						MapView(context) as View
					},
					update = {}
				)
			}
		}
	}
}
        

تابع NeshanMapView با ایجاد یک AndroidView آبجکت MapView ی نشان را به UI اضافه میکند.

ست کردن موارد پیش فرض نقشه

در صورتی که میخواهید نقشه به صورت پیش فرض دارای مقادیر دلخواه باشد، میتوانید مقادیر را در بلوک apply از MapView ست نمایید.

در مثال زیر، هنگام لود نقشه، زوم نقشه روی 13 و لایه ترافیک غیر فعال و لایه poi فعال شده است.

            @Composable
private fun NeshanMapView(context: Context) {
	Surface() {
		Row() {
			Column() {
				AndroidView(
					factory = { context ->
						MapView(context).apply {
							setZoom(13f, .5f)
							isTrafficEnabled = false
							isPoiEnabled = true
							.
							.
							.
						}
					},
					update = {}
				)
			}
		}
	}
}
        

افزودن مارکر

جهت اعمال تغییرات روی نقشه مانند افزودن مارکر، تغییر جهت نقشه، چرخاندن نقشه و … باید از Recomposition استفاده کرد. همچنین چون متغیرها داخل تابع NeshanMapView تعریف شده است، برای حفظ مقادیر قبلی هنگام recomposition باید از remember استفاده کرد.

با توجه به توضیحات بالا، نحوه افزودن مارکر در Jetpack Compose به صورت زیر است:

در مثال بالا یک نقشه و یک ElevatedButton زیر آن ایجاد شده است که با کلیک روی دکمه یک مارکر روی نقشه ایجاد میشود.

فرایند افزودن مارکر به این صورت است که پس از کلیک کاربر روی دکمه Add marker متد createMarker یک شی مارکر با مختصات مشخص ایجاد میکند و مقدار بازگشتی تابع که از نوع Marker است در متغیر marker که در فانکشن NeshanMapView ساخته شده است ریخته میشود.

متغیر markerRemember متغیری از نوع remember است که آبجکت ایجاد شده را در هر Recomposition نگهداری میکند. marker ایجاد شده به عنوان value ی markerRemember ست میشود و عملیات Recomposition انجام میشود.

هنگام اجرای Recomposition تابع NeshanMapView مجددا اجرا میشود و value ی مربوط به markerRemember در بلوک update چک میشود.

در صورتی که مقدار آن مخالف null باشد، توسط شی it که نام object نقشه افزوده شده است و متد addMarker، مارکر ایجاد شده روی نقشه اضافه میشود.

            @Composable
private fun NeshanMapView(context: Context) {

    var marker: Marker? = null

    val markerRemember = remember { mutableStateOf(marker) }

    Surface() {
        Row() {
            Column() {
                AndroidView(
                    factory = { context ->
                        MapView(context) as View
                    },
                    update = {
                        if (markerRemember.value != null) {
                            it.addMarker(markerRemember.value)
                            markerRemember.value = null
                        }
                    },
                    modifier = Modifier.weight(1f),
                )
                ElevatedButton(
                    onClick = {
                        marker = createMarker(context, LatLng(35.12345, 52.12345))
                        markerRemember.value = marker
                    }
                ) {
                    Text("Add marker")
                }
            }
        }
    }
}
        

موارد زیر را میتوان همانند روش افزودن مارکر روی نقشه اعمال کرد.

حذف مارکر

قطعه کد زیر نحوه حذف مارکر از روی نقشه را نمایش می دهد:

            @Composable
private fun NeshanMapView(context: Context) {

	var marker1: Marker? = null

	val marker1Remember = remember { mutableStateOf(marker1) }
	val removeMarker1Remember = remember { mutableStateOf(false) }

	Surface() {
		Row() {
			Column() {
				AndroidView(
					factory = { context ->
						MapView(context) as View
					},
					update = {
						if (marker1Remember.value != null) {
							it.addMarker(marker1Remember.value)
							marker1Remember.value = null
						}
						if(removeMarker1Remember.value){
							it.removeMarker(marker1)
						}
					},
					modifier = Modifier.weight(1f),
				)
				ElevatedButton(
					onClick = {
						marker1 = createMarker(context, LatLng(35.70204, 51.335352))
						marker1Remember.value = marker1
					}
				) {
					Text("Add marker")
				}
				ElevatedButton(
					onClick = {
						removeMarker1Remember.value = true
					}
				) {
					Text("Remove marker")
				}
			}
		}
	}
}
        

در مثال بالا متغیری از نوع Marker به نام marker1 ساخته میشود. سپس یک متغیر از نوع remember برای ذخیذه سازی مقدار marker1 ساخته میشود.

متغیر removeMarker1Remember نیز از نوع remember است و برای حذف مارکر از آن استفاده میکنیم.

پس از کلیک روی دکمه Add marker، متغیر marker1 ساخته شده و به عنوان value ی متغیر marker1Remember ست میشود.

پس از ست شدن این مقدار، عملیات Recomposition به صورت اتوماتیک اتفاق می افتد و در بلوک update چک میشود که آیا مقدار value ی متغیر marker1Remember مخالف null است یا خیر که در این صورت marker1 روی نقشه اضافه میشود.

سپس پس از کلیک روی دکمه Remove marker، مقداری value ی متغیر removeMarker1Remember برابر true ست میشود و مجددا عملیات Recomposition اتفاق می افتد.

در عملیات Recomposition، در بلوک update چک میشود که آیا مقدار removeMarker1Remember برابر true است یا خیر، که در این صورت توسط شی نقشه که در اینجا it نام دارد، اقدام به حذف مارکر میکند.

رسم خط

قطعه کد زیر نحوه رسم خط روی نقشه را نمایش می دهد:

            @Composable
private fun NeshanMapView(context: Context) {

    var polyline: Polyline? = null

    val polylineRemember = remember { mutableStateOf(polyline) }

    Surface() {
        Row() {
            Column() {
                AndroidView(
                    factory = { context ->
                        MapView(context) as View
                    },
                    update = {
                        if(polylineRemember.value != null){
                            it.addPolyline(polylineRemember.value)
                        }
                    },
                    modifier = Modifier.weight(1f),
                )
                ElevatedButton(
                    onClick = {
                        polyline = createPolyline()
                        polylineRemember.value = polyline
                    }
                ) {
                    Text("Add polyline")
                }
            }
        }
    }
}
    
fun createPolyline(): Polyline {
	var latLngs = ArrayList<LatLng>()
	latLngs.add(LatLng(35.769368, 51.327650))
	latLngs.add(LatLng(35.756670, 51.323889))
	latLngs.add(LatLng(35.746670, 51.383889))

	return Polyline(latLngs, getLineStyle())
}

fun getLineStyle(): LineStyle {
	val lineStyleBuilder = LineStyleBuilder()
	lineStyleBuilder.color = Color(2, 119, 189, 190)
	lineStyleBuilder.width = 4f
	return lineStyleBuilder.buildStyle()
}
        

رسم و حذف دایره

            @Composable
private fun NeshanMapView(context: Context) {

	var circle1: Circle? = null

	val circle1Remember = remember { mutableStateOf(circle1) }
	val removeCircle1Remember = remember { mutableStateOf(false) }

	Surface() {
		Row() {
			Column() {
				AndroidView(
					factory = { context ->
						MapView(context) as View
					},
					update = {
						if (circle1Remember.value != null) {
							it.addCircle(circle1Remember.value)
							circle1Remember.value = null
						}
						if (removeCircle1Remember.value) {
							it.removeCircle(circle1)
						}
					},
					modifier = Modifier.weight(1f),
				)
				ElevatedButton(
					onClick = {
						circle1 = createCircle(LatLng(35.70204, 51.335352),100f)
						circle1Remember.value = circle1
					}
				) {
					Text("Add circle")
				}
				ElevatedButton(
					onClick = {
						removeCircle1Remember.value = true
					}
				) {
					Text("Remove circle")
				}
			}
		}
	}
}

fun createCircle(centerLatLng: LatLng, radius: Double): Circle {
	return Circle(centerLatLng, radius, Color(2, 119, 189, 190), getLineStyle())
}

fun getLineStyle(): LineStyle {
	val lineStyleBuilder = LineStyleBuilder()
	lineStyleBuilder.color = Color(2, 119, 189, 190)
	lineStyleBuilder.width = 4f
	return lineStyleBuilder.buildStyle()
}
        

رسم و حذف چند ضلعی

            @Composable
private fun NeshanMapView(context: Context) {

	var polygon1: Polygon? = null

	val polygon1Remember = remember { mutableStateOf(polygon1) }
	val removePolygon1Remember = remember { mutableStateOf(false) }

	Surface() {
		Row() {
			Column() {
				AndroidView(
					factory = { context ->
						MapView(context) as View
					},
					update = {
						if (polygon1Remember.value != null) {
							it.addPolygon(polygon1Remember.value)
							polygon1Remember.value = null
						}
						if (removePolygon1Remember.value) {
							it.removePolygon(polygon1)
						}
					},
					modifier = Modifier.weight(1f),
				)
				ElevatedButton(
					onClick = {
						var latLngs = ArrayList<LatLng>()
						latLngs.add(LatLng(35.704760, 51.333912))
						latLngs.add(LatLng(35.702212, 51.340889))
						latLngs.add(LatLng(35.699977, 51.336472))
						latLngs.add(LatLng(35.700902, 51.330897))
						polygon1 = createPolygon(latLngs)

						polygon1Remember.value = polygon1
					}
				) {
					Text("Add polygon")
				}
				ElevatedButton(
					onClick = {
						removePolygon1Remember.value = true
					}
				) {
					Text("Remove polygon")
				}
			}
		}
	}
}
        

تغییر زاویه دوربین

            @Composable
private fun NeshanMapView(context: Context) {

	var tiltCameraAmount: Float? = null
	var tiltCameraDuration: Float? = null

	val tiltCameraAmountRemember = remember { mutableStateOf(tiltCameraAmount) }
	val tiltCameraDurationRemember = remember { mutableStateOf(tiltCameraDuration) }

	Surface() {
		Row() {
			Column() {
				AndroidView(
					factory = { context ->
						MapView(context) as View
					},
					update = {
						if (tiltCameraAmountRemember.value != null && tiltCameraDurationRemember.value != null) {
							it.setTilt(
								tiltCameraAmountRemember.value!!,
								tiltCameraDurationRemember.value!!
							)
							tiltCameraAmountRemember.value = null
							tiltCameraDurationRemember.value = null
						}
					},
					modifier = Modifier.weight(1f),
				)
				ElevatedButton(
					onClick = {
						tiltCameraAmount = 30f
						tiltCameraDuration = .5f
						tiltCameraAmountRemember.value = tiltCameraAmount
						tiltCameraDurationRemember.value = tiltCameraDuration
					}
				) {
					Text("Tilt camera")
				}
			}
		}
	}
}
        

تغییر جهت دوربین

            @Composable
private fun NeshanMapView(context: Context) {

	var cameraBearingAmount: Float? = null
	var cameraBearingDuration: Float? = null

	val cameraBearingAmountRemember = remember { mutableStateOf(cameraBearingAmount) }
	val cameraBearingDurationRemember = remember { mutableStateOf(cameraBearingDuration) }

	Surface() {
		Row() {
			Column() {
				AndroidView(
					factory = { context ->
						MapView(context) as View
					},
					update = {
						if (cameraBearingAmountRemember.value != null && cameraBearingDurationRemember.value != null) {
							it.setBearing(
								cameraBearingAmountRemember.value!!,
								cameraBearingDurationRemember.value!!
							)
							cameraBearingAmountRemember.value = null
							cameraBearingDurationRemember.value = null
						}
					},
					modifier = Modifier.weight(1f),
				)
				ElevatedButton(
					onClick = {
						cameraBearingAmount = 30f
						cameraBearingDuration = .5f
						cameraBearingAmountRemember.value = cameraBearingAmount
						cameraBearingDurationRemember.value = cameraBearingDuration
					}
				) {
					Text("Rotate camera")
				}
			}
		}
	}
}
        

تغییر استایل نقشه

            @Composable
private fun NeshanMapView(context: Context) {

	var mapStyle: Int? = null

	val mapStyleRemember = remember { mutableStateOf(mapStyle) }

	Surface() {
		Row() {
			Column() {
				AndroidView(
					factory = { context ->
						MapView(context) as View
					},
					update = {
						if (mapStyleRemember.value != null) {
							it.setMapStyle(mapStyleRemember.value!!)
						}
					},
					modifier = Modifier.weight(1f),
				)
				ElevatedButton(
					onClick = {
						mapStyleRemember.value = NeshanMapStyle.NESHAN_NIGHT
					}
				) {
					Text("Night style")
				}
			}
		}
	}
}
        

نمایش موقعیت کاربر

            class UserLocationActivity : ComponentActivity() {

    private val TAG: String = MainActivity::class.java.name
    private val REQUEST_CODE = 123
    private val UPDATE_INTERVAL_IN_MILLISECONDS: Long = 1000

    private var userMarker: Marker? = null
    private var addUserMarkerRemember = mutableStateOf(userMarker)
    private var moveCameraLatLng: LatLng? = null
    private var moveCameraState = mutableStateOf(moveCameraLatLng)
    private var moveCameraDuration: Float? = null
    private var moveCameraDurationState = mutableStateOf(moveCameraDuration)

    private var userLocation: Location? = null
    private lateinit var fusedLocationClient: FusedLocationProviderClient
    private lateinit var settingsClient: SettingsClient
    private lateinit var locationRequest: LocationRequest
    private var locationSettingsRequest: LocationSettingsRequest? = null
    private var locationCallback: LocationCallback? = null

    override fun onStart() {
        super.onStart()
        setContent {
            JetpackComposeTheme {
                // A surface container using the 'background' color from the theme
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {
                    NeshanMapView(this@UserLocationActivity)
                }
            }
        }
        initLocation()
        startReceivingLocationUpdates()
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
    }

    @Composable
    private fun NeshanMapView(context: Context) {
        Surface() {
            Row() {
                Column() {
                    AndroidView(
                        factory = { context ->
                            MapView(context) as View
                        },
                        update = {
                            if (addUserMarkerRemember.value != null) {
                                it.addMarker(addUserMarkerRemember.value)
                            }
                            if (moveCameraState.value != null && moveCameraDurationState.value != null) {
                                it.moveCamera(
                                    moveCameraState.value,
                                    moveCameraDurationState.value!!
                                )
                            }
                        },
                        modifier = Modifier.weight(1f),
                    )
                }
            }
        }
    }

    fun createMarker(context: Context, loc: LatLng?): Marker {
        val animStBl = AnimationStyleBuilder()
        animStBl.fadeAnimationType = AnimationType.ANIMATION_TYPE_SMOOTHSTEP
        animStBl.sizeAnimationType = AnimationType.ANIMATION_TYPE_SPRING
        animStBl.phaseInDuration = 0.5f
        animStBl.phaseOutDuration = 0.5f
        val animSt = animStBl.buildStyle()

        val markStCr = MarkerStyleBuilder()
        markStCr.size = 30f
        markStCr.bitmap = BitmapUtils.createBitmapFromAndroidBitmap(
            BitmapFactory.decodeResource(context.resources, R.drawable.ic_marker)
        )
        markStCr.animationStyle = animSt
        val markSt = markStCr.buildStyle()

        return Marker(loc, markSt)
    }

    private fun initLocation() {
        fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
        settingsClient = LocationServices.getSettingsClient(this)

        locationCallback = object : LocationCallback() {
            override fun onLocationResult(locationResult: LocationResult) {
                super.onLocationResult(locationResult)
                // location is received
                userLocation = locationResult.lastLocation
                onLocationChange()
            }
        }

        locationRequest = LocationRequest.Builder(
            Priority.PRIORITY_HIGH_ACCURACY,
            UPDATE_INTERVAL_IN_MILLISECONDS
        ).build()
        val builder = LocationSettingsRequest.Builder()
        builder.addLocationRequest(locationRequest)
        locationSettingsRequest = builder.build()
    }

    private fun startReceivingLocationUpdates() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            if (ContextCompat.checkSelfPermission(
                    this@UserLocationActivity,
                    Manifest.permission.ACCESS_FINE_LOCATION
                ) == PackageManager.PERMISSION_GRANTED && ContextCompat.checkSelfPermission(
                    this@UserLocationActivity,
                    Manifest.permission.ACCESS_COARSE_LOCATION
                ) == PackageManager.PERMISSION_GRANTED
            ) {
                startLocationUpdates()
            } else {
                requestPermissions(
                    arrayOf(
                        Manifest.permission.ACCESS_FINE_LOCATION,
                        Manifest.permission.ACCESS_COARSE_LOCATION
                    ), REQUEST_CODE
                )
            }

        } else {
            startLocationUpdates()
        }
    }

    private fun startLocationUpdates() {
        settingsClient
            .checkLocationSettings(locationSettingsRequest!!)
            .addOnSuccessListener(this, OnSuccessListener {
                Log.i(
                    TAG,
                    "All location settings are satisfied."
                )
                if (ContextCompat.checkSelfPermission(
                        this@UserLocationActivity,
                        Manifest.permission.ACCESS_FINE_LOCATION
                    ) != PackageManager.PERMISSION_GRANTED && ContextCompat.checkSelfPermission(
                        this@UserLocationActivity,
                        Manifest.permission.ACCESS_COARSE_LOCATION
                    ) != PackageManager.PERMISSION_GRANTED
                ) {
                    Log.d("UserLocationUpdater", " required permissions are not granted ")
                    return@OnSuccessListener
                }
                fusedLocationClient.requestLocationUpdates(
                    locationRequest,
                    locationCallback!!,
                    Looper.myLooper()
                )
            })
            .addOnFailureListener(this) { e ->
                val statusCode = (e as ApiException).statusCode
                when (statusCode) {
                    LocationSettingsStatusCodes.RESOLUTION_REQUIRED -> try {
                        Log.i(
                            TAG,
                            "Location settings are not satisfied. Attempting to upgrade location settings"
                        )
                        // Show the dialog by calling startResolutionForResult(), and check the
                        // result in onActivityResult().
                        val rae = e as ResolvableApiException
                        rae.startResolutionForResult(this@UserLocationActivity, REQUEST_CODE)
                    } catch (sie: IntentSender.SendIntentException) {
                        Log.i(
                            TAG,
                            "PendingIntent unable to execute request."
                        )
                    }
                    LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE -> {
                        val errorMessage =
                            "Location settings are inadequate, and cannot be fixed here. Fix in Settings."
                        Log.e(
                            TAG,
                            errorMessage
                        )
                        Toast.makeText(this@UserLocationActivity, errorMessage, Toast.LENGTH_LONG)
                            .show()
                    }
                }
            }
    }

    private fun onLocationChange() {
        if (userLocation != null) {
            stopLocationUpdates()
            var marker: Marker = createMarker(
                this@UserLocationActivity,
                LatLng(userLocation!!.latitude, userLocation!!.longitude)
            )
            addUserMarkerRemember.value = marker
            moveCameraState.value = LatLng(userLocation!!.latitude, userLocation!!.longitude)
            moveCameraDurationState.value = .5f
        }
    }

    private fun stopLocationUpdates() {
        // Removing location updates
        fusedLocationClient
            .removeLocationUpdates(locationCallback!!)
            .addOnCompleteListener(
                this
            ) {
                Toast.makeText(applicationContext, "Location updates stopped!", Toast.LENGTH_SHORT)
                    .show()
            }
    }
}
        

در مثال بالا علاوه بر پیاده سازی نحوه نمایش موقعیت کاربر، نحوه حرکت دادن دوربین را نیز پیاده سازی کرده ایم.

متغیرهای moveCameraLatLng ، moveCameraDuration ، moveCameraState و moveCameraDurationState برای حرکت دادن دوربین مورد استفاده قرار میگیرند.

فعالسازی لایه ترافیک

            @Composable
    private fun NeshanMapView(context: Context) {

        val trafficLayerRemember = remember { mutableStateOf(false) }

        Surface() {
            Row() {
                Column() {
                    AndroidView(
                        factory = { context ->
                            MapView(context) as View
                        },
                        update = {
                            if (trafficLayerRemember.value != null) {
                                it.setTrafficEnabled(trafficLayerRemember.value)
                            }
                        },
                        modifier = Modifier.weight(1f),
                    )
                    ElevatedButton(
                        onClick = {
                            trafficLayerRemember.value = !trafficLayerRemember.value
                        }
                    ) {
                        Text("Toggle traffic layer")
                    }
                }
            }
        }
    }
        

فعالسازی لایه poi

            @Composable
    private fun NeshanMapView(context: Context) {

        val poiLayerRemember = remember { mutableStateOf(false) }

        Surface() {
            Row() {
                Column() {
                    AndroidView(
                        factory = { context ->
                            MapView(context) as View
                        },
                        update = {
                            if (poiLayerRemember.value != null) {
                                it.setPoiEnabled(poiLayerRemember.value)
                            }
                        },
                        modifier = Modifier.weight(1f),
                    )
                    ElevatedButton(
                        onClick = {
                            poiLayerRemember.value = !poiLayerRemember.value
                        }
                    ) {
                        Text("Toggle poi layer")
                    }
                }
            }
        }
    }
        

افزودن برچسب

            @Composable
    private fun NeshanMapView(context: Context) {

        var label1:Label? = null

        val addLabel1Remember = remember { mutableStateOf(label1) }

        Surface() {
            Row() {
                Column() {
                    AndroidView(
                        factory = { context ->
                            MapView(context) as View
                        },
                        update = {
                            if (addLabel1Remember.value != null) {
                                it.addLabel(addLabel1Remember.value)
                            }
                        },
                        modifier = Modifier.weight(1f),
                    )
                    ElevatedButton(
                        onClick = {
                            addLabel1Remember.value = createLabel(LatLng(35.700476, 51.337644),"Label1 caption")
                        }
                    ) {
                        Text("Add label")
                    }
                }
            }
        }
    }
        

خوشه بندی نشانگرها

            @Composable
    private fun NeshanMapView(context: Context) {

        Surface() {
            Row() {
                Column() {
                    AndroidView(
                        factory = { context ->
                            MapView(context).apply {
                                settings.isMarkerClusteringEnabled = true
                            }
                        },
                        update = {

                        },
                        modifier = Modifier.weight(1f),
                    )
                }
            }
        }
    }
        

متدهای کار با دوربین

جهت آشنایی با متدهای کار با دوربین روی نقشه میتوانید به صفحه متدهای کار با دوربین مراجعه نمایید.

در ادامه نحوه پیاده سازی این متدها در Jetpack Compose را شرح میدهیم:

متد moveToCameraBounds

            @Composable
    private fun NeshanMapView(context: Context) {

        var moveToCameraBoundsNorthEast: LatLng? = null
        var moveToCameraBoundsSouthWest: LatLng? = null
        var moveToCameraBoundsintegerZoom: Boolean? = null
        var moveToCameraBoundsDuration: Float? = null

        val moveToCameraBoundsNorthEastRemember =
            remember { mutableStateOf(moveToCameraBoundsNorthEast) }
        val moveToCameraBoundsSouthWestRemember =
            remember { mutableStateOf(moveToCameraBoundsSouthWest) }
        val moveToCameraBoundsIntegerZoomRemember =
            remember { mutableStateOf(moveToCameraBoundsintegerZoom) }
        val moveToCameraBoundsDurationRemember =
            remember { mutableStateOf(moveToCameraBoundsDuration) }

        var moveCameraLatLng: LatLng? = null
        var moveCameraDuration: Float? = null

        val moveCameraRemember = remember { mutableStateOf(moveCameraLatLng) }
        val moveCameraDurationRemember = remember { mutableStateOf(moveCameraDuration) }

        Surface() {
            Row() {
                Column() {
                    AndroidView(
                        factory = { context ->
                            MapView(context) as View
                        },
                        update = {
                            if (moveCameraRemember.value != null && moveCameraDurationRemember.value != null) {
                                it.moveCamera(
                                    moveCameraRemember.value,
                                    moveCameraDurationRemember.value!!
                                )
                            }
                            if (moveToCameraBoundsNorthEastRemember.value != null
                                && moveToCameraBoundsSouthWestRemember.value != null
                                && moveToCameraBoundsIntegerZoomRemember.value != null
                                && moveToCameraBoundsDurationRemember.value != null
                            ) {

                                it.moveToCameraBounds(
                                    LatLngBounds(
                                        moveToCameraBoundsNorthEastRemember.value,
                                        moveToCameraBoundsSouthWestRemember.value
                                    ),
                                    ScreenBounds(
                                        ScreenPos(0f, 0f),
                                        ScreenPos(it.width.toFloat(), it.height.toFloat())
                                    ),
                                    moveToCameraBoundsIntegerZoomRemember.value!!,
                                    moveToCameraBoundsDurationRemember.value!!
                                )
                            }
                        },
                        modifier = Modifier.weight(1f),
                    )
                    ElevatedButton(
                        onClick = {
                            moveCameraLatLng = LatLng(36.316203, 59.576074)
                            moveCameraDuration = .5f

                            moveCameraRemember.value = moveCameraLatLng
                            moveCameraDurationRemember.value = moveCameraDuration
                        }
                    ) {
                        Text("Move camera")
                    }
                    ElevatedButton(
                        onClick = {
                            moveToCameraBoundsNorthEast =
                                LatLng(35.809174301661336, 51.53005019022984)
                            moveToCameraBoundsSouthWest =
                                LatLng(35.61431826413479, 51.137353382540034)
                            moveToCameraBoundsintegerZoom = false
                            moveToCameraBoundsDuration = .5f

                            moveToCameraBoundsNorthEastRemember.value = moveToCameraBoundsNorthEast
                            moveToCameraBoundsSouthWestRemember.value = moveToCameraBoundsSouthWest
                            moveToCameraBoundsDurationRemember.value = moveToCameraBoundsDuration
                            moveToCameraBoundsIntegerZoomRemember.value =
                                moveToCameraBoundsintegerZoom
                        }
                    ) {
                        Text("MoveToCameraBounds method test")
                    }
                }
            }
        }
    }
        

متد moveCamera

            @Composable
    private fun NeshanMapView(context: Context) {

        var moveCameraLatLng: LatLng? = null
        var moveCameraDuration: Float? = null

        val moveCameraRemember = remember { mutableStateOf(moveCameraLatLng) }
        val moveCameraDurationRemember = remember { mutableStateOf(moveCameraDuration) }

        Surface() {
            Row() {
                Column() {
                    AndroidView(
                        factory = { context ->
                            MapView(context) as View
                        },
                        update = {
                            if (moveCameraRemember.value != null && moveCameraDurationRemember.value != null) {
                                it.moveCamera(
                                    moveCameraRemember.value,
                                    moveCameraDurationRemember.value!!
                                )
                            }
                        },
                        modifier = Modifier.weight(1f),
                    )
                    ElevatedButton(
                        onClick = {
                            moveCameraLatLng = LatLng(35.701519, 51.337244)
                            moveCameraDuration = .5f

                            moveCameraRemember.value = moveCameraLatLng
                            moveCameraDurationRemember.value = moveCameraDuration
                        }
                    ) {
                        Text("Move camera")
                    }
                }
            }
        }
    }
        

متغیر cameraTargetPosition

برای دسترسی به نقطه وسط نقشه نیز میتوانید در بلوک های factory و update به صورت زیر دسترسی داشته باشید:

            @Composable
    private fun NeshanMapView(context: Context) {

        Surface() {
            Row() {
                Column() {
                    AndroidView(
                        factory = { context ->
                            MapView(context).apply{
                                cameraTargetPosition
                            }
                        },
                        update = {
                            it.cameraTargetPosition
                        },
                        modifier = Modifier.weight(1f),
                    )
                }
            }
        }
    }
        

لیسنرها

setOnCameraMoveListener

setOnCameraMoveStartListener

setOnCameraMoveFinishedListener

            @Composable
    private fun NeshanMapView(context: Context) {

        Surface() {
            Row() {
                Column() {
                    AndroidView(
                        factory = { context ->
                            MapView(context).apply{
                                setOnCameraMoveListener {
                                    //Do someting...
                                }
                                setOnCameraMoveStartListener { 
                                    //Do something
                                }
                                setOnCameraMoveFinishedListener { 
                                    //Do something
                                }
                            }
                        },
                        update = {

                        },
                        modifier = Modifier.weight(1f),
                    )
                }
            }
        }
    }
        

setOnMarkerClickListener

setOnCircleClickListener

setOnPolygonClickListener

setOnPolylineClickListener

            @Composable
    private fun NeshanMapView(context: Context) {

        Surface() {
            Row() {
                Column() {
                    AndroidView(
                        factory = { context ->
                            MapView(context).apply {
                                setOnMarkerClickListener {
                                    //Do something
                                }
                                setOnPolylineClickListener {
                                    //Do something
                                }
                                setOnPolygonClickListener {
                                    //Do something
                                }
                                setOnCircleClickListener {
                                    //Do something
                                }
                            }
                        },
                        update = {

                        },
                        modifier = Modifier.weight(1f),
                    )
                }
            }
        }
    }