八、地图点击长按事件
实际开发中都会对地图的点击和长按做处理,比如点击某一个地方获取经纬度,下面来操作一下吧。
在initMap()方法中,添加对地图点击和长按的监听。然后实现AMap.OnMapClickListener和AMap.OnMapLongClickListener
之后重写两个方法,如下所示。
代码语言:txt复制 /**
* 地图单击事件
* @param latLng
*/
@Override
public void onMapClick(LatLng latLng) {
showMsg("点击了地图,经度:" latLng.longitude ",纬度:" latLng.latitude);
}
/**
* 地图长按事件
* @param latLng
*/
@Override
public void onMapLongClick(LatLng latLng) {
showMsg("长按了地图,经度:" latLng.longitude ",纬度:" latLng.latitude);
}
可以看到我在点击和长按的监听中弹出Toast显示经纬度信息,这是通过LatLng对象获取的,下面运行一下。
可看到成功获取到了经纬度,但是这就够了吗?用户又看不懂,那么怎么样让用户知道自己点击的是那里呢?那就是要把经纬度进行一次转换,转换成实际的地址描述。在高德中这种坐标转地址称之为逆地理编码
① 逆地理编码
上面已经说过了,逆地理编码就是将坐标转为地址,坐标刚才已经拿到了,就是经纬度,下面来转换一下吧。
首先在MainActivity中创建两个对象。
代码语言:txt复制 //地理编码搜索
private GeocodeSearch geocodeSearch;
//解析成功标识码
private static final int PARSE_SUCCESS_CODE = 1000;
然后在initMap()中构建对象,然后设置监听。
之后实现这个GeocodeSearch.OnGeocodeSearchListener
重写里面的两个方法。
代码语言:txt复制 /**
* 坐标转地址
* @param regeocodeResult
* @param rCode
*/
@Override
public void onRegeocodeSearched(RegeocodeResult regeocodeResult, int rCode) {
}
/**
* 地址转坐标
* @param geocodeResult
* @param rCode
*/
@Override
public void onGeocodeSearched(GeocodeResult geocodeResult, int rCode) {
}
一个是地址转坐标,一个是坐标转地址。当前我们需要将坐标转地址,所以目前重点关注这个onRegeocodeSearched方法。
既然是坐标转地址,那么肯定要先拿到坐标,刚才的地图点击的监听中我们已经拿到了坐标,于是你就可以写出这样的一个方法:
代码语言:txt复制 /**
* 通过经纬度获取地址
* @param latLng
*/
private void latlonToAddress(LatLng latLng) {
//位置点 通过经纬度进行构建
LatLonPoint latLonPoint = new LatLonPoint(latLng.latitude, latLng.longitude);
//逆编码查询 第一个参数表示一个Latlng,第二参数表示范围多少米,第三个参数表示是火系坐标系还是GPS原生坐标系
RegeocodeQuery query = new RegeocodeQuery(latLonPoint, 20, GeocodeSearch.AMAP);
//异步获取地址信息
geocodeSearch.getFromLocationAsyn(query);
}
通过经纬度构建LatLonPoint对象,然后构建RegeocodeQuery时,传入,并且输入另外两个参数,范围和坐标系。最后通过geocodeSearch发起一个异步的地址获取请求。
代码语言:txt复制 @Override
public void onRegeocodeSearched(RegeocodeResult regeocodeResult, int rCode) {
//解析result获取地址描述信息
if(rCode == PARSE_SUCCESS_CODE){
RegeocodeAddress regeocodeAddress = regeocodeResult.getRegeocodeAddress();
//显示解析后的地址
showMsg("地址:" regeocodeAddress.getFormatAddress());
}else {
showMsg("获取地址失败");
}
}
然后在返回值中,进行判断处理,通过Toast显示地址信息。最后别忘了在地图的点击和长按监听中调用这个latlonToAddress()方法。
下面运行一下。
② 地理编码
上面说了逆地理编码,下面来说说地理编码,地理编码就是地址转坐标,那么它的使用场景是怎么样的呢?比如说你到一个景点去游玩,不知道路线只知道景点名,那么这个时候通常你会在导航软件中输入这个景点名,然后搜索出前往的路线及搭乘的交通工具。此时,导航软件会将你输入的地址转成经纬度坐标,然后通过你当前的所在地坐标计算距离,获取两点之间的交通情况,然后规划路线,是不是脑瓜子嗡嗡的,怎么导航还有这么多门道吗?其实我说的还算简单了,里面的步骤还会有很多的细化过程,好了,当前的重点不是这个地理编码吗?下面我也模仿一下,通过输入框输入地址,然后得出它的经纬度坐标。
现在屏幕的空间已经不多了,所在在不影响地图显示的情况下,我打算改变一下样式。
首先修改activity_main.xml
代码语言:txt复制<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.appcompat.widget.Toolbar
android:id="@ id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/colorPrimaryDark"
android:paddingTop="8dp"
android:paddingEnd="12dp"
android:paddingBottom="8dp">
<EditText
android:id="@ id/et_address"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/et_bg"
android:hint="请输入地址"
android:imeOptions="actionSearch"
android:paddingStart="12dp"
android:singleLine="true"
android:textColor="#000"
android:textSize="14sp" />
</androidx.appcompat.widget.Toolbar>
<!--地图-->
<com.amap.api.maps.MapView
android:id="@ id/map_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@ id/toolbar" />
<!--浮动按钮-->
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@ id/fab_poi"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_alignParentBottom="true"
android:layout_margin="20dp"
android:clickable="true"
android:onClick="queryPOI"
android:src="@drawable/icon_favorite_red"
android:visibility="gone"
app:backgroundTint="#FFF"
app:backgroundTintMode="screen"
app:hoveredFocusedTranslationZ="18dp"
app:pressedTranslationZ="18dp" />
</RelativeLayout>
我去掉了默认的ActionBar,改用Toolbar,然后在Toolbar中放了一个输入框,修改键盘的回车为搜索文字,下面进去MainActivity。
代码语言:txt复制 //输入框
private EditText etAddress;
onCreate中,
实现EditText.OnKeyListener
重写onKey方法
代码语言:txt复制 /**
* 键盘点击
*
* @param v
* @param keyCode
* @param event
* @return
*/
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_ENTER && event.getAction() == KeyEvent.ACTION_UP) {
//获取输入框的值
String address = etAddress.getText().toString().trim();
if (address == null || address.isEmpty()) {
showMsg("请输入地址");
}else {
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
//隐藏软键盘
imm.hideSoftInputFromWindow(getWindow().getDecorView().getWindowToken(), 0);
// name表示地址,第二个参数表示查询城市,中文或者中文全拼,citycode、adcode
GeocodeQuery query = new GeocodeQuery(address,"深圳");
geocodeSearch.getFromLocationNameAsyn(query);
}
return true;
}
return false;
}
在键盘按键监听时,监听是点击回车键,同时判断是否为抬起,因为按键是两个动作,按下和抬起,如果不判断就出触发两次事件,然后判断输入是否为空,不为空则隐藏软键盘,构建GeocodeQuery对象,这里有一个地址,还有一个城市,而这个城市的值在实际开发中应该是从用户数据的地点一步一步进行排查,比如先从区/县进行,没有则到市,再没有则到省,然后是全国,获取最接近当前输入地址的所在区域。而这里就没有必要那么麻烦了,因此我就写死了值为深圳,因为重点不是这个城市的问题,而是地理编码的问题。下面进入到onGeocodeSearched方法。
代码语言:txt复制 @Override
public void onGeocodeSearched(GeocodeResult geocodeResult, int rCode) {
if (rCode == PARSE_SUCCESS_CODE) {
List<GeocodeAddress> geocodeAddressList = geocodeResult.getGeocodeAddressList();
if(geocodeAddressList!=null && geocodeAddressList.size()>0){
LatLonPoint latLonPoint = geocodeAddressList.get(0).getLatLonPoint();
//显示解析后的坐标
showMsg("坐标:" latLonPoint.getLongitude() "," latLonPoint.getLatitude());
}
} else {
showMsg("获取坐标失败");
}
}
代码也是一目了然,通过返回值获取编码地址列表,判断不为空并且大于0则取第一条数据,然后获取经纬度的值显示出来。运行效果图如下所示:
③ 添加标点Marker
通常使用地图是会对地图进行标注,添加标点。刚才通过点击地图获取到了经纬度,那么同样可以根据这个经纬度在地图上绘制标点。
那么其实也很简单,下面在onMapClick方法中添加如下代码:
代码语言:txt复制 //添加标点
aMap.addMarker(new MarkerOptions().position(latLng).snippet("DefaultMarker"));
运行一下看会怎么样。
但是你会添加标点也要会删除才行。
④ 删除标点Marker
修改一下布局的代码,
代码语言:txt复制 <!--浮动按钮 获取poi-->
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@ id/fab_poi"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_alignParentBottom="true"
android:layout_margin="20dp"
android:clickable="true"
android:onClick="queryPOI"
app:fabSize="mini"
android:src="@drawable/icon_favorite_red"
android:visibility="invisible"
app:backgroundTint="#FFF"
app:backgroundTintMode="screen"
app:hoveredFocusedTranslationZ="18dp"
app:pressedTranslationZ="18dp" />
<!--浮动按钮 清空marker-->
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@ id/fab_clear_marker"
android:layout_width="wrap_content"
android:layout_above="@ id/fab_poi"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_marginEnd="20dp"
android:clickable="true"
android:onClick="clearAllMarker"
app:fabSize="mini"
android:src="@drawable/icon_clear"
app:backgroundTint="#FFF"
android:visibility="invisible"
app:backgroundTintMode="screen"
app:hoveredFocusedTranslationZ="18dp"
app:pressedTranslationZ="18dp" />
图标
这里我改变了浮动按钮的大小,然后增加了一个删除标点的按钮,当点击地图时显示这个浮动按钮,然后点击按钮时清空地图,当然这个清空要稍微麻烦一点,特别是你地图上有多个标点的时候。
代码语言:txt复制 //浮动按钮 清空地图标点
private FloatingActionButton fabClearMarker;
//标点列表
private List<Marker> markerList = new ArrayList<>();
写一个添加地图标点的方法
代码语言:txt复制 /**
* 添加地图标点
*
* @param latLng
*/
private void addMarker(LatLng latLng) {
//显示浮动按钮
fabClearMarker.show();
//添加标点
Marker marker = aMap.addMarker(new MarkerOptions().position(latLng).snippet("DefaultMarker"));
markerList.add(marker);
}
在地图点击时调用。
然后新写一个clearAllMarker方法
代码语言:txt复制 /**
* 清空地图Marker
*
* @param view
*/
public void clearAllMarker(View view) {
if (markerList != null && markerList.size()>0){
for (Marker markerItem : markerList) {
markerItem.remove();
}
}
fabClearMarker.hide();
}
清空标点,下面运行一下看看效果。
OK,就是这样的。
⑤ 绘制动画效果Marker
在addMarker方法中,添加如下代码:
代码语言:txt复制 //设置标点的绘制动画效果
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();
添加位置如下图所示:
上面的代码要注意导包的问题,不是Android自带的包而是高德SDK里面的
这段代码的意思就是配置一个旋转动画,然后设置旋转的角度和旋转所需要的时间,之后设置给marker。就可以,下面来看看效果吧。
这个动画是逆时针的,可以自己根据需要的效果进行更改。
当然可能这一个动画并不能满足你的需求,SDK中还提供了其他的,比如缩放动画、位移动画、透明度动画、渐变动画。它们都继承自Animation。
可以根据里面的参数进行配置然后达到你要的效果,那么就Marker的绘制动画效果就说到这,如果你有需要我用代码说明其他动画的需求,可以评论一下,我根据你的需求加上去。
⑥ Marker的点击和拖拽事件
先来看看Marker的点击事件,实现AMap.OnMarkerClickListener。
然后在initMap()方法中配置。
之后重写onMarkerClick方法:
代码语言:txt复制 /**
* Marker点击事件
* @param marker
* @return
*/
@Override
public boolean onMarkerClick(Marker marker) {
showMsg("点击了标点");
return true;
}
下面运行试一下:
点击事件就写好了,至于点击之后你要做什么,就看你的需求了。
下面就是拖拽事件了,实现AMap.OnMarkerDragListener。
依然在initMap中设置。
然后实现方法这里有三个方法需要重写。
代码语言:txt复制 /**
* 开始拖动
* @param marker
*/
@Override
public void onMarkerDragStart(Marker marker) {
Log.d(TAG,"开始拖动");
}
/**
* 拖动中
* @param marker
*/
@Override
public void onMarkerDrag(Marker marker) {
Log.d(TAG,"拖动中");
}
/**
* 拖动完成
* @param marker
*/
@Override
public void onMarkerDragEnd(Marker marker) {
Log.d(TAG,"拖动完成");
}
我在三个方法中都打印了日志,
还有一步别忘记了,增加marker的可拖动属性,默认为false。
下面运行一下:
来看看日志
OK,这样就可以了。
⑦ 绘制 InfoWindow
标点也是可以携带一些信息的,而这个信息可以由InfoWindow(信息窗体)展示处理出来。
首先应该显示出来这个infoWindow,上面我们写了这个Marker的点击事件,那么可以在点击的时候显示InfoWindow,再点击就显示。
现在addMarker方法中设置InfoWindow中信息的信息。
然后在onMarkerClick方法中,通过marker.isInfoWindowShown()判断当前Marker的InfoWindow是否显示,之后通过showInfoWindow来显示,hideInfoWindow来隐藏。
代码语言:txt复制 /**
* Marker点击事件
*
* @param marker
* @return
*/
@Override
public boolean onMarkerClick(Marker marker) {
//showMsg("点击了标点");
//显示InfoWindow
if (!marker.isInfoWindowShown()) {
//显示
marker.showInfoWindow();
} else {
//隐藏
marker.hideInfoWindow();
}
return true;
}
然后运行一下:
如果你希望在绘制Marker的时候就出现这个InfoWindow,你可以这样做,
自行运行一下即可。
刚才是使用了自带的样式,其实InfoWindow是可以自己定义样式的,首先添加两个图片。
建议在我的源码里面复制,直接在博客中保存图片会有问题。
下面在layout下创建两个xml。
custom_info_contents.xml
代码语言:txt复制<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<ImageView
android:id="@ id/badge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="5dp"
android:adjustViewBounds="true" >
</ImageView>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical" >
<TextView
android:id="@ id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:ellipsize="end"
android:singleLine="true"
android:textColor="#ff000000"
android:textSize="14dp"
android:textStyle="bold" />
<TextView
android:id="@ id/snippet"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:singleLine="true"
android:textColor="#ff7f7f7f"
android:textSize="14dp" />
</LinearLayout>
</LinearLayout>
custom_info_window.xml
代码语言:txt复制<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="100dp"
android:layout_height="100dp"
android:background="@mipmap/custom_info_bubble"
android:orientation="horizontal" >
<ImageView
android:id="@ id/badge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="5dp" >
</ImageView>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical" >
<TextView
android:id="@ id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:ellipsize="end"
android:singleLine="true"
android:textColor="#ff000000"
android:textSize="14dp"
android:textStyle="bold" />
<TextView
android:id="@ id/snippet"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:singleLine="true"
android:textColor="#ff7f7f7f"
android:textSize="14dp" />
</LinearLayout>
</LinearLayout>
下面回到MainActivity中,先实现AMap.InfoWindowAdapter
然后在initMap添加InfoWindow适配器监听
之后重写两个方法。
代码语言:txt复制 /**
* 修改内容
*
* @param marker
* @return
*/
@Override
public View getInfoContents(Marker marker) {
return null;
}
/**
* 修改背景
*
* @param marker
*/
@Override
public View getInfoWindow(Marker marker) {
return null;
}
下面将会一起修改,修改后的代码如下:
代码语言:txt复制 /**
* 修改内容
*
* @param marker
* @return
*/
@Override
public View getInfoContents(Marker marker) {
View infoContent = getLayoutInflater().inflate(
R.layout.custom_info_contents, null);
render(marker, infoContent);
return infoContent;
}
/**
* 修改背景
*
* @param marker
*/
@Override
public View getInfoWindow(Marker marker) {
View infoWindow = getLayoutInflater().inflate(
R.layout.custom_info_window, null);
render(marker, infoWindow);
return infoWindow;
}
/**
* 渲染
*
* @param marker
* @param view
*/
private void render(Marker marker, View view) {
((ImageView) view.findViewById(R.id.badge))
.setImageResource(R.drawable.icon_yuan);
//修改InfoWindow标题内容样式
String title = marker.getTitle();
TextView titleUi = ((TextView) view.findViewById(R.id.title));
if (title != null) {
SpannableString titleText = new SpannableString(title);
titleText.setSpan(new ForegroundColorSpan(Color.RED), 0,
titleText.length(), 0);
titleUi.setTextSize(15);
titleUi.setText(titleText);
} else {
titleUi.setText("");
}
//修改InfoWindow片段内容样式
String snippet = marker.getSnippet();
TextView snippetUi = ((TextView) view.findViewById(R.id.snippet));
if (snippet != null) {
SpannableString snippetText = new SpannableString(snippet);
snippetText.setSpan(new ForegroundColorSpan(Color.GREEN), 0,
snippetText.length(), 0);
snippetUi.setTextSize(20);
snippetUi.setText(snippetText);
} else {
snippetUi.setText("");
}
}
然后运行:
⑧ InfoWindow的点击事件
刚才的InfoWindow也是可以点击的,先实现AMap.OnInfoWindowClickListener
同样在
然后重写onInfoWindowClick方法。
代码语言:txt复制 /**
* InfoWindow点击事件
*
* @param marker
*/
@Override
public void onInfoWindowClick(Marker marker) {
showMsg("弹窗内容:标题:" marker.getTitle() "n片段:" marker.getSnippet());
}
运行效果如下:
这样就可以了。
⑨ 改变地图中心点
我们在实际使用中通常会有这样的操作,希望点击一下就可以移动到所在地,这其实是比较容易做到的,回顾我们现在是一进入地图就会定位到当前所在地,而当我点击地图上其他位置时,会增加一个标点,而我们要做的就是把这个标点作为地图中心,然后移动地图位置即可。
那么思路已经有了,很简单下面来写代码,代码也是很简单的,首先新增一个updateMapCenter方法,里面传递一个LatLng对象作为入参。方法如下:
代码语言:txt复制 /**
* 改变地图中心位置
* @param latLng 位置
*/
private void updateMapCenter(LatLng latLng) {
// CameraPosition 第一个参数: 目标位置的屏幕中心点经纬度坐标。
// CameraPosition 第二个参数: 目标可视区域的缩放级别
// CameraPosition 第三个参数: 目标可视区域的倾斜度,以角度为单位。
// CameraPosition 第四个参数: 可视区域指向的方向,以角度为单位,从正北向顺时针方向计算,从0度到360度
CameraPosition cameraPosition = new CameraPosition(latLng, 16, 30, 0);
//位置变更
CameraUpdate cameraUpdate = CameraUpdateFactory.newCameraPosition(cameraPosition);
//改变位置
aMap.moveCamera(cameraUpdate);
}
先通过CameraPosition配置一个中心位置对象,对象需要四个参数,在注释中已经说明了,然后通过CameraUpdate配置一个位置改变对象,传入刚才的cameraPosition。最后就是在地图上改变位置了。通过aMap.moveCamera()。这个方法比较简单,但别忘记了去调用,在onMapClick调用即可。
那么下面运行一下吧。
现在的确是移动过去了,不过好像是一闪而过,感觉用户的体验不是很好,而在使用高德地图APP的时候感觉很平滑的切换中心点,这个其实SDK中也提供了,你只需要把moveCamera改成animateCamera就可以了做到平滑移动,而不会显得很突兀了。这个animateCamera是一个多参方法,里面还可以传动画的时间,取消的回调等等。
那么我们只需要有一个动画就行了,这就很简单。
代码语言:txt复制 //带动画的移动
aMap.animateCamera(cameraUpdate);
下面运行一下吧。
嗯,可能GIF上看着效果不是特别的明显,在自己手机上去体验一下就知道了,这个地图平移动画还是很不错的。