📖第4章 Android高德地图绘制标记点Marker
-
-
- [✅绘制默认 Marker](#✅绘制默认 Marker)
- ✅绘制多个Marker
- [✅绘制自定义 Marker](#✅绘制自定义 Marker)
- ✅Marker点击事件
- ✅Marker动画效果
- ✅Marker拖拽事件
- [✅绘制默认 Infowindow](#✅绘制默认 Infowindow)
-
- [🚩隐藏InfoWindow 弹框](#🚩隐藏InfoWindow 弹框)
- [✅绘制自定义 InfoWindow](#✅绘制自定义 InfoWindow)
-
- [🚩实现 InfoWindow 样式和内容](#🚩实现 InfoWindow 样式和内容)
- [🚩可触发的 InfoWindow 事件](#🚩可触发的 InfoWindow 事件)
- [🚩自定义复杂的 InfoWindow](#🚩自定义复杂的 InfoWindow)
-
✅绘制默认 Marker
效果如下图:
通过aMap.addMarker()
来添加标记点marker
,而经纬度等信息需要通过MarkerOptions
来设置,示例代码如下:
java
//marker标记物
LatLng latLng = new LatLng(31.042119,121.410428);
final Marker marker = aMap.addMarker(new MarkerOptions().position(latLng).title("测试地点").snippet("这里是测试内容"));
Marker 常用属性
名称 | 说明 |
---|---|
position | 在地图上标记位置的经纬度值。必填参数 |
title | 点标记的标题 |
snippet | 点标记的内容 |
draggable | 点标记是否可拖拽 |
visible | 点标记是否可见 |
anchor | 点标记的锚点 |
alpha | 点的透明度 |
anchor
锚点可以精确控制标记图标相对于标记点(经纬度)的位置,以满足不同场景下的需求。比如您可能希望将锚点设置为标记图标的其他部分,例如顶部中心或左侧中心。默认锚点位置是底部中心
java
anchor(0.0f, 0.5f); // 左侧中心
anchor(1.0f, 0.5f); // 右部中心
anchor(1.0f, 1.0f); // 底部右侧
anchor(0.5f, 0.0f); // 顶部中心
alpha
透明度是用来表示对象的可见度或不透明度的属性。应用场景:
- 标记动画:实现标记的淡入淡出效果,以改善用户体验
java
// 创建标记并设置透明度为0.5
MarkerOptions markerOptions = new MarkerOptions()
.position(new LatLng(latitude, longitude))
.icon(BitmapDescriptorFactory.fromResource(R.drawable.marker_icon))
.alpha(0.5f); // 设置透明度为0.5
Marker marker = aMap.addMarker(markerOptions);
// 在动画中逐渐将透明度变为1.0
ObjectAnimator alphaAnimator = ObjectAnimator.ofFloat(marker, "alpha", 0.5f, 1.0f);
alphaAnimator.setDuration(1000);
alphaAnimator.start();
- 突出显示标记: 在一组标记中突出显示特定的标记,可以使目标标记更加显眼。
java
// 创建多个标记
MarkerOptions targetMarkerOptions = new MarkerOptions()
.position(new LatLng(targetLatitude, targetLongitude))
.icon(BitmapDescriptorFactory.fromResource(R.drawable.target_marker_icon))
.alpha(1.0f); // 目标标记的透明度为1.0
Marker targetMarker = aMap.addMarker(targetMarkerOptions);
MarkerOptions otherMarkerOptions = new MarkerOptions()
.position(new LatLng(otherLatitude, otherLongitude))
.icon(BitmapDescriptorFactory.fromResource(R.drawable.other_marker_icon))
.alpha(0.5f); // 其他标记的透明度为0.5
Marker otherMarker = aMap.addMarker(otherMarkerOptions);
- 动态显示与隐藏:将透明度设置为0.0时,标记将完全不可见,而设置为1.0时,则完全可见。
java
// 创建标记并设置透明度为0.0,标记开始时不可见
MarkerOptions markerOptions = new MarkerOptions()
.position(new LatLng(latitude, longitude))
.icon(BitmapDescriptorFactory.fromResource(R.drawable.marker_icon))
.alpha(0.0f); // 设置透明度为0.0
Marker marker = aMap.addMarker(markerOptions);
// 在动画中逐渐将透明度变为1.0,标记逐渐变得可见
ObjectAnimator alphaAnimator = ObjectAnimator.ofFloat(marker, "alpha", 0.0f, 1.0f);
alphaAnimator.setDuration(1000);
alphaAnimator.start();
✅绘制多个Marker
效果如下图:
不管是创建默认的Marker
还是自定义的Marker
都一样,都是通过MarkerOptions
设置Marker
的信息,再通过aMap.addMarker(markerOption)
在地图上添加。
示例代码如下:
java
//样本数据
List<LatLng> positon = new ArrayList<>();
positon.add(new LatLng(31.041742,121.411517));
positon.add(new LatLng(31.041370,121.411699));
positon.add(new LatLng(31.041563,121.412198));
//绘制自定义marker
MarkerOptions options = new MarkerOptions();
for (int i = 0; i < positon.size(); i++) {
options.position(positon.get(i));
options.title("测试"+i);
options.snippet("内容"+i);
aMap.addMarker(options);
}
✅绘制自定义 Marker
效果如下图:
绘制自定义 Marker
的自定义icon
图标是通过BitmapDescriptorFactory
来处理,它能将图标资源文件转换成位图(Bitmap)对象,以便在地图上使用。示例代码如下:
java
//绘制自定义marker
LatLng latLng2 = new LatLng(31.041991,121.409628);
MarkerOptions markerOption = new MarkerOptions();
markerOption.position(latLng2);
markerOption.title("测试2").snippet("我是自定义marker");
markerOption.draggable(true);//设置Marker可拖动
markerOption.icon(BitmapDescriptorFactory.fromBitmap(BitmapFactory
.decodeResource(getResources(),R.drawable.icon_marker_orange)));
markerOption.setFlat(false);//设置marker平贴地图效果
aMap.addMarker(markerOption);
至于icon
图标可以在阿里巴巴矢量图标库里面下载png
格式的图片即可。
🚩扩展:在自定义的marker中绘制文字等其他信息
应用场景:需要在地图上看见该标记点中的数据信息,可以是姓名简称,数字等信息。 效果如下图:
一共两个步骤:
- 绘制带文本的图片,格式为
BitmapDescriptor
类型 - 在地图上添加标记点
java
//绘制自定义带文字的marker
BitmapDescriptor withDataIcon = drawIcon(R.drawable.icon_marker_orange, "李", Color.WHITE);
LatLng latLng2 = new LatLng(31.041991,121.409628);
MarkerOptions markerOption = new MarkerOptions();
markerOption.position(latLng2);
markerOption.title("测试").snippet("我是携带数据的marker");
markerOption.draggable(true);//设置Marker可拖动
markerOption.icon(withDataIcon);
markerOption.setFlat(false);//设置marker平贴地图效果
aMap.addMarker(markerOption);
drawIcon
方法如下:
java
/**
* 绘制带文字的marker
* @param markerStyle 图片资源
* @param text 文字
* @param textColor 文字颜色
* @return
*/
private BitmapDescriptor drawIcon(int markerStyle, String text, int textColor) {
Bitmap bitmap = null;
BitmapDescriptor icon = null;
try {
bitmap = BitmapFactory.decodeResource(getResources(), markerStyle);//图片转bitmap位图
int markerStyleWidth = bitmap.getWidth();//获取bitmap位图的宽
int markerStyleHeight = bitmap.getHeight();//获取bitmap位图的高
Bitmap mutableBitmap = bitmap.copy(Bitmap.Config.ARGB_8888, true);//复制Bitmap对象
Canvas canvas = new Canvas(mutableBitmap); //创建Canvas对象
Paint paint = new Paint();//创建Paint对象,用于定义绘制文本的样式
paint.setColor(textColor);//设置文字颜色
paint.setTextSize(getResources().getDimensionPixelSize(R.dimen.text_size));//设置文字大小
//获取文本边界
Rect textBounds = new Rect();
paint.getTextBounds(text, 0, text.length(), textBounds);
int textWidth = textBounds.width();//文本宽度
int textHeight = textBounds.height();//文本高度
//计算文字在图片上的坐标,使其在图片居中位置
float x = (markerStyleWidth - textWidth) / 2.0f;//x表示绘制文本起始点的横坐标,从左开始
float y = (markerStyleHeight + textHeight) / 2.0f - 20;//x表示绘制文本起始点的纵坐标,从顶部开始
canvas.drawText(text, x, y, paint); //使用Canvas绘制文本
icon = BitmapDescriptorFactory.fromBitmap(mutableBitmap);// 最后将Bitmap 转换为 BitmapDescriptor
return icon;
} finally {
if (bitmap != null){
bitmap.recycle();
}
if (icon != null){
icon.recycle();
}
}
}
注意:
Bitmap
和BitmapDescriptor
是一个占用内存的对象,需要及时回收以防止内存泄漏。虽然Java有垃圾回收机制,但它是在适当的时机,例如在内存紧张或空闲时,扫描不再被引用的对象并将其释放,在某些情况下,手动释放资源仍然是一个良好的习惯。R.dimen.text_size
是在values文件下的dimens.xml
资源文件中定义的,如果你没有则创建一个并添加下面代码。
xml
<resources>
<dimen name="text_size">14sp</dimen>
</resources>
✅Marker点击事件
点击 Marker 时会回调AMap.OnMarkerClickListener
,监听器的实现示例如下:
java
//设置marker点击事件
aMap.setOnMarkerClickListener(this);
重写onMarkerClick
方法
java
/**
* marker 点击监听事件
* 返回 true 则表示接口已响应事件,否则返回false
* @param marker
* @return
*/
@Override
public boolean onMarkerClick(Marker marker) {
return false;
}
✅Marker动画效果
将动画效果放在点击marker时可以更好看到变化,自地图 SDK V4.0.0 版本起,SDK 提供了给 Marker 设置动画的方法,具体实现方法如下:
java
/**
* marker 点击监听事件
* 返回 true 则表示接口已响应事件,否则返回false
* @param marker
* @return
*/
@Override
public boolean onMarkerClick(Marker marker) {
//180度旋转动画
Animation animation = new RotateAnimation(marker.getRotateAngle(),marker.getRotateAngle()+180,0,0,0);
long duration = 1000L;
animation.setDuration(duration);
animation.setInterpolator(new LinearInterpolator());
marker.setAnimation(animation);
marker.startAnimation();
return false;
}
✅Marker拖拽事件
拖拽 Marker
时会回调AMap.OnMarkerDragListener
,监听器的实现示例如下:
java
//设置marker拖拽监听事件
aMap.setOnMarkerDragListener(this);
java
/**
* 当marker开始被拖动时回调此方法, 这个marker的位置可以通过getPosition()方法返回。
* @param marker
*/
@Override
public void onMarkerDragStart(Marker marker) {
Log.d("MainActivity", "Start: "+marker.getPosition());
}
/**
* 在marker拖动过程中回调此方法, 这个marker的位置可以通过getPosition()方法返回。
* @param marker
*/
@Override
public void onMarkerDrag(Marker marker) {
Log.d("MainActivity", "Drag: "+marker.getPosition());
}
/**
* 在marker拖动完成后回调此方法, 这个marker的位置可以通过getPosition()方法返回。
* @param marker
*/
@Override
public void onMarkerDragEnd(Marker marker) {
Log.d("MainActivity", "End: "+marker.getPosition());
}
✅绘制默认 Infowindow
默认 Infowindow是不用创建的,当我们创建marker时自带有的,SDK
为用户提供了默认的 InfoWindow 样式,只显示 Marker 对象的两个属性,一个是 title
和另一个 snippet
。
调用 Marker 类的 showInfoWindow()
和 hideInfoWindow()
方法可以控制显示和隐藏。
当改变 Marker 的 title 和 snippet 属性时,再次调用 showInfoWindow()
,可以更新 InfoWindow 显示内容。
🚩隐藏InfoWindow 弹框
在什么时候去隐藏InfoWindow 弹框,一般情况可以在点击map地图的其他地方关闭它,实现方式如下:
- 首先设置
marker
和map
点击监听事件
java
//设置marker点击事件
aMap.setOnMarkerClickListener(this);
//地图点击监听事件
aMap.setOnMapClickListener(this);
- 然后在
marker
点击事件中记录当前点击的marker
和Infowindow
是否弹框的布尔值
java
private Marker current;//记录当前点击的marker
private boolean isMarkerClicked = false;//判断是否Infowindow弹框
@Override
public boolean onMarkerClick(Marker marker) {
current = marker;
isMarkerClicked = true;
return false;
}
- 最后在
onMapClick
地图点击事件中隐藏Infowindow
弹框
java
/**
* map地图点击监听事件
*/
@Override
public void onMapClick(LatLng latLng) {
if (!isMarkerClicked) {
if (current != null && current.isInfoWindowShown()) {
current.hideInfoWindow();//隐藏当前Infowindow弹框
}
}
isMarkerClicked = false; // 重置标记状态
}
✅绘制自定义 InfoWindow
实现 InfoWindowAdapter
接口,其中有两个方法需要实现,依次来看一下:
java
public interface InfoWindowAdapter {
View getInfoWindow(Marker marker);
View getInfoContents(Marker marker);
}
View getInfoWindow(Marker marker)
- 当实现此方法并返回有效值时(返回值不为空,则视为有效),SDK 将不会使用默认的样式,而采用此方法返回的样式(即 View)。默认会将Marker 的 title 和 snippet 显示到 InfoWindow 中。
- 如果此时修改了 Marker 的 title 或者 snippet 内容,再次调用类 Marker 的 showInfoWindow() 方法,InfoWindow 内容不会更新。
- 自定义 InfoWindow 之后所有的内容更新都需要用户自己完成。
- 当调用 Marker 类的 showInfoWindow() 方法时,SDK 会调用 getInfoWindow(Marker marker) 方法和 getInfoContents(Marker marker) 方法(之后会提到),在这些方法中更新 InfoWindow 的内容即可。
注意:如果此方法返回的 View 没有设置 InfoWindow 背景图,SDK 会默认添加一个背景图。
View getInfoContents(Marker marker)
此方法和 getInfoWindow(Marker marker) 方法的实质是一样的,唯一的区别是:
- 此方法不能修改整个 InfoWindow 的背景和边框,无论自定义的样式是什么样,SDK 都会在最外层添加一个默认的边框。
简而言之,getInfoContents(Marker marker)
只允许你自定义 InfoWindow
的内容,而不能改变整个窗口的外观和边框。SDK 会在你自定义的内容外面添加一个默认的边框。这可能是为了确保 InfoWindow
在地图上有一致的外观,以保持用户体验的统一性。如果你希望完全自定义整个 InfoWindow
的外观,包括边框,那么应该使用 getInfoWindow(Marker marker)
方法。
🚩实现 InfoWindow 样式和内容
实现 InfoWindow
样式和内容的步骤如下:
- 设置InfoWindow适配器
- 在
getInfoWindow
返回一个自定义View
组件 =R.layout.custom_info_window
。
java
implements AMap.InfoWindowAdapter//实现接口
aMap.setInfoWindowAdapter(this);//设置InfoWindow适配器
实现getInfoWindow和getInfoContents方法
java
View infoWindow = null;
@Override
public View getInfoWindow(Marker marker) {
if (infoWindow == null) {
infoWindow = LayoutInflater.from(this).inflate(R.layout.custom_info_window, null);
}
render(marker, infoWindow);
return infoWindow;
}
@Override
public View getInfoContents(Marker marker) {
return null;
}
public void render(Marker marker, View view) {
//如果想修改自定义Infow中内容,请通过view找到它并修改
String title = marker.getTitle();
TextView titleUi = view.findViewById(R.id.title);
titleUi.setText(title);
String snippet = marker.getSnippet();
TextView snippetUi = view.findViewById(R.id.snippet);
snippetUi.setText(snippet);
}
R.layout.custom_info_window
组件:
xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
>
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="17sp"
android:textColor="#72db0f"
android:text="标题"
/>
<TextView
android:id="@+id/snippet"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="17sp"
android:textColor="#db0f00"
android:text="详细内容"
/>
</LinearLayout>
🚩可触发的 InfoWindow 事件
点击 InfoWindow
时会回调 AMap.OnInfoWindowClickListener
,监听器的实现示例如下:
java
OnInfoWindowClickListener listener = new OnInfoWindowClickListener() {
@Override
public void onInfoWindowClick(Marker marker) {
marker.setTitle("infowindow clicked");
}
};
//绑定信息窗点击事件
aMap.setOnInfoWindowClickListener(listener);
简单点说就是可以自定义点击 InfoWindow
后的行为,例如改变标题、展示更多信息或触发其他操作。它有哪些使用场景和用途:
- 刷新 InfoWindow 内容: 在点击事件中,您可以修改
InfoWindow
的内容,使其动态刷新,显示最新的信息。这对于实时更新的信息非常有用。 - **交互式地图体验:**例如点击时显示更多的内容、点击后关闭
InfoWindow
弹框,marker.hideInfoWindow()
即可。 - **与标记点关联的操作:**例如,在地图上显示商家的标记点,点击
InfoWindow
可以跳转到商家详情页面。
等等还要其他的,这里就举例这几种。
🚩自定义复杂的 InfoWindow
根据需求,你可能需求对标记点进行各种操作,如下图:
其实绘制自定义 InfoWindow
本质上就是两步,先设置InfoWindow
适配器,其次在view组件中自定义要显示的样式或按钮。
在这种自定义可能会遇到居中显示问题,如何点击标点时让其在屏幕中心,或者在屏幕中心下方一点点使其能都刚好在屏幕中心完全显示。只需在onMarkerClick
点击事件中实现如下逻辑:
- 将
marker
位置坐标下移一小段距离显示 - 注意实现
onMarkerClick
前需要aMap.setOnMarkerClickListener(this);
设置
java
@Override
public boolean onMarkerClick(Marker marker) {
// 当前点击的marker位置坐标
LatLng markerLatLng = marker.getPosition();
// 计算新坐标(下移一小段距离)
double offsetLat = 0.003;
LatLng newCenterLatLng = new LatLng(markerLatLng.latitude + offsetLat, markerLatLng.longitude);
// 创建一个新的 CameraPosition,设置新的方向为 0(北方向)
CameraPosition newPosition = new CameraPosition.Builder()
.target(newCenterLatLng)
.bearing(0) // 将方向设置为 0(北方向)
.zoom(15) // 设置缩放级别
.build();
// 移动地图中心点
aMap.animateCamera(CameraUpdateFactory.newCameraPosition(newPosition));
return true; // 返回 true 表示消费了点击事件,不再传递给其他监听器
}