初衷
因为国内很多基于app使用到了定位功能,因此写一篇仿照微信聊天定位的博客供大家参考。此次定位主要选用高德api进行代码开发。也许小伙伴会问定位为什么选用高德api 而不选用百度或者使用原生态android 定位功能!原因有如下几点
1、谷歌原始定位api 具有局限性,在室内或者其他复杂情况也许出现定位不准
2、不选用百度api 是因为百度api 文档没高德写的规范,百度api demo 全部嵌套在一起很难读懂,不管文档或者demo 都很难理解
3、高德api的官方demo 会把每一个功能拆分,这样利于理解,文档也比较规范。利于阅读!
4、在开发中我小伙伴遇到的坑,百度比较多。
那下面小伙伴快来看看仿微信聊天定位的效果图吧
前期配置
因为我们是基于高德开发。所以要登录高德api官网并申请key
在key申请过程中sha1 也许让小伙伴有点手足无措。你可以用代码方式进行获取
public class Sha1Utils {
//这个是获取SHA1的方法
public static String getCertificateSHA1Fingerprint(Context context) {
//获取包管理器
PackageManager pm = context.getPackageManager();
//获取当前要获取SHA1值的包名,也可以用其他的包名,但需要注意,
//在用其他包名的前提是,此方法传递的参数Context应该是对应包的上下文。
String packageName = context.getPackageName();
//返回包括在包中的签名信息
int flags = PackageManager.GET_SIGNATURES;
PackageInfo packageInfo = null;
try {
//获得包的所有内容信息类
packageInfo = pm.getPackageInfo(packageName, flags);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
//签名信息
Signature[] signatures = packageInfo.signatures;
byte[] cert = signatures[0].toByteArray();
//将签名转换为字节数组流
InputStream input = new ByteArrayInputStream(cert);
//证书工厂类,这个类实现了出厂合格证算法的功能
CertificateFactory cf = null;
try {
cf = CertificateFactory.getInstance("X509");
} catch (Exception e) {
e.printStackTrace();
}
//X509证书,X.509是一种非常通用的证书格式
X509Certificate c = null;
try {
c = (X509Certificate) cf.generateCertificate(input);
} catch (Exception e) {
e.printStackTrace();
}
String hexString = null;
try {
//加密算法的类,这里的参数可以使MD4,MD5等加密算法
MessageDigest md = MessageDigest.getInstance("SHA1");
//获得公钥
byte[] publicKey = md.digest(c.getEncoded());
//字节到十六进制的格式转换
hexString = byte2HexFormatted(publicKey);
} catch (NoSuchAlgorithmException e1) {
e1.printStackTrace();
} catch (CertificateEncodingException e) {
e.printStackTrace();
}
return hexString;
}
//这里是将获取到得编码进行16进制转换
private static String byte2HexFormatted(byte[] arr) {
StringBuilder str = new StringBuilder(arr.length * 2);
for (int i = 0; i < arr.length; i++) {
String h = Integer.toHexString(arr[i]);
int l = h.length();
if (l == 1)
h = "0" + h;
if (l > 2)
h = h.substring(l - 2, l);
str.append(h.toUpperCase());
if (i < (arr.length - 1))
str.append(':');
}
return str.toString();
}
}
之后把相应高德jar并 导入as 并进行相关配置即可进行下一步了
显示地图
我们要进行定位,首先要能让我们app能成功加载图层。加载图层的核心类有2个
1、MapView 这个就是我们看到的看到的自定义控件。地理位置城市乡镇,铁路学校都是又他来显示
2、AMap 这个可以说是对MapView 进行管控的类。比如说控制自定义图层的缩放比例,设置移动监听,或者添加一个一个的坐标等
具体代码如下
public class WeiXinLocationActivity extends AppCompatActivity {
private MapView mapView;
private AMap aMap;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_weixin_location);
mapView = findViewById(R.id.map);
mapView.onCreate(savedInstanceState);
init();
}
private void init() {
// 如果没有map 就获取map
if (aMap == null) {
aMap = mapView.getMap();
}
aMap.moveCamera(CameraUpdateFactory.zoomTo(17));
/** 设置监听 */
// aMap.setLocationSource(this);// 设置定位监听
aMap.setMyLocationEnabled(true);// 设置为true表示显示定位层并可触发定位,false表示隐藏定位层并不可触发定位,默认是false
// 设置定位的类型为定位模式:定位(AMap.LOCATION_TYPE_LOCATE)、跟随(AMap.LOCATION_TYPE_MAP_FOLLOW)
//地图模式可选类型:MAP_TYPE_NORMAL,MAP_TYPE_SATELLITE,MAP_TYPE_NIGHT
aMap.setMapType(AMap.MAP_TYPE_SATELLITE);
}
@Override
protected void onDestroy() {
super.onDestroy();
//在activity执行onDestroy时执行mMapView.onDestroy(),销毁地图
mapView.onDestroy();
}
@Override
protected void onResume() {
super.onResume();
//在activity执行onResume时执行mMapView.onResume (),重新绘制加载地图
mapView.onResume();
}
@Override
protected void onPause() {
super.onPause();
//在activity执行onPause时执行mMapView.onPause (),暂停地图的绘制
mapView.onPause();
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
//在activity执行onSaveInstanceState时执行mMapView.onSaveInstanceState (outState),保存地图当前的状态
mapView.onSaveInstanceState(outState);
}
}
运行出来就有这样的效果了
定位
既然已经成功加载了图层,那么我们下一步就该进行定位了。
进行定位的时候我们肯定会得到高德对定位请求的数据返回成功或者失败,那么我们怎么知道定位成功或者失败呢?那我们就需要实现AMapLocationListener这个接口会对你请求的定位给你返回相应的数据!之后我们就可以进行相应操作
public class WeiXinLocationActivity extends AppCompatActivity implements
AMapLocationListener
所以要实现其接口
@Override
public void onLocationChanged(AMapLocation amapLocation) {
if (amapLocation != null) {
if (amapLocation.getErrorCode() == 0) {
//定位成功回调信息,设置相关消息
amapLocation.getLocationType();//获取当前定位结果来源,如网络定位结果,详见定位类型表
double lat= amapLocation.getLatitude();//获取纬度
double lon= amapLocation.getLongitude();//获取经度
amapLocation.getAccuracy();//获取精度信息
if (mk == null) {
// 添加一个标记物
AddMark(lat,lon);
}
LatLng ll = new LatLng(lat
,lon);
//定位成功移动图层至定位区域
aMap.animateCamera(CameraUpdateFactory
.newCameraPosition(new CameraPosition(ll, 17, 0, 0)));
} else {
//显示错误信息ErrCode是错误码,errInfo是错误信息,详见错误码表。
Log.e("AmapError","location Error, ErrCode:"
+ amapLocation.getErrorCode() + ", errInfo:"
+ amapLocation.getErrorInfo());
}
}
}
定位成功我们则需要把图层移动到相应区域。所以我们会用到
aMap.animateCamera(CameraUpdateFactory.newCameraPosition(new CameraPosition(ll, 17, 0, 0)))方法, 而传入的参数ll是代表经纬度的一个参数。
既然定位成功或者失败我们都知道返回接口了。那么我们怎么发起定位请求呢?
mlocationClient = new AMapLocationClient(this);
//初始化定位参数
mLocationOption = new AMapLocationClientOption();
//设置定位监听
mlocationClient.setLocationListener(this);
//设置定位模式为高精度模式,Battery_Saving为低功耗模式,Device_Sensors是仅设备模式
mLocationOption.setLocationMode(AMapLocationClientOption.AMapLocationMode.Hight_Accuracy);
//设置定位间隔,单位毫秒,默认为2000ms
mLocationOption.setInterval(2000);
//设置定位参数
mlocationClient.setLocationOption(mLocationOption);
// 此方法为每隔固定时间会发起一次定位请求,为了减少电量消耗或网络流量消耗,
// 注意设置合适的定位时间的间隔(最小间隔支持为1000ms),并且在合适时间调用stopLocation()方法来取消定位请求
// 在定位结束后,在合适的生命周期调用onDestroy()方法
// 在单次定位情况下,定位无论成功与否,都无需调用stopLocation()方法移除请求,定位sdk内部会移除
//启动定位
mlocationClient.startLocation();
这样我们就发起了定位了,小伙伴快来看看效果吧
搜索附近之poi
那我们什么时候会用到附近搜索呢?
1、初始化定位成功会用到搜索附近,这样我们listview 才会有附近兴趣点数据。
2、当我们滑动屏幕图层开始移动的时候,mark角标也会移动,那么我们也应该搜索附近。
既然以上二点都会搜索附近。我们可以封装一个方法。这样我们就可以直接调用方法!
因为一个点是由经纬度组成,所以方法我们应该传入经纬度2个参数。而看了高德api。我们方法这样是不够的,还需要传入城市编码才可以。
搜索封装代码如下
private void PoiSearch(double lat, double lon, String cityCode) {// AMapLocation
LatLonPoint point = new LatLonPoint(lat, lon);
query = new PoiSearch.Query(
"",
"汽车维修|摩托车服务|餐饮服务|购物服务|生活服务|体育休闲服务|医疗保健服务|住宿服务|风景名胜|商务住宅|政府机构及社会团体|科教文化服务|交通设施服务|金融保险服务|道路附属设施|地名地址信息|公共设施",
cityCode);
// "公共设施|商务住宅"
query.setPageSize(20);// 设置每页最多返回多少条poiitem
poiSearch = new PoiSearch(this, query);
poiSearch.setBound(new PoiSearch.SearchBound(point, 1000));// 设置周边搜索的中心点以及区域
poiSearch.setOnPoiSearchListener(this);//
poiSearch.searchPOIAsyn();// 开始搜索
}
封装的代码写好之后,我们把刚才上面二点需要进行调用搜索的地方进行代码补充就好!
现在我们都封装好了搜索的方法。搜索之后哪我们怎么得知搜索的结果呢?其实很简单,和定位一样只要实现相应的接口PoiSearch.OnPoiSearchListener,高德就会告诉我们搜索的返回值了!然后我们把相应的返回值填充到listview的item上
代码如下
@Override
public void onPoiSearched(PoiResult result, int code) {
if (code == 1000) {//成功是1000 其他都是失败
if (result != null & result.getQuery() != null) {
ArrayList<PoiItem> list = result.getPois();
Collections.sort(list, new DisComparator());
// 点击选择listview 第一位是自己选择的地方
list.add(0, firstLocation);
adapter.setData(list);
// 第一次进入定位默认选中第一个
if (list.size() > 0 && isFristEntry == true) {
selectWhere = list.get(0);
isFristEntry = false;
}
// 判断是拖拽 选中第一个 是点击选中点击
if (isTouchMap) {
selectWhere = list.get(0);
adapter.showSelect(0);
}
lv.setVisibility(View.VISIBLE);
tvLoading.setVisibility(View.INVISIBLE);
}
} else {
Toast.makeText(getApplicationContext(),
"失败"+code, Toast.LENGTH_LONG).show();
}
}
小伙伴们快来看看效果,还不错吧
mark滑动和ListView点击在图层的位移
现在我们大体做的差不多了。那就逐步完善吧
1、完善点击listview item mark的位移。这个很简单,其实小伙伴只需要给listview设置一个点击监听,然后获取点击item的经纬度,让mark位移就好
private void initonClick() {
lv.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> arg0, View arg1,
int position, long arg3) {
selectWhere = (PoiItem) adapter.getItem(position);
LatLng ll = new LatLng(selectWhere.getLatLonPoint()
.getLatitude(), selectWhere.getLatLonPoint()
.getLongitude());
mk.setPosition(ll);
// 图层位置的变化动画
// aMap.animateCamera(CameraUpdateFactory.newCameraPosition(new
// CameraPosition(ll, 16, 0, 0)), 1500, null);
aMap.animateCamera(CameraUpdateFactory
.newCameraPosition(new CameraPosition(ll, 17, 0, 0)));
adapter.showSelect(position);
isTouchMap = false;//不是滑动,所以不需要搜索附近poi
}
});
}
2,滑动自定义图层,mark随之位移,并搜索附近兴趣点。
其实我们只需要实现这2个监听器。就可以知道图层被手指触摸而滑动了,并且也可以知道图层多久结束滑动!
// 移动监听
aMap.setOnCameraChangeListener(this);
// 触摸自定义监听
aMap.setOnMapTouchListener(this);
aMap.setOnCameraChangeListener(this);
// 触摸自定义监听
aMap.setOnMapTouchListener(this);
然后我们实现他们相应的方法即可
//图层被手指触摸了
@Override
public void onTouch(MotionEvent motionEvent) {
switch (motionEvent.getAction()) {
case MotionEvent.ACTION_MOVE://表示手指在滑动自定义图层
isTouchMap = true;
break;
default:
break;
}
}
// 移动的mark
//图层在位移,因为手指触摸
@Override
public void onCameraChange(CameraPosition position) {
if (mk == null) {
LatLng ll = position.target;
AddMark(ll.latitude, ll.longitude);
}
mk.setPosition(position.target);
}
//图层结束,改变mark位置,并搜索附近兴趣点
@Override
public void onCameraChangeFinish(CameraPosition position) {
LatLng ll = position.target;
if (mk == null) {
AddMark(ll.latitude, ll.longitude);
}
mk.setPosition(position.target);
if (isTouchMap) {
PoiSearch(ll.latitude, ll.longitude, cityCode);
lv.setVisibility(View.INVISIBLE);
tvLoading.setVisibility(View.VISIBLE);
} else {
lv.setVisibility(View.VISIBLE);
tvLoading.setVisibility(View.INVISIBLE);
}
}
注意的是代码中有一个isTouchMap的布尔值。是为了来判断是手指滑动图层还是手指点击listview 的item。
如果是手指滑动图层,则需要搜索附近兴趣点。否则则不需要搜索!
代码写完。小伙伴快来看看效果吧
如果小伙伴喜欢可以去github上下载代码