feat: Add multi-map support with Google, Baidu, and Gaode providers
- Implement MapProvider interface with Google, Baidu, Gaode map providers - Support satellite and normal map types for all providers - Add coordinate conversion (WGS84 to BD09/GCJ02) - Add tab-based map type and provider switching UI - Add map settings with default provider and type configuration - Add location button widget - Add webview-based Baidu and Gaode map HTML assets
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<title>°Ù¶ÈµØÍ¼</title>
|
||||
<style type="text/css">
|
||||
body, html, #container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
margin: 0;
|
||||
font-family: "΢ÈíÑźÚ";
|
||||
}
|
||||
</style>
|
||||
<script type="text/javascript" src="//api.map.baidu.com/api?v=3.0&ak=YOUR_BAIDU_AK"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="container"></div>
|
||||
<script type="text/javascript">
|
||||
var map = new BMap.Map("container");
|
||||
var point = new BMap.Point(116.404, 39.915);
|
||||
map.centerAndZoom(point, 15);
|
||||
map.enableScrollWheelZoom(true);
|
||||
map.addControl(new BMap.NavigationControl());
|
||||
map.addControl(new BMap.ScaleControl());
|
||||
map.addControl(new BMap.MapTypeControl());
|
||||
|
||||
var polyline = null;
|
||||
|
||||
function setCenter(lat, lng) {
|
||||
var point = new BMap.Point(lng, lat);
|
||||
map.centerAndZoom(point, 17);
|
||||
var marker = new BMap.Marker(point);
|
||||
map.clearOverlays();
|
||||
map.addOverlay(marker);
|
||||
}
|
||||
|
||||
function setMapType(type) {
|
||||
if (type === 'satellite') {
|
||||
map.setMapType(BMAP_SATELLITE_MAP);
|
||||
} else if (type === 'hybrid') {
|
||||
map.setMapType(BMAP_HYBRID_MAP);
|
||||
} else {
|
||||
map.setMapType(BMAP_NORMAL_MAP);
|
||||
}
|
||||
}
|
||||
|
||||
function drawTrack(points) {
|
||||
if (polyline) {
|
||||
map.removeOverlay(polyline);
|
||||
}
|
||||
if (points.length > 0) {
|
||||
var bmapPoints = points.map(function(p) {
|
||||
return new BMap.Point(p[1], p[0]);
|
||||
});
|
||||
polyline = new BMap.Polyline(bmapPoints, {strokeColor:"#1E90FF", strokeWeight:5, strokeOpacity:0.8});
|
||||
map.addOverlay(polyline);
|
||||
map.setViewport(bmapPoints);
|
||||
}
|
||||
}
|
||||
|
||||
function clearTrack() {
|
||||
if (polyline) {
|
||||
map.removeOverlay(polyline);
|
||||
polyline = null;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,77 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<title>¸ßµÂµØÍ¼</title>
|
||||
<style type="text/css">
|
||||
body, html, #container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
margin: 0;
|
||||
font-family: "΢ÈíÑźÚ";
|
||||
}
|
||||
</style>
|
||||
<script type="text/javascript" src="https://webapi.amap.com/maps?v=1.4.15&key=YOUR_GAODE_KEY"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="container"></div>
|
||||
<script type="text/javascript">
|
||||
var map = new AMap.Map('container', {
|
||||
zoom: 15,
|
||||
center: [116.397428, 39.90923],
|
||||
pitch: 0,
|
||||
viewMode: '3D'
|
||||
});
|
||||
map.addControl(new AMap.ToolBar());
|
||||
map.addControl(new AMap.Scale());
|
||||
map.addControl(new AMap.MapType());
|
||||
|
||||
var polyline = null;
|
||||
|
||||
function setCenter(lat, lng) {
|
||||
map.setZoomAndCenter(17, [lng, lat]);
|
||||
map.clearMap();
|
||||
var marker = new AMap.Marker({
|
||||
position: [lng, lat]
|
||||
});
|
||||
map.add(marker);
|
||||
}
|
||||
|
||||
function setMapType(type) {
|
||||
if (type === 'satellite') {
|
||||
map.setMapType('satellite');
|
||||
} else {
|
||||
map.setMapType('standard');
|
||||
}
|
||||
}
|
||||
|
||||
function drawTrack(points) {
|
||||
if (polyline) {
|
||||
map.remove(polyline);
|
||||
}
|
||||
if (points.length > 0) {
|
||||
var amapPoints = points.map(function(p) {
|
||||
return [p[1], p[0]];
|
||||
});
|
||||
polyline = new AMap.Polyline({
|
||||
path: amapPoints,
|
||||
strokeColor: '#1E90FF',
|
||||
strokeWeight: 5,
|
||||
strokeOpacity: 0.8
|
||||
});
|
||||
map.add(polyline);
|
||||
map.setFitView();
|
||||
}
|
||||
}
|
||||
|
||||
function clearTrack() {
|
||||
if (polyline) {
|
||||
map.remove(polyline);
|
||||
polyline = null;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,9 +1,13 @@
|
||||
package com.senstools.domain.model
|
||||
|
||||
import com.senstools.presentation.ui.map.provider.MapType
|
||||
|
||||
data class AppSettings(
|
||||
val gpsEnabled: Boolean = true,
|
||||
val sensorEnabled: Boolean = true,
|
||||
val mapEnabled: Boolean = true,
|
||||
val gpsRate: Int = 5,
|
||||
val sensorRate: Int = 100
|
||||
val sensorRate: Int = 100,
|
||||
val defaultMapProvider: String = "Google",
|
||||
val defaultMapType: MapType = MapType.GOOGLE_NORMAL
|
||||
)
|
||||
|
||||
@@ -6,22 +6,35 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.viewModels
|
||||
import com.google.android.gms.maps.CameraUpdateFactory
|
||||
import com.google.android.gms.maps.GoogleMap
|
||||
import com.google.android.gms.maps.OnMapReadyCallback
|
||||
import com.google.android.gms.maps.SupportMapFragment
|
||||
import com.google.android.gms.maps.model.LatLng
|
||||
import com.google.android.gms.maps.model.PolylineOptions
|
||||
import com.google.android.material.tabs.TabLayout
|
||||
import com.senstools.databinding.FragmentMapBinding
|
||||
import com.senstools.presentation.ui.map.provider.*
|
||||
import com.senstools.presentation.viewmodel.MapViewModel
|
||||
|
||||
class MapFragment : Fragment(), OnMapReadyCallback {
|
||||
class MapFragment : Fragment() {
|
||||
|
||||
private var _binding: FragmentMapBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
|
||||
private val viewModel: MapViewModel by viewModels()
|
||||
private var googleMap: GoogleMap? = null
|
||||
private var currentProvider: MapProvider? = null
|
||||
|
||||
private val providers = mutableMapOf<String, MapProvider>()
|
||||
private val googleTypes = listOf(
|
||||
MapType.GOOGLE_NORMAL,
|
||||
MapType.GOOGLE_SATELLITE,
|
||||
MapType.GOOGLE_TERRAIN,
|
||||
MapType.GOOGLE_HYBRID
|
||||
)
|
||||
private val baiduTypes = listOf(
|
||||
MapType.BAIDU_NORMAL,
|
||||
MapType.BAIDU_SATELLITE,
|
||||
MapType.BAIDU_HYBRID
|
||||
)
|
||||
private val gaodeTypes = listOf(
|
||||
MapType.GAODE_NORMAL,
|
||||
MapType.GAODE_SATELLITE
|
||||
)
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
@@ -34,41 +47,117 @@ class MapFragment : Fragment(), OnMapReadyCallback {
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
val mapFragment = childFragmentManager.findFragmentById(
|
||||
com.senstools.R.id.mapFragment
|
||||
) as? SupportMapFragment
|
||||
mapFragment?.getMapAsync(this)
|
||||
setupProviders()
|
||||
setupTabs()
|
||||
setupObservers()
|
||||
}
|
||||
|
||||
override fun onMapReady(map: GoogleMap) {
|
||||
googleMap = map
|
||||
map.uiSettings.apply {
|
||||
isZoomControlsEnabled = true
|
||||
isMyLocationButtonEnabled = true
|
||||
private fun setupProviders() {
|
||||
providers["Google"] = GoogleMapProvider()
|
||||
providers["°Ù¶È"] = BaiduMapProvider()
|
||||
providers["¸ßµÂ"] = GaodeMapProvider()
|
||||
}
|
||||
|
||||
private fun setupTabs() {
|
||||
binding.tabMapProvider.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
|
||||
override fun onTabSelected(tab: TabLayout.Tab?) {
|
||||
val providerName = tab?.text.toString()
|
||||
switchProvider(providerName)
|
||||
}
|
||||
|
||||
override fun onTabUnselected(tab: TabLayout.Tab?) {}
|
||||
override fun onTabReselected(tab: TabLayout.Tab?) {}
|
||||
})
|
||||
|
||||
binding.tabMapType.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
|
||||
override fun onTabSelected(tab: TabLayout.Tab?) {
|
||||
val position = tab?.position ?: 0
|
||||
switchMapType(position)
|
||||
}
|
||||
|
||||
override fun onTabUnselected(tab: TabLayout.Tab?) {}
|
||||
override fun onTabReselected(tab: TabLayout.Tab?) {}
|
||||
})
|
||||
|
||||
providers.keys.forEach { providerName ->
|
||||
binding.tabMapProvider.addTab(binding.tabMapProvider.newTab().setText(providerName))
|
||||
}
|
||||
|
||||
providers.keys.firstOrNull()?.let { switchProvider(it) }
|
||||
}
|
||||
|
||||
private fun switchProvider(providerName: String) {
|
||||
currentProvider?.onPause()
|
||||
|
||||
val provider = providers[providerName] ?: return
|
||||
|
||||
provider.initialize(requireContext())
|
||||
|
||||
binding.mapContainer.removeAllViews()
|
||||
binding.mapContainer.addView(provider.view)
|
||||
|
||||
currentProvider = provider
|
||||
provider.onResume()
|
||||
|
||||
updateMapTypeTabs()
|
||||
|
||||
viewModel.currentLocation.value?.let {
|
||||
provider.setLocation(it.latitude, it.longitude)
|
||||
}
|
||||
}
|
||||
|
||||
private fun typesForProvider(providerName: String): List<MapType> {
|
||||
return when (providerName) {
|
||||
"Google" -> googleTypes
|
||||
"°Ù¶È" -> baiduTypes
|
||||
"¸ßµÂ" -> gaodeTypes
|
||||
else -> googleTypes
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateMapTypeTabs() {
|
||||
binding.tabMapType.removeAllTabs()
|
||||
val types = typesForProvider(currentProvider?.name ?: "Google")
|
||||
types.forEach { type ->
|
||||
binding.tabMapType.addTab(binding.tabMapType.newTab().setText(type.displayName))
|
||||
}
|
||||
}
|
||||
|
||||
private fun switchMapType(position: Int) {
|
||||
val types = typesForProvider(currentProvider?.name ?: "Google")
|
||||
if (position in types.indices) {
|
||||
currentProvider?.setMapType(types[position])
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupObservers() {
|
||||
viewModel.currentLocation.observe(viewLifecycleOwner) { location ->
|
||||
location?.let {
|
||||
val latLng = LatLng(it.latitude, it.longitude)
|
||||
googleMap?.animateCamera(CameraUpdateFactory.newLatLngZoom(latLng, 17f))
|
||||
currentProvider?.setLocation(it.latitude, it.longitude)
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.trackPoints.observe(viewLifecycleOwner) { points ->
|
||||
googleMap?.clear()
|
||||
if (points.isNotEmpty()) {
|
||||
val polylineOptions = PolylineOptions()
|
||||
.add(*points.map { LatLng(it.latitude, it.longitude) }.toTypedArray())
|
||||
googleMap?.addPolyline(polylineOptions)
|
||||
currentProvider?.clearTrack()
|
||||
points.forEach {
|
||||
currentProvider?.addTrackPoint(it.latitude, it.longitude)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
currentProvider?.onResume()
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
currentProvider?.onPause()
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
currentProvider?.onDestroy()
|
||||
_binding = null
|
||||
}
|
||||
}
|
||||
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
package com.senstools.presentation.ui.map.provider
|
||||
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.webkit.WebChromeClient
|
||||
import android.webkit.WebView
|
||||
import android.webkit.WebViewClient
|
||||
import android.widget.FrameLayout
|
||||
|
||||
class BaiduMapProvider : MapProvider {
|
||||
|
||||
override val name = "°Ù¶È"
|
||||
override var type: MapType = MapType.BAIDU_NORMAL
|
||||
private set
|
||||
|
||||
private var webView: WebView? = null
|
||||
private var container: FrameLayout? = null
|
||||
private val trackPoints = mutableListOf<Pair<Double, Double>>()
|
||||
|
||||
override val view: View
|
||||
get() = container ?: throw IllegalStateException("Map not initialized")
|
||||
|
||||
override fun initialize(context: Context) {
|
||||
container = FrameLayout(context)
|
||||
webView = WebView(context).apply {
|
||||
settings.javaScriptEnabled = true
|
||||
settings.domStorageEnabled = true
|
||||
webViewClient = WebViewClient()
|
||||
webChromeClient = WebChromeClient()
|
||||
}
|
||||
container?.addView(webView)
|
||||
loadBaiduMap()
|
||||
}
|
||||
|
||||
private fun loadBaiduMap() {
|
||||
val mapType = when (type) {
|
||||
MapType.BAIDU_SATELLITE -> "satellite"
|
||||
MapType.BAIDU_HYBRID -> "hybrid"
|
||||
else -> "normal"
|
||||
}
|
||||
webView?.loadUrl("file:///android_asset/baidu_map.html")
|
||||
}
|
||||
|
||||
override fun setLocation(latitude: Double, longitude: Double) {
|
||||
val bdCoords = convertWGS84ToBD09(latitude, longitude)
|
||||
webView?.evaluateJavascript(
|
||||
"setCenter(${bdCoords.first}, ${bdCoords.second})",
|
||||
null
|
||||
)
|
||||
}
|
||||
|
||||
override fun addTrackPoint(latitude: Double, longitude: Double) {
|
||||
val bdCoords = convertWGS84ToBD09(latitude, longitude)
|
||||
trackPoints.add(bdCoords)
|
||||
updatePolyline()
|
||||
}
|
||||
|
||||
override fun clearTrack() {
|
||||
trackPoints.clear()
|
||||
webView?.evaluateJavascript("clearTrack()", null)
|
||||
}
|
||||
|
||||
private fun updatePolyline() {
|
||||
val pointsStr = trackPoints.joinToString(",") { "[${it.first}, ${it.second}]" }
|
||||
webView?.evaluateJavascript("drawTrack([$pointsStr])", null)
|
||||
}
|
||||
|
||||
override fun setMapType(type: MapType) {
|
||||
this.type = type
|
||||
val mapType = when (type) {
|
||||
MapType.BAIDU_SATELLITE -> "satellite"
|
||||
MapType.BAIDU_HYBRID -> "hybrid"
|
||||
else -> "normal"
|
||||
}
|
||||
webView?.evaluateJavascript("setMapType('$mapType')", null)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
webView?.onResume()
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
webView?.onPause()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
webView?.destroy()
|
||||
}
|
||||
|
||||
private fun convertWGS84ToBD09(lat: Double, lon: Double): Pair<Double, Double> {
|
||||
val pi = 3.1415926535897932384626
|
||||
val x = lon - 0.0065
|
||||
val y = lat - 0.006
|
||||
val z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * pi * 3000.0 / 180.0)
|
||||
val theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * pi * 3000.0 / 180.0)
|
||||
val bdLon = z * Math.cos(theta) + 0.0065
|
||||
val bdLat = z * Math.sin(theta) + 0.006
|
||||
return Pair(bdLat, bdLon)
|
||||
}
|
||||
}
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
package com.senstools.presentation.ui.map.provider
|
||||
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.webkit.WebChromeClient
|
||||
import android.webkit.WebView
|
||||
import android.webkit.WebViewClient
|
||||
import android.widget.FrameLayout
|
||||
|
||||
class GaodeMapProvider : MapProvider {
|
||||
|
||||
override val name = "¸ßµÂ"
|
||||
override var type: MapType = MapType.GAODE_NORMAL
|
||||
private set
|
||||
|
||||
private var webView: WebView? = null
|
||||
private var container: FrameLayout? = null
|
||||
private val trackPoints = mutableListOf<Pair<Double, Double>>()
|
||||
|
||||
override val view: View
|
||||
get() = container ?: throw IllegalStateException("Map not initialized")
|
||||
|
||||
override fun initialize(context: Context) {
|
||||
container = FrameLayout(context)
|
||||
webView = WebView(context).apply {
|
||||
settings.javaScriptEnabled = true
|
||||
settings.domStorageEnabled = true
|
||||
webViewClient = WebViewClient()
|
||||
webChromeClient = WebChromeClient()
|
||||
}
|
||||
container?.addView(webView)
|
||||
loadGaodeMap()
|
||||
}
|
||||
|
||||
private fun loadGaodeMap() {
|
||||
webView?.loadUrl("file:///android_asset/gaode_map.html")
|
||||
}
|
||||
|
||||
override fun setLocation(latitude: Double, longitude: Double) {
|
||||
val gdCoords = convertWGS84ToGCJ02(latitude, longitude)
|
||||
webView?.evaluateJavascript(
|
||||
"setCenter(${gdCoords.first}, ${gdCoords.second})",
|
||||
null
|
||||
)
|
||||
}
|
||||
|
||||
override fun addTrackPoint(latitude: Double, longitude: Double) {
|
||||
val gdCoords = convertWGS84ToGCJ02(latitude, longitude)
|
||||
trackPoints.add(gdCoords)
|
||||
updatePolyline()
|
||||
}
|
||||
|
||||
override fun clearTrack() {
|
||||
trackPoints.clear()
|
||||
webView?.evaluateJavascript("clearTrack()", null)
|
||||
}
|
||||
|
||||
private fun updatePolyline() {
|
||||
val pointsStr = trackPoints.joinToString(",") { "[${it.first}, ${it.second}]" }
|
||||
webView?.evaluateJavascript("drawTrack([$pointsStr])", null)
|
||||
}
|
||||
|
||||
override fun setMapType(type: MapType) {
|
||||
this.type = type
|
||||
val mapType = when (type) {
|
||||
MapType.GAODE_SATELLITE -> "satellite"
|
||||
else -> "normal"
|
||||
}
|
||||
webView?.evaluateJavascript("setMapType('$mapType')", null)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
webView?.onResume()
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
webView?.onPause()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
webView?.destroy()
|
||||
}
|
||||
|
||||
private fun convertWGS84ToGCJ02(lat: Double, lon: Double): Pair<Double, Double> {
|
||||
val pi = 3.1415926535897932384626
|
||||
val a = 6378245.0
|
||||
val ee = 0.00669342162296594997
|
||||
val (dLat, dLon) = transform(lat, lon)
|
||||
val radLat = lat / 180.0 * pi
|
||||
var magic = Math.sin(radLat)
|
||||
magic = 1 - ee * magic * magic
|
||||
val sqrtMagic = Math.sqrt(magic)
|
||||
val mgLat = lat + (dLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * pi)
|
||||
val mgLon = lon + (dLon * 180.0) / (a / sqrtMagic * Math.cos(radLat) * pi)
|
||||
return Pair(mgLat, mgLon)
|
||||
}
|
||||
|
||||
private fun transform(lat: Double, lon: Double): Pair<Double, Double> {
|
||||
var x = lon - 105.0
|
||||
var y = lat - 35.0
|
||||
var dLat = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * Math.sqrt(Math.abs(x))
|
||||
dLat += (20.0 * Math.sin(6.0 * x * Math.PI) + (20.0 * Math.sin(2.0 * x * Math.PI)) * 2.0 / 3.0)
|
||||
dLat += (20.0 * Math.sin(y * Math.PI) + (40.0 * Math.sin(y / 3.0 * Math.PI)) * 2.0 / 3.0)
|
||||
dLat += (160.0 * Math.sin(y / 12.0 * Math.PI) + (320 * Math.sin(y * Math.PI / 30.0)) * 2.0 / 3.0)
|
||||
|
||||
var dLon = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * Math.sqrt(Math.abs(x))
|
||||
dLon += (20.0 * Math.sin(6.0 * x * Math.PI) + (20.0 * Math.sin(2.0 * x * Math.PI)) * 2.0 / 3.0)
|
||||
dLon += (20.0 * Math.sin(x * Math.PI) + (40.0 * Math.sin(x / 3.0 * Math.PI)) * 2.0 / 3.0)
|
||||
dLon += (150.0 * Math.sin(x / 12.0 * Math.PI) + (300.0 * Math.sin(x / 30.0 * Math.PI)) * 2.0 / 3.0)
|
||||
return Pair(dLat, dLon)
|
||||
}
|
||||
}
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
package com.senstools.presentation.ui.map.provider
|
||||
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.widget.FrameLayout
|
||||
import com.google.android.gms.maps.CameraUpdateFactory
|
||||
import com.google.android.gms.maps.GoogleMap
|
||||
import com.google.android.gms.maps.MapView
|
||||
import com.google.android.gms.maps.OnMapReadyCallback
|
||||
import com.google.android.gms.maps.model.LatLng
|
||||
import com.google.android.gms.maps.model.Polyline
|
||||
import com.google.android.gms.maps.model.PolylineOptions
|
||||
|
||||
class GoogleMapProvider : MapProvider, OnMapReadyCallback {
|
||||
|
||||
override val name = "Google"
|
||||
override var type: MapType = MapType.GOOGLE_NORMAL
|
||||
private set
|
||||
|
||||
private var googleMap: GoogleMap? = null
|
||||
private var mapView: MapView? = null
|
||||
private var trackPolyline: Polyline? = null
|
||||
private val trackPoints = mutableListOf<LatLng>()
|
||||
private var container: FrameLayout? = null
|
||||
|
||||
override val view: View
|
||||
get() = mapView ?: throw IllegalStateException("Map not initialized")
|
||||
|
||||
override fun initialize(context: Context) {
|
||||
container = FrameLayout(context)
|
||||
mapView = MapView(context)
|
||||
container?.addView(mapView)
|
||||
mapView?.onCreate(null)
|
||||
mapView?.getMapAsync(this)
|
||||
}
|
||||
|
||||
override fun onMapReady(map: GoogleMap) {
|
||||
googleMap = map
|
||||
map.uiSettings.apply {
|
||||
isZoomControlsEnabled = true
|
||||
isMyLocationButtonEnabled = true
|
||||
isCompassEnabled = true
|
||||
}
|
||||
applyMapType()
|
||||
}
|
||||
|
||||
private fun applyMapType() {
|
||||
setMapType(type)
|
||||
}
|
||||
|
||||
override fun setLocation(latitude: Double, longitude: Double) {
|
||||
val latLng = LatLng(latitude, longitude)
|
||||
googleMap?.animateCamera(CameraUpdateFactory.newLatLngZoom(latLng, 17f))
|
||||
}
|
||||
|
||||
override fun addTrackPoint(latitude: Double, longitude: Double) {
|
||||
trackPoints.add(LatLng(latitude, longitude))
|
||||
updatePolyline()
|
||||
}
|
||||
|
||||
override fun clearTrack() {
|
||||
trackPoints.clear()
|
||||
trackPolyline?.remove()
|
||||
trackPolyline = null
|
||||
}
|
||||
|
||||
private fun updatePolyline() {
|
||||
trackPolyline?.remove()
|
||||
if (trackPoints.isNotEmpty()) {
|
||||
val polylineOptions = PolylineOptions()
|
||||
.addAll(trackPoints)
|
||||
.color(0xFF1E90FF.toInt())
|
||||
.width(5f)
|
||||
trackPolyline = googleMap?.addPolyline(polylineOptions)
|
||||
}
|
||||
}
|
||||
|
||||
override fun setMapType(type: MapType) {
|
||||
this.type = type
|
||||
googleMap?.mapType = when (type) {
|
||||
MapType.GOOGLE_NORMAL -> GoogleMap.MAP_TYPE_NORMAL
|
||||
MapType.GOOGLE_SATELLITE -> GoogleMap.MAP_TYPE_SATELLITE
|
||||
MapType.GOOGLE_TERRAIN -> GoogleMap.MAP_TYPE_TERRAIN
|
||||
MapType.GOOGLE_HYBRID -> GoogleMap.MAP_TYPE_HYBRID
|
||||
else -> GoogleMap.MAP_TYPE_NORMAL
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
mapView?.onResume()
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
mapView?.onPause()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
mapView?.onDestroy()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.senstools.presentation.ui.map.provider
|
||||
|
||||
import android.content.Context
|
||||
import android.location.Location
|
||||
import android.view.View
|
||||
import androidx.annotation.ColorInt
|
||||
|
||||
interface MapProvider {
|
||||
val name: String
|
||||
val type: MapType
|
||||
val view: View
|
||||
|
||||
fun initialize(context: Context)
|
||||
fun setLocation(latitude: Double, longitude: Double)
|
||||
fun addTrackPoint(latitude: Double, longitude: Double)
|
||||
fun clearTrack()
|
||||
fun setMapType(type: MapType)
|
||||
fun onResume()
|
||||
fun onPause()
|
||||
fun onDestroy()
|
||||
}
|
||||
|
||||
enum class MapType(val displayName: String) {
|
||||
GOOGLE_NORMAL("Google 标准"),
|
||||
GOOGLE_SATELLITE("Google 卫星"),
|
||||
GOOGLE_TERRAIN("Google 地形"),
|
||||
GOOGLE_HYBRID("Google 混合"),
|
||||
BAIDU_NORMAL("百度 标准"),
|
||||
BAIDU_SATELLITE("百度 卫星"),
|
||||
BAIDU_HYBRID("百度 混合"),
|
||||
GAODE_NORMAL("高德 标准"),
|
||||
GAODE_SATELLITE("高德 卫星")
|
||||
}
|
||||
@@ -4,9 +4,11 @@ import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ArrayAdapter
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.viewModels
|
||||
import com.senstools.databinding.FragmentSettingsBinding
|
||||
import com.senstools.presentation.ui.map.provider.MapType
|
||||
import com.senstools.presentation.viewmodel.SettingsViewModel
|
||||
|
||||
class SettingsFragment : Fragment() {
|
||||
@@ -16,6 +18,9 @@ class SettingsFragment : Fragment() {
|
||||
|
||||
private val viewModel: SettingsViewModel by viewModels()
|
||||
|
||||
private val mapProviders = listOf("Google", "°Ù¶È", "¸ßµÂ")
|
||||
private val allMapTypes = MapType.values().map { it.displayName }
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
@@ -27,10 +32,27 @@ class SettingsFragment : Fragment() {
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
setupSpinners()
|
||||
setupObservers()
|
||||
setupListeners()
|
||||
}
|
||||
|
||||
private fun setupSpinners() {
|
||||
val providerAdapter = ArrayAdapter(
|
||||
requireContext(),
|
||||
android.R.layout.simple_dropdown_item_1line,
|
||||
mapProviders
|
||||
)
|
||||
binding.tvDefaultMapProvider.setAdapter(providerAdapter)
|
||||
|
||||
val typeAdapter = ArrayAdapter(
|
||||
requireContext(),
|
||||
android.R.layout.simple_dropdown_item_1line,
|
||||
allMapTypes
|
||||
)
|
||||
binding.tvDefaultMapType.setAdapter(typeAdapter)
|
||||
}
|
||||
|
||||
private fun setupObservers() {
|
||||
viewModel.settings.observe(viewLifecycleOwner) { settings ->
|
||||
binding.apply {
|
||||
@@ -39,6 +61,8 @@ class SettingsFragment : Fragment() {
|
||||
switchMap.isChecked = settings.mapEnabled
|
||||
sliderGpsRate.value = settings.gpsRate.toFloat()
|
||||
sliderSensorRate.value = settings.sensorRate.toFloat()
|
||||
tvDefaultMapProvider.setText(settings.defaultMapProvider, false)
|
||||
tvDefaultMapType.setText(settings.defaultMapType.displayName, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -60,6 +84,13 @@ class SettingsFragment : Fragment() {
|
||||
sliderSensorRate.addOnChangeListener { _, value, _ ->
|
||||
viewModel.setSensorRate(value.toInt())
|
||||
}
|
||||
tvDefaultMapProvider.setOnItemClickListener { _, _, position, _ ->
|
||||
viewModel.setDefaultMapProvider(mapProviders[position])
|
||||
}
|
||||
tvDefaultMapType.setOnItemClickListener { _, _, position, _ ->
|
||||
val selectedType = MapType.values().find { it.displayName == allMapTypes[position] }
|
||||
selectedType?.let { viewModel.setDefaultMapType(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.senstools.domain.model.AppSettings
|
||||
import com.senstools.presentation.ui.map.provider.MapType
|
||||
|
||||
class SettingsViewModel : ViewModel() {
|
||||
|
||||
@@ -37,4 +38,12 @@ class SettingsViewModel : ViewModel() {
|
||||
fun setSensorRate(rate: Int) {
|
||||
_settings.value = _settings.value?.copy(sensorRate = rate)
|
||||
}
|
||||
|
||||
fun setDefaultMapProvider(provider: String) {
|
||||
_settings.value = _settings.value?.copy(defaultMapProvider = provider)
|
||||
}
|
||||
|
||||
fun setDefaultMapType(mapType: MapType) {
|
||||
_settings.value = _settings.value?.copy(defaultMapType = mapType)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M12,8c-2.21,0 -4,1.79 -4,4s1.79,4 4,4 4,-1.79 4,-4 -1.79,-4 -4,-4zM12,14c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,18c-3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6 6,2.69 6,6 -2.69,6 -6,6z"/>
|
||||
</vector>
|
||||
@@ -1,12 +1,55 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<androidx.constraintlayout.widget.ConstraintLayout 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">
|
||||
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/mapFragment"
|
||||
android:name="com.google.android.gms.maps.SupportMapFragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
<FrameLayout
|
||||
android:id="@+id/mapContainer"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/mapTypeSelector" />
|
||||
|
||||
</FrameLayout>
|
||||
<LinearLayout
|
||||
android:id="@+id/mapTypeSelector"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:padding="8dp"
|
||||
android:background="@android:color/white"
|
||||
android:elevation="4dp"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent">
|
||||
|
||||
<com.google.android.material.tabs.TabLayout
|
||||
android:id="@+id/tabMapProvider"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
app:tabMode="scrollable"
|
||||
app:tabGravity="start" />
|
||||
|
||||
<com.google.android.material.tabs.TabLayout
|
||||
android:id="@+id/tabMapType"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
app:tabMode="scrollable"
|
||||
app:tabGravity="start" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/fabMyLocation"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:src="@drawable/ic_my_location"
|
||||
android:layout_margin="16dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
@@ -139,6 +139,44 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="Default Map Provider" />
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp">
|
||||
|
||||
<com.google.android.material.textfield.AutoCompleteTextView
|
||||
android:id="@+id/tvDefaultMapProvider"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="none"
|
||||
android:hint="Select Provider" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="Default Map Type" />
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp">
|
||||
|
||||
<com.google.android.material.textfield.AutoCompleteTextView
|
||||
android:id="@+id/tvDefaultMapType"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="none"
|
||||
android:hint="Select Map Type" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
</LinearLayout>
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user