一、如图。
二、设计详情:地图为腾讯MapView、搜索列表RecycleView 为屏幕长度的一半、点击地图空白处关闭列表、点击地图地点名称、搜索并展示;
三、注意点:腾讯地图显示和定位是分开的、上面的效果实现、必须接入地图和定位两个sdk;
// 腾讯定位 implementation 'com.tencent.map.geolocation:TencentLocationSdk-openplatform:7.3.0' // 腾讯地图 implementation 'com.tencent.map:tencent-map-vector-sdk:4.3.4'
注意腾讯地图分为2D、3D; 如上为3D效果
四、业务或逻辑关键点 (整体代码在第五点)
地图展示:SupportMapFragment (腾讯提供)、内部封装了MapView、优点:开发者不需要关心地图生命周期;
xml中:
<fragment android:id="@+id/map_frag" class="com.tencent.tencentmap.mapsdk.maps.SupportMapFragment" android:layout_width="match_parent" android:layout_height="match_parent" />
地址搜索关键代码:
companion object {
private const val MSG_SUGGESTION = 10000
}
//地址模糊搜索、
private fun searchAddress(address: String?) {
if (address.isNullOrEmpty()) return
val tencentSearch = TencentSearch(this)
val suggestionParam = SuggestionParam(address, "上海")
//suggestion也提供了filter()方法和region方法
//具体说明见文档,或者官网的webservice对应接口
tencentSearch.suggestion(suggestionParam, object : HttpResponseListener<BaseObject> {
override fun onSuccess(arg0: Int, arg1: BaseObject?) {
if (arg1 == null) {
return
}
progressBar.postDelayed({
progressBar.visibility = View.GONE
val msg = Message()
msg.what = MSG_SUGGESTION
//关键点arg1
msg.obj = arg1
handler.sendMessage(msg)
}, 350)
}
override fun onFailure(arg0: Int, arg1: String?, arg2: Throwable?) {
progressBar.visibility = View.GONE
}
})
}
//地址搜索结果预览
private val handler = object : Handler() {
override fun handleMessage(msg: Message) {
super.handleMessage(msg)
if (msg.what == MSG_SUGGESTION) {
val searchList = (msg.obj as SuggestionResultObject).data
//RecycViewAdapter
mapAdapter.addData(searchList)
}
}
}
搜索结果实体类(腾讯)
public class SuggestionResultObject extends BaseObject {
public int count;
//此处data 为搜索列表结果数据
public List<SuggestionResultObject.SuggestionData> data;
//sub_pois 暂无用
public List<SuggestionResultObject.SubPoi> sub_pois;
public SuggestionResultObject() {
}
public static final class SubPoi extends JsonComposer {
public String parent_id;
public String id;
public String title;
public String address;
@Json(
name = "location"
)
public LatLng latLng;
public String adcode;
public String city;
public SubPoi() {
}
}
public static final class SuggestionData extends JsonComposer {
public String id;
public String title;
public String address;
public String province;
public String city;
/** @deprecated */
public String district;
//区号
public String adcode;
public int type;
@Json(
name = "location"
)
//经纬度
public LatLng latLng;
public float _distance;
public SuggestionData() {
}
}
}
地图默认marker点 定位Icon(红色箭头):
/**
* 设置定位图标样式
*/
private fun setLocMarkerStyle() {
val locationStyle = MyLocationStyle()
//创建图标
val bitmapDescriptor =
BitmapDescriptorFactory.fromResource(R.drawable.ic_map_location)
locationStyle?.icon(bitmapDescriptor)
//设置定位圆形区域的边框宽度
locationStyle?.strokeWidth(3)
//设置圆区域的颜色
locationStyle?.fillColor(R.color.style)
//连续定位,但不会移动到地图中心点,并且会跟随设备移动
// locationStyle = //locationStyle?.myLocationType(MyLocationStyle.LOCATION_TYPE_FOLLOW_NO_CENTER)
supportMapFragment = fragmentManger.findFragmentById(R.id.map_frag) as SupportMapFragment
val tencentMap = supportMapFragment?.map
tencentMap?.setMyLocationStyle(locationStyle)
}
点击地址Marker Icon显示:
//地图位置点击回调 、 tencentMap?.setOnMapPoiClickListener(this)
override fun onClicked(mapPoi: MapPoi?) {
setMarker(mapPoi?.getPosition(), mapPoi?.getName())
}
// latLng 经纬度、name 地址名称
private fun setMarker(latLng: LatLng?, name: String?) {
marker?.remove()
val options = MarkerOptions().position(latLng)
//设置infowindow
options.title("地址:")
options.snippet(name)
// options.icon(BitmapDescriptorFactory.fromResource(R.mipmap.ic_launcher))
marker = tencentMap?.addMarker(options)
marker?.showInfoWindow()
}
移动地图
//移动镜头
private fun initCamera(isAnima: Boolean = false) {
//对地图操作类进行操作
val cameraSigma = CameraUpdateFactory.newCameraPosition(
CameraPosition(
centerLatLng,
17f,
0f,
0f
)
)
//移动地图
if (isAnima)
tencentMap?.animateCamera(cameraSigma)
else
tencentMap?.moveCamera(cameraSigma)
}
五、整体代码
class MapActivity : BaseHttpActivity(), LocationSource, TencentLocationListener,
TencentMap.OnMapPoiClickListener {
companion object {
private const val MSG_SUGGESTION = 10000
}
//腾讯地图操控类
private var tencentMap: TencentMap? = null
private var supportMapFragment: SupportMapFragment? = null
//UI控制类
private var mapUiSettings: UiSettings? = null
//地址数据
private var locationChangedListener: OnLocationChangedListener? = null
//位置管理
private var locationManager: TencentLocationManager? = null
//地址请求封装
private var locationRequest: TencentLocationRequest? = null
//地图图标样式
private var locationStyle: MyLocationStyle? = null
//地图标点
private var marker: Marker? = null
//默认北京
private var centerLatLng = LatLng(39.984066, 116.307548)
//适配器相关
private val mapAdapter = MapAdapter()
//------------BaseHttpActivity 重载的方法 无需关注、删除即可
override fun statusBarColor() = R.color.F7F7F7
override fun needRightTv() = true
override fun needBackIcon() = true
override fun getLayoutId() = R.layout.map_activity
override fun setTitle() = getString(R.string.map_title)
//------------BaseHttpActivity 重载的方法、删除即可
private var firstInter = true
//---等同于onCreat() 优先级大于initDate()
override fun initView() {
//tvRight.text = getString(R.string.map_sub_title)
//tvRight.setTextColor(getColor(R.color.F15A24))
//创建tencentMap地图对象,可以完成对地图的几乎所有操作
val fragmentManger = supportFragmentManager
supportMapFragment = fragmentManger.findFragmentById(R.id.map_frag) as SupportMapFragment
initMap()
//初始化Camera
initCamera()
//建立定位
initLocation()
//初始化地图点击处理
initPoiClick()
initAdapter()
}
private fun initAdapter() {
addressRv.layoutManager = LinearLayoutManager(this)
addressRv.adapter = mapAdapter
}
//---等同于onCreat() 优先级小于initView()
override fun initDate() {
ivReset.setOnClickListener {
initCamera(true)
}
// 地址选择后的点击事件、可以参考
/*tvRight.setOnClickListener {
val data = mapAdapter.getSelectData()
if (data != null) {
eventBus.post(data)
finish()
} else {
ToastUtils.showToast("所选地址不能为空!")
}
}*/
}
//移动镜头
private fun initCamera(isAnima: Boolean = false) {
//对地图操作类进行操作
val cameraSigma = CameraUpdateFactory.newCameraPosition(
CameraPosition(
centerLatLng,
17f,
0f,
0f
)
)
//移动地图
if (isAnima)
tencentMap?.animateCamera(cameraSigma)
else
tencentMap?.moveCamera(cameraSigma)
}
private fun initMap() {
tencentMap = supportMapFragment?.map
//设置当前位置可见
tencentMap?.isMyLocationEnabled = true
//地图UI设置
mapUiSettings = tencentMap?.uiSettings
//设置显示定位的图标
// mapUiSettings?.isMyLocationButtonEnabled = true
mapUiSettings?.setLogoPosition(TencentMapOptions.LOGO_POSITION_TOP_RIGHT)
//地图点击监听
tencentMap?.setOnMapClickListener {
progressBar?.visibility = View.GONE
addressRv?.visibility = View.GONE
flContainer.reSetHeight()
}
}
/**
* 定位的一些初始化设置
*/
private fun initLocation() {
//用于访问腾讯定位服务的类, 周期性向客户端提供位置更新
locationManager = TencentLocationManager.getInstance(this)
//设置坐标系
locationManager?.coordinateType = TencentLocationManager.COORDINATE_TYPE_GCJ02
//创建定位请求
locationRequest = TencentLocationRequest.create()
//设置定位周期(位置监听器回调周期)为3s
locationRequest?.interval = 3000
//地图上设置定位数据源
tencentMap?.setLocationSource(this)
//设置当前位置可见
tencentMap?.isMyLocationEnabled = true
//设置定位图标样式
setLocMarkerStyle()
tencentMap?.setMyLocationStyle(locationStyle)
}
/**
* 设置定位图标样式
*/
private fun setLocMarkerStyle() {
locationStyle = MyLocationStyle()
//创建图标
val bitmapDescriptor =
// BitmapDescriptorFactory.fromBitmap(getBitMap(R.drawable.ic_map_location))
BitmapDescriptorFactory.fromResource(R.drawable.ic_map_location)
locationStyle?.icon(bitmapDescriptor)
//设置定位圆形区域的边框宽度
locationStyle?.strokeWidth(3)
//设置圆区域的颜色
locationStyle?.fillColor(R.color.style)
//连续定位,但不会移动到地图中心点,并且会跟随设备移动
// locationStyle = locationStyle?.myLocationType(MyLocationStyle.LOCATION_TYPE_FOLLOW_NO_CENTER)
}
//设置定位图片
private fun getBitMap(resourceId: Int): Bitmap? {
var bitmap = BitmapFactory.decodeResource(resources, resourceId)
val width = bitmap.width
val height = bitmap.height
val newWidth = 55
val newHeight = 55
val widthScale = newWidth.toFloat() / width
val heightScale = newHeight.toFloat() / height
val matrix = Matrix()
matrix.postScale(widthScale, heightScale)
bitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, true)
return bitmap
}
//地图位置点击
private fun initPoiClick() {
tencentMap?.setOnMapPoiClickListener(this)
}
//首次启动验证
override fun activate(onLocationChangedListener: OnLocationChangedListener?) {
locationChangedListener = onLocationChangedListener
val err = locationManager?.requestLocationUpdates(locationRequest, this, Looper.myLooper())
when (err) {
1 -> Toast.makeText(this, "设备缺少使用腾讯定位服务需要的基本条件", Toast.LENGTH_SHORT).show()
2 -> Toast.makeText(this, "manifest 中配置的 key 不正确", Toast.LENGTH_SHORT).show()
3 -> Toast.makeText(this, "自动加载libtencentloc.so失败", Toast.LENGTH_SHORT).show()
else -> {
}
}
}
override fun deactivate() {
locationManager?.removeUpdates(this)
locationManager = null
locationRequest = null
locationChangedListener = null
}
//定位回调
override fun onLocationChanged(
tencentLocation: TencentLocation?,
error: Int,
errorMsg: String?
) {
if (error == TencentLocation.ERROR_OK && locationChangedListener != null) {
if (tencentLocation != null) {
val location = Location(tencentLocation.provider)
//设置经纬度以及精度
location.latitude = tencentLocation.latitude
location.longitude = tencentLocation.longitude
location.accuracy = tencentLocation.accuracy
locationChangedListener?.onLocationChanged(location)
//记录坐标
centerLatLng.setLatitude(tencentLocation.latitude)
centerLatLng.setLongitude(tencentLocation.longitude)
//设置镜头不随中心点移动
if (locationStyle?.myLocationType != MyLocationStyle.LOCATION_TYPE_FOLLOW_NO_CENTER) {
locationStyle =
locationStyle?.myLocationType(MyLocationStyle.LOCATION_TYPE_FOLLOW_NO_CENTER)
tencentMap?.setMyLocationStyle(locationStyle)
}
//地址搜索
if (firstInter) {
firstInter = false
searchAddress(tencentLocation.name)
}
} else {
}
}
}
override fun onStatusUpdate(p0: String?, p1: Int, p2: String?) {
}
override fun onClicked(mapPoi: MapPoi?) {
setMarker(mapPoi?.getPosition(), mapPoi?.getName())
//searchAddress loading
progressBar.visibility = View.VISIBLE
searchAddress(mapPoi?.getName())
}
private fun setMarker(latLng: LatLng?, name: String?) {
marker?.remove()
val options = MarkerOptions().position(latLng)
//设置infowindow
options.title("地址:")
options.snippet(name)
// options.icon(BitmapDescriptorFactory.fromResource(R.mipmap.ic_launcher))
marker = tencentMap?.addMarker(options)
marker?.showInfoWindow()
}
//模糊搜索
private fun searchAddress(address: String?) {
if (address.isNullOrEmpty()) return
val tencentSearch = TencentSearch(this)
val suggestionParam = SuggestionParam(address, "上海")
//suggestion也提供了filter()方法和region方法
//具体说明见文档,或者官网的webservice对应接口
tencentSearch.suggestion(suggestionParam, object : HttpResponseListener<BaseObject> {
override fun onSuccess(arg0: Int, arg1: BaseObject?) {
if (arg1 == null) {
return
}
progressBar.postDelayed({
progressBar.visibility = View.GONE
val msg = Message()
msg.what = MSG_SUGGESTION
msg.obj = arg1
handler.sendMessage(msg)
}, 350)
}
override fun onFailure(arg0: Int, arg1: String?, arg2: Throwable?) {
progressBar.visibility = View.GONE
}
})
}
//地址搜索结果预览
private val handler = object : Handler() {
override fun handleMessage(msg: Message) {
super.handleMessage(msg)
if (msg.what == MSG_SUGGESTION) {
val searchList = (msg.obj as SuggestionResultObject).data
mapAdapter.addData(searchList)
if (searchList.isEmpty()) {
addressRv.visibility = View.GONE
flContainer.reSetHeight()
} else {
addressRv.visibility = View.VISIBLE
//动态设置布局宽高
val heightDp = ScreenUtils.getAppScreenHeight() / 2
flContainer.setHeight(heightDp)
}
}
}
}
override fun onDestroy() {
super.onDestroy()
handler.removeCallbacksAndMessages(null)
}
}
map_activity.xml
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment
android:id="@+id/map_frag"
class="com.tencent.tencentmap.mapsdk.maps.SupportMapFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@+id/flContainer"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<FrameLayout
android:id="@+id/flContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:background="@color/white"
android:animateLayoutChanges="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/addressRv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
tools:visibility="visible" />
<ProgressBar
android:id="@+id/progressBar"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_gravity="center|top"
android:layout_margin="5dp"
android:indeterminateTint="@color/F15A24"
android:visibility="gone"
tools:visibility="visible" />
</FrameLayout>
<ImageView
android:id="@+id/ivReset"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_marginEnd="15dp"
android:layout_marginBottom="15dp"
android:src="@drawable/ic_location_reset"
android:text="reSet"
app:layout_constraintBottom_toTopOf="@+id/flContainer"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
资源icon
ic_location_reset
ic_map_location
颜色: android:indeterminateTint="@color/F15A24" ==#F15A24、
android:background="@color/white"==#ffffff
地图搜索列表 适配器代码及相关xml
class MapAdapter : RecyclerView.Adapter<MapAdapter.MapHolder>() {
private val data = mutableListOf<SuggestionResultObject.SuggestionData>()
private var selectPos = 0
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MapHolder {
val view =
LayoutInflater.from(parent.context).inflate(R.layout.adapter_map_item, parent, false)
return MapHolder(view)
}
override fun onBindViewHolder(holder: MapHolder, position: Int) {
holder.bind(position)
}
override fun getItemCount() = data.size
fun addData(newData: MutableList<SuggestionResultObject.SuggestionData>?) {
newData?.run {
if (newData.isNotEmpty()) {
selectPos = 0
data.clear()
data.addAll(this)
notifyDataSetChanged()
}
}
}
inner class MapHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bind(pos: Int) {
val bean = data[pos]
with(itemView) {
val leftImg = findViewById<ImageView>(R.id.ivAddress)
//地址标题
val tvAddressTitle = findViewById<TextView>(R.id.tvAddressTitle)
//地址详情
val tvAddressInfo = findViewById<TextView>(R.id.tvAddressInfo)
val bomLine = findViewById<View>(R.id.bom_line)
tvAddressTitle.text = bean.title
tvAddressInfo.text = bean.address
if (selectPos == pos) {
tvAddressTitle.setTextColor(
ContextCompat.getColor(
itemView.context,
R.color.ED6A0C
)
)
} else {
tvAddressTitle.setTextColor(
ContextCompat.getColor(
itemView.context,
R.color._666666
)
)
}
if (pos == 0) {
leftImg.setImageResource(R.drawable.ic_location)
} else {
leftImg.setImageResource(R.drawable.ic_map_search_point)
}
//隐藏末尾下划线
if (pos == data.size - 1) {
bomLine.visibility = View.GONE
} else bomLine.visibility = View.VISIBLE
//状态改变
setOnClickListener {
if (selectPos == bindingAdapterPosition) return@setOnClickListener
selectPos = bindingAdapterPosition
notifyDataSetChanged()
}
}
}
}
fun getSelectData(): SuggestionResultObject.SuggestionData? = try {
data[selectPos]
} catch (e: Exception) {
e.printStackTrace()
null
}
}
adapter_map_item.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:background="@color/white"
android:layout_width="match_parent"
android:layout_height="72dp">
<ImageView
android:id="@+id/ivAddress"
android:layout_width="15dp"
android:layout_height="20dp"
android:layout_marginLeft="15dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@drawable/ic_location" />
<TextView
android:id="@+id/tvAddressTitle"
style="@style/tv_18_color_66_font_m"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:layout_marginTop="12dp"
android:ellipsize="end"
android:includeFontPadding="false"
android:maxLines="1"
android:paddingRight="5dp"
app:layout_constraintLeft_toRightOf="@+id/ivAddress"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="翔鹿大厦" />
<TextView
android:id="@+id/tvAddressInfo"
style="@style/tv_14_color_aa_font_m"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:ellipsize="end"
android:includeFontPadding="false"
android:maxLines="1"
android:paddingRight="5dp"
app:layout_constraintLeft_toLeftOf="@+id/tvAddressTitle"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tvAddressTitle"
tools:text="上海市闵行区莘庄镇顾戴路3009号、上海市闵行区莘庄镇顾戴路3009号、上海市闵行区莘庄镇顾戴路3009号、上海市闵行区莘庄镇顾戴路3009号" />
<View
android:id="@+id/bom_line"
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_marginLeft="15dp"
android:layout_marginRight="15dp"
android:background="@color/F4F4F4"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
资源或icon
ic_location
ic_map_search_point
android:background="@color/F4F4F4" == #f4f4f4
R.color.ED6A0C ==#ED6A0C
字体先关样式可删除
至此Over!!!