首先我们来看一下饿了么新增地址的效果。
总结一下需要实现的效果有哪些:
定位
滑动地图时能够及时的获取到地图中心点的位置信息
滑动时以及停止滑动的时的动画效果
点击位置信息后获取附近位置列表
首先我们来分析一下这几个问题:定位的话可以使用高德地图,当然也可以使用百度地图,最终我们采用的是高德地图;滑动时要获取中心点的位置信息这个功能需要调用高德地图的相关API来实现,具体是什么后面讲;第三个动画效果,我们需要分析一下有哪些状态以及触发的条件是什么;最后一个和第二个一样,也是调用相关的API就可以了。下面我们来一个一个分析。
前提
需要说明一下:关于地图的集成以及一些相关的配置信息我就不展开了,详细的配置教程可以去官网查看。
定位
首先需要在布局文件中添加<MapView>
<com.amap.api.maps.MapView android:id="@+id/map" android:layout_width="match_parent" android:layout_height="match_parent"/>
然后需要在Activity中添加对地图的生命周期管理
public class MainActivity extends Activity { MapView mMapView = null; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //获取地图控件引用 mMapView = (MapView) findViewById(R.id.map); //在activity执行onCreate时执行mMapView.onCreate(savedInstanceState),创建地图 mMapView.onCreate(savedInstanceState); } protected void onDestroy() { super.onDestroy(); //在activity执行onDestroy时执行mMapView.onDestroy(),销毁地图 mMapView.onDestroy(); } protected void onResume() { super.onResume(); //在activity执行onResume时执行mMapView.onResume (),重新绘制加载地图 mMapView.onResume(); } protected void onPause() { super.onPause(); //在activity执行onPause时执行mMapView.onPause (),暂停地图的绘制 mMapView.onPause(); } protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); //在activity执行onSaveInstanceState时执行mMapView.onSaveInstanceState (outState),保存地图当前的状态 mMapView.onSaveInstanceState(outState); } }
最后需要获取到AMap,AMap是地图的控制器。我们后面的很多操作都需要用到它。
//定义了一个地图view mapView = (MapView) findViewById(R.id.map); mapView.onCreate(savedInstanceState);// 此方法须覆写,虚拟机需要在很多情况下保存地图绘制的当前状态。 //初始化地图控制器对象 AMap aMap; if (aMap == null) { aMap = mapView.getMap(); }
最后我们的地图就可以显示了。
不出意味的话应该是这种效果:
(图片来自高德地图文档)
很显然这不符合我们的UI要求,所以我们还需要做一些定制化。在MapView中对UI处理的类是UiSettings。
if (mAMap == null) { mAMap = mMapView.getMap(); //获取UiSettings实例 UiSettings uiSettings = mAMap.getUiSettings(); //设置缩放控件 uiSettings.setZoomControlsEnabled(false); //设置定位按钮 uiSettings.setMyLocationButtonEnabled(false); }
注释已经写好了,所以我就不多重复了。
地图是显示了但并没有定位到我们当前的位置,所以下面还需要实现定位的功能。
高德地图中管理定位的类是AMapLocationClient ,AMapLocationClientOption 是用于配置定位。OnLocationChangedListener是定位监听器。
//初始化定位 mLocationClient = new AMapLocationClient(getApplicationContext()); //设置定位回调监听 mLocationClient.setLocationListener(this); //初始化定位参数 mLocationOption = new AMapLocationClientOption(); //设置定位模式为高精度模式,Battery_Saving为低功耗模式,Device_Sensors是仅设备模式 mLocationOption.setLocationMode(AMapLocationClientOption.AMapLocationMode.Hight_Accuracy); //设置是否返回地址信息(默认返回地址信息) mLocationOption.setNeedAddress(true); //设置是否只定位一次,默认为false mLocationOption.setOnceLocation(true); //设置是否强制刷新WIFI,默认为强制刷新 mLocationOption.setWifiActiveScan(true); //设置是否允许模拟位置,默认为false,不允许模拟位置 mLocationOption.setMockEnable(false); //设置定位间隔,单位毫秒,默认为2000ms mLocationOption.setInterval(2000); //给定位客户端对象设置定位参数 mLocationClient.setLocationOption(mLocationOption); //启动定位 mLocationClient.startLocation();
然后我们需要在onLocationChanged()中监听位置信息。所有的定位信息都在AMapLocation中。
//定位成功回调信息,设置相关消息 amapLocation.getLocationType();//获取当前定位结果来源,如网络定位结果,详见官方定位类型表 amapLocation.getLatitude();//获取纬度 amapLocation.getLongitude();//获取经度 amapLocation.getAccuracy();//获取精度信息 SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); Date date = new Date(amapLocation.getTime()); df.format(date);//定位时间 amapLocation.getAddress();//地址,如果option中设置isNeedAddress为false,则没有此结果,网络定位结果中会有地址信息,GPS定位不返回地址信息。 amapLocation.getCountry();//国家信息 amapLocation.getProvince();//省信息 amapLocation.getCity();//城市信息 amapLocation.getDistrict();//城区信息 amapLocation.getStreet();//街道信息 amapLocation.getStreetNum();//街道门牌号信息 amapLocation.getCityCode();//城市编码 amapLocation.getAdCode();//地区编码
定位是实现了,但还需要我们和MapView进行关联。这时我们需要调用setLocationSource()方法,该方法接收LocationSource,所以我们需要实现这个接口,这个接口需要重写两个方法:activate() , deactivate()方法。在activate()中我是做定位相关的初始化和配置操作的;在deactivate()中做释放资源。
在获取到定位的经纬度后我们需要把当前的地图移到定位点,这个操作在高德地图中被称为camera,我的理解是视角的意思,也就是说需要把MapView当前的视角移到定位点。
//设置缩放级别 mAMap.moveCamera(CameraUpdateFactory.zoomTo(17)); //将地图移动到定位点 mAMap.moveCamera(CameraUpdateFactory.changeLatLng(new LatLng(amapLocation.getLatitude(), amapLocation.getLongitude()))); //点击定位按钮 能够将地图的中心移动到定位点 mListener.onLocationChanged(amapLocation);
mListener是activate()方法中的参数。
这样配置以后定位的效果基本上已经实现了。但还是有几个小问题:
定位效果
定位蓝点
对于第一个问题,由于我们在上面设置了uiSettings.setMyLocationButtonEnabled(false)所以需要我们自己实现定位的效果,如果你设置成true(默认是true)就不需要自己实现了,具体的还是要根据你的业务需求。由于项目的需求我在代码中设置了只定位一次(后面可能会改,但目前测试来讲并没有遇到问题),所以我们把定位的数据保存了起来,在点击定位按钮后我们调用了mAMap.animateCamera(CameraUpdateFactory.changeLatLng(new LatLng(mLocatingModel.getLatitude(), mLocatingModel.getLongitude())));这个方法的作用是改变MapView的视角,也就是把当前地图显示的位置切换到需要的位置。
关于定位蓝点的问题,官网提供了自定义的方法。主要是使用MyLocationStyle这个类,做好配置以后,调用mAMap.setMyLocationStyle(myLocationStyle)来实现效果。
最终我们的效果是这样的:
滑动获取中心点位置信息
刚开始的时候我觉得没法实现,原因是高德地图的文档中并没有提供相关的接口,但是我搜了一下发现是有的(高德地图的文档写的是真差,很多解释和没解释一样,全靠自己写demo来一个一个了解)。
实现的核心方法是setOnCameraChangeListener()方法,我们需要监听AMap.OnCameraChangeListener这个接口。这个接口需要重写两个方法:onCameraChange() , onCameraChangeFinish();第一个视角改变的回调,第二个是视角改变完成以后的回调。
为了显示中间位置,需要绘制一个图标。这个图标的位置是屏幕的中心点(可能有偏差,如果与实际不符,需要把中间点的坐标设置成MapView的中间位置)
设置好以后我们就可以在每次滑动以后在onCameraChangeFinish()中的参数CameraPosition获取到地图中心点的位置信息。但是返回的只有经纬度,可视区域指向的方向等信息,并没有一些位置的文字信息,所以我们还需要调用高德地图的搜索功能。
高德地图的搜索功能是调用GeocodeSearch的getFromLocationAsyn()方法实现的,需要传入RegeocodeQuery实例,最后我们还要监听GeocodeSearch.OnGeocodeSearchListener接口,由于我们是通过经纬度来查询位置信息(逆地理编码),所以我们需要在onRegeocodeSearched()中获取位置信息。
/** * 逆地理编码返回接口 * @param regeocodeResult * @param i */ public void onRegeocodeSearched(RegeocodeResult regeocodeResult, int i) { RegeocodeAddress regeocodeAddress = regeocodeResult.getRegeocodeAddress(); String formatAddress = regeocodeAddress.getFormatAddress(); mAddressInfoTv.setText(formatAddress); }
这样我们的第二个问题也已经解决了。
动画效果
通过观察饿了么的效果图我们可以发现:但地图在移动的过程中,中间点是需要上移的,同时下方的阴影变大,当松手以后返回原来的位置以及效果。刚开始的时候我是想通过高德的API来实现,我最终找到的接口是:setAMapGestureListener(),这个方法是监听地图手势识别,需要传入AMapGestureListener实例。
刚开始我是在onFling() , onScroll() , onMapStable()中来控制动画的,当最终实现的效果总是有点瑕疵,于是我就放弃监听这个回调了。
最终我是监听AMap.OnMapTouchListener来实现的。
mAMap.setOnMapTouchListener(new AMap.OnMapTouchListener() { public void onTouch(MotionEvent motionEvent) { int action = motionEvent.getAction(); if(action == MotionEvent.ACTION_DOWN){ isUp = false; }else if(action == MotionEvent.ACTION_MOVE){ isUp = false; Log.i(TAG, "onTouch: is Move"); }else if(action == MotionEvent.ACTION_UP){ isUp = true; }else if(action == MotionEvent.ACTION_CANCEL){ isUp = false; } } });
在这个里面我设置了一个标志位isUp来判断手指有没有抬起。然后在onCameraChange() , onCameraChangeFinish()方法中来控制动画。可能有的人会疑惑为什么要在这两个方法中呢?其实之前我已经讲过了,当地图的视角改变了以后是会回调这两个方法的。
public void onCameraChange(CameraPosition cameraPosition) { if (!isFirstMoveCamera) { startLocatingAnimator(); } }
在方法中我们添加了isFirstMoveCamera这个标志位,这个标志位的作用是判断是不是第一次视角改变(定位时视角会改变,也会回调这个方法,根据需求是不需要动画效果的)。
public void onCameraChangeFinish(CameraPosition cameraPosition) { if (isUp && !isFirstMoveCamera) { stopLocatingAnimator(); } isFirstMoveCamera = false; LogUtil.i(TAG,"Latitude: " + cameraPosition.target.latitude + ", Longitude: " + cameraPosition.target.longitude); }
之所以在onCameraChangeFinish()中停止动画,原因是就算手指已经抬起,当可能由于惯性的原因此时地图还在改变而这个时候动画是不能停止的.动画停止的条件应该是手指抬起并且地图停止了,所以我们在onCameraChangeFinish()方法中停止动画。
具体的动画效果我就不细说了,但有一点需要提一下:为了避免动画的重复执行,我们需要在添加一个动画是否开始的标志位isAnimatorStarted来控制一下,否则的话当你移动地图后手指按住地图的时候,动画会反复的执行。所以你可以在onAnimationStart()中把isAnimatorStarted设置成true,在执行动画的时候做个判断就可以了,当然你可以有其他的方式来做处理,这只是我的实现方式而已。
获取附近位置列表
获取附近位置列表是使用PoiSearch来实现的。
//构造PoiSearch.Query实例,第一个参数是keyword,第二个是Poi搜索类型,第三个是poi搜索区域 PoiSearch.Query query = new PoiSearch.Query("","",""); //设置每页最多返回多少条position query.setPageSize(PER_PAGE_SIZE); //设置查询页码 query.setPageNum(currentPage); //生成poiSearch实例 mPoiSearch = new PoiSearch(mContext,query); //周边检索POI mPoiSearch.setBound(new PoiSearch.SearchBound(new LatLonPoint(mPlaceModel.getLatitude(),mPlaceModel.getLongitude()),1000)); //设置搜索监听器 mPoiSearch.setOnPoiSearchListener(this); //发送请求 mPoiSearch.searchPOIAsyn();
最终我们在onPoiSearched()方法中获取信息
public void onPoiSearched(PoiResult result, int rCode) { //解析result获取POI信息 }
总结
最终我们实现的效果图是怎样的呢?