当今世界最大的地图 sdk 应该是 google 地图, 但是由于国内墙掉了 google play service, 国内是无法使用 google 地图的, 然而国内比较热门的地图 sdk 是高德地图和百度地图.(如果你是 iOS, 还有自带的地图)
近来项目中需要世界地图, 所以特此做了一个高德地图和 google 地图兼容的模块了.
Sdk 接入
1.google 地图, 接入相对比较简单, 当然因为 Android 本身就是 google 亲儿子的原因.
需要引入 google service 的 sdk, 以及 google map 的 sdk
2. 高德地图接入相对比较复杂一点, 可以选择 2d,3d, 定位, 搜索多种模块去接入地图.
然后需要申请账号, 随便邮箱手机号就可以了, 通过 keytools 命令提出 keystore 的 sha1 值, 包名和
sha1 值相互绑定的, 每次请求都会验证.
然后配置 AndroidManifest 中的 meta-data.
预览模块
地图选择 UI
1. 高德地图是通过 sdk 提供的 com.amap.API.maps2d.MapView 自定义地图 View 来实现的.
google 地图是通过 sdk 提供一个 Fragment 空间来实现地图获取
- <fragment xmlns:Android="http://schemas.android.com/apk/res/android"
- Android:id="@+id/google_map"
- Android:name="com.google.android.gms.maps.SupportMapFragment"
- Android:layout_width="match_parent"
- Android:layout_height="match_parent"/>
- mapFragment = childFragmentManager.findFragmentById(R.id.google_map) as SupportMapFragment
2. 地图预览
地图预览 (不小心透露了我的工作地址).PNG
Google 地图和高德地图接口相关的名字都是差不多的, 比较常用的接口
moveCamera 视窗转移 缩放级别分为 1~17 级, 数值越大地图越精准
addMarker 添加地图标签
google 地图是使用 getMapAysnc, 会有 onMapReady 的接口回调
- mapFragment?.getMapAsync(this)
- /**
- * 地图就绪
- */
- override fun onMapReady(googleMap: GoogleMap?) {
- googleMap ?: return
- with(googleMap) {
- val latLng = LatLng(latitude, longitude)
- // 视觉转移
- moveCamera(CameraUpdateFactory.newLatLngZoom(latLng, 16f))
- // 添加坐标
- addMarker(MarkerOptions().position(latLng))
- }
- }
高德地图使用 setOnMapLoadedListener 方法来设置回调
- aMap?.setOnMapLoadedListener {
- // 视觉移动
- aMap?.moveCamera(CameraUpdateFactory.newLatLngZoom(LatLng(latitude, longitude), 100f))
- // 添加坐标
- aMap?.addMarker(MarkerOptions().anchor(0.5f, 0.5f)
- .icon(BitmapDescriptorFactory
- .fromBitmap(BitmapFactory.decodeResource(
- resources, R.drawable.common_drag_location)))
- .position(LatLng(latitude, longitude)))
- }
如果不想地图的坐标和视觉点显示居中怎么办?
需要将布局中 margin 上着手, 如果想要往上移, 就需要将 marginTop 设置为负值, 这样地图中心点就会上移动, 并且视觉点也会和中心点一样上移.
定位模块
定位预览图. PNG
1. 高德提供了 AMapLocationListener 作为专为提供高德坐标的监听
- private fun setUpMap() {
- myLocationStyle = MyLocationStyle()
- myLocationStyle?.strokeColor(Color.argb(0, 0, 0, 0))// 设置圆形的边框颜色
- myLocationStyle?.radiusFillColor(Color.argb(0, 0, 0, 0))// 设置圆形的填充颜色
- myLocationStyle?.myLocationIcon(BitmapDescriptorFactory.fromResource(R.drawable.common_self_location)) // 显示自身定位坐标
- aMap?.setMyLocationStyle(myLocationStyle)
- aMap?.isMyLocationEnabled = true// 设置为 true 表示显示定位层并可触发定位, false 表示隐藏定位层并不可触发定位, 默认是 false
- val uriSettings = aMap?.uiSettings
- uriSettings?.isZoomControlsEnabled = false // 关掉缩放键
- }
- private fun initLoc() {
- // 初始化定位
- mLocationClient = AMapLocationClient(context!!.applicationContext)
- // 设置定位回调监听
- mLocationClient?.setLocationListener(this)
- // 初始化定位参数
- mLocationOption = AMapLocationClientOption()
- // 设置定位模式为高精度模式, Battery_Saving 为低功耗模式, Device_Sensors 是仅设备模式
- mLocationOption?.locationMode = AMapLocationClientOption.AMapLocationMode.Hight_Accuracy
- // 设置是否返回地址信息 (默认返回地址信息)
- mLocationOption?.isNeedAddress = true
- // 设置是否只定位一次, 默认为 false
- mLocationOption?.isOnceLocation = false
- // 设置是否强制刷新 Wi-Fi, 默认为强制刷新
- mLocationOption?.isWifiActiveScan = false
- // 设置是否允许模拟位置, 默认为 false, 不允许模拟位置
- mLocationOption?.isMockEnable = false
- // 设置定位间隔, 单位毫秒, 默认为 3000ms
- mLocationOption?.interval = (3000)
- // 给定位客户端对象设置定位参数
- mLocationClient?.setLocationOption(mLocationOption)
- // 启动定位
- mLocationClient?.startLocation()
- }
- override fun onLocationChanged(amapLocation: AMapLocation?) {
- // 监听实时定位
- }
google 的定位是使用 Android 原生的 Location 定位
- locationManager = context!!.getSystemService(Context.LOCATION_SERVICE) as LocationManager
- locationManager?.requestLocationUpdates(LocationManager.GPS_PROVIDER, 3000, 10f, this)
- locationManager?.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 3000, 10f, this)
- /**
- * 地图就绪
- */
- override fun onMapReady(googleMap: GoogleMap?) {
- googleMap ?: return
- this.googleMap = googleMap
- with(googleMap.uiSettings) {
- isZoomGesturesEnabled = true
- isMyLocationButtonEnabled = true
- isScrollGesturesEnabled = true
- }
- try {
- googleMap.isMyLocationEnabled = true
- } catch (e: SecurityException) {
- ALog.e(TAG, e)
- }
- }
- /**
- * 定位更新
- */
- override fun onLocationChanged(location: Location?) {
- }
搜索模块
搜索结果. PNG
1.google 搜索有两种方式, 一种是通过 webapi 来搜索出附近相关的地点 (这里使用了 RxVolley 的框架), 这个的好处关联结果比较多.
这里不用 Uri.Builder 的拼接方式是因为其指定了 Utf-8 的格式转换将会出现 "," 强转为 "%" 号
- val googlePlaceUrl = "https://maps.googleapis.com/maps/api/place/nearbysearch/json"
- fun getGoogleNearByPlaces(latitude: Double, longitude: Double, radius: Int): Observable<GoogleLocation> {
- val builder = StringBuilder(googlePlaceUrl)
- builder.append("?location=").append(latitude.toString()).append(",").append(longitude.toString())
- builder.append("&radius=").append(radius.toString())
- builder.append("&key=").append(googlePlaceKey)
- return RxVolley.get<GoogleLocation>(builder.toString(), null, object : TypeToken<GoogleLocation>() {}.type)
- }
第二种是文字关联搜索 (地点自动完成),google 提供了一个自定义的 Fragment, 但是如果你有高级定制, 不用 AutoCompleteTextView, 那就需要通过定义一个 Adapter 来获取相关内容.(搜索结果比较少)
这边是使用了需要高级定义搜索, 所以使用了 Adapter 的形式.
- override fun doSearch(key: String, city: String?) {
- Observable.create(ObservableOnSubscribe<ArrayList<AutocompletePrediction>> {
- it.onNext(getAutocomplete(key)!!) // 需要在次线程
- }).subscribeOn(Schedulers.io())
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe({
- searchAdpater?.clearData()
- for (item in it) {
- val placeResult = mGeoDataClient!!.getPlaceById(item.placeId)
- placeResult.addOnCompleteListener(mUpdatePlaceDetailsCallback) // 异步访问单个 placeId 的详细信息
- }
- }, {
- ALog.e(TAG, it)
- })
- }
- /**
- * 异步访问单个 placeId 的详细信息
- */
- val mUpdatePlaceDetailsCallback = object : OnCompleteListener<PlaceBufferResponse> {
- override fun onComplete(task: Task<PlaceBufferResponse>) {
- try {
- val place = task.result.get(0)
- searchAdpater?.addData(LocationItem(false, place.latLng.latitude, place.latLng.longitude, place.name.toString(), place.address.toString()))
- ALog.i(TAG, "Place details received:" + place.name)
- task.result.release()
- } catch (e: RuntimeRemoteException) {
- ALog.e(TAG, e)
- }
- }
- }
- private fun getAutocomplete(constraint: CharSequence): ArrayList<AutocompletePrediction>? {
- ALog.d(TAG, "Starting autocomplete query for:" + constraint)
- // Submit the query to the autocomplete API and retrieve a PendingResult that will
- // contain the results when the query completes.
- val results = mGeoDataClient?.getAutocompletePredictions(constraint.toString(), null,
- null)
- // This method should have been called off the main UI thread. Block and wait for at most
- // 60s for a result from the API.
- // 收集文字关联预测结果
- try {
- Tasks.await<AutocompletePredictionBufferResponse>(results!!, 2, TimeUnit.SECONDS)
- } catch (e: ExecutionException) {
- e.printStackTrace()
- } catch (e: InterruptedException) {
- e.printStackTrace()
- } catch (e: TimeoutException) {
- e.printStackTrace()
- }
- try {
- val autocompletePredictions = results!!.result
- ALog.d(TAG, "Query completed. Received" + autocompletePredictions.count
- + "predictions.")
- // Freeze the results immutable representation that can be stored safely.
- return DataBufferUtils.freezeAndClose<AutocompletePrediction, AutocompletePrediction>(autocompletePredictions)
- } catch (e: RuntimeExecutionException) {
- // If the query did not complete successfully return null
- Toast.makeText(context, "Error contacting API:" + e.toString(),
- Toast.LENGTH_SHORT).show()
- ALog.e(TAG, "Error getting autocomplete prediction API call", e)
- return null
- }
- }
2. 高德地图中的 PoiSearch 是支持通过关键字搜索和经纬度地址附近搜索.
```
/**
* 经纬度搜索
来源: https://juejin.im/entry/5c6e6c2ef265da2d9b5e142c