在上一篇文章中,我们讲到了高德地图之拾取地点,今天接下来总结路线规划,整个实现的过程大致如下:
获取终点 规划路线 路线的选择与路线的绘制 路线结果的展示首先来看看需要实现的效果:
整个布局比较简单,唯一需要注意的就是Tablayout的使用因为在在使用这个控件的时候栽过跟头,,先看看源码:
mTabLayout = (TabLayout) findViewById(R.id.tabs); //tab的字体选择器,默认灰色,选择时白色 mTabLayout.setTabTextColors(Color.LTGRAY, Color.WHITE); //设置tab的下划线颜色,默认是粉红色 mTabLayout.setSelectedTabIndicatorColor(Color.WHITE); mTabLayout.addTab(mTabLayout.newTab().setText("驾车")); mTabLayout.addTab(mTabLayout.newTab().setText("步行")); mTabLayout.addTab(mTabLayout.newTab().setText("骑车"));因为我当时在使用的过程中,因为增加了其它的属性,导致Tablayout的tab不能这样占满屏宽,要么是tab剧中显示,要么是居左显示,后来删除所有的属性后,直接现在这个样式了。 接下来开始总结我们上面实现的过程。
这个其实已经很简单了,因为我们上一次已经实现了高德地图之拾取地点,所以这里在点击目的地的EditText的时候调用该Activity即可,代码如下:
@Override public void onClick(View v) { switch (v.getId()){ case R.id.rl_tv_end: Intent intent = new Intent(this,PiclocationActivity.class);//拾取坐标点 startActivityForResult(intent,0); endList.clear(); break; } } /** * 获取终点信息 * @param requestCode * @param resultCode * @param intent */ @Override protected void onActivityResult(int requestCode, int resultCode, Intent intent) { super.onActivityResult(requestCode, resultCode, intent); if(resultCode==RESULT_OK){ tvEnd.setText("到 "+intent.getStringExtra("address")); LatLonPoint endLp = intent.getParcelableExtra("value"); endList.clear(); endList.add(new NaviLatLng(endLp.getLatitude(),endLp.getLongitude())); } }在获取终点的过程中,我们先是将终点的坐标集合清空,然后将最新的坐标点保存到集合中,并将目的地地址显示出来。
在实现规划路线的时候,我们需要获取一个AMapNavi对象,然后实现一个导航事件监听接口——AMapNaviListener。这个接口有许多抽象方法,但是我们必须要实现的有两个方法——路线规划成功以后回调规划结果:
/** * 初始化AMap对象 */ private void initMap() { if (amap == null) { ... mAMapNavi = AMapNavi.getInstance(getApplicationContext()); mAMapNavi.addAMapNaviListener(this); } } //多路线算路成功回调 onCalculateMultipleRoutesSuccess(int[] routeIds) //步行,驾车,骑行路径规划成功后的回调函数。(单条路线) onCalculateRouteSuccess() //步行或者驾车路径规划失败后的回调函数。 onCalculateRouteFailure(int errorInfo)在重写上面的接口回调函数之前,我们需要先实现路线规划,因为回调函数都是成功或者失败的处理方法,所以我们先去规划,规划了以后再来重写结果的函数。 因为驾车、步行、骑行等路线的规划都是不同的函数,而且我们都需要实现,所以我们需要增加一个int值的flag,来标识不同的路线规划,该标识默认为驾车。接下来我们看看路线规划的函数:
/** * 方法必须重写 */ @Override protected void onResume() { super.onResume(); mapview.onResume(); planRoute();//路线规划 } /** * 路线规划 */ private void planRoute() { mRecyclerView.setVisibility(View.GONE);//多条路线规划结果 oneWay.setVisibility(View.GONE);//一条路线规划结果 if(startList.size()>0 && endList.size()>0){ if(navigationType == 0){//驾车 int strategy=0; try { /** * 方法: * int strategy=mAMapNavi.strategyConvert(congestion, avoidhightspeed, cost, hightspeed, multipleroute); * 参数: * @congestion 躲避拥堵 * @avoidhightspeed 不走高速 * @cost 避免收费 * @hightspeed 高速优先 * @multipleroute 多路径 * * 说明: * 以上参数都是boolean类型,其中multipleroute参数表示是否多条路线,如果为true则此策略会算出多条路线。 * 注意: * 不走高速与高速优先不能同时为true * 高速优先与避免收费不能同时为true */ strategy = mAMapNavi.strategyConvert(true, false, false, true, true); } catch (Exception e) { e.printStackTrace(); } mAMapNavi.calculateDriveRoute(startList, endList, wayList, strategy); }else if(navigationType == 1){//步行 mAMapNavi.calculateWalkRoute(startList.get(0), endList.get(0)); }else{//骑行 mAMapNavi.calculateRideRoute(startList.get(0), endList.get(0)); } } }有三点需要说明:
无论起点坐标还是终点坐标,均可设置多个。当坐标以列表形式存放时,列表的尾点为实际导航点(起点或终点),其他坐标点为辅助信息,带有方向性,可有效避免算路到马路的另一侧。最多支持设置 4 个途经点。路径的计算策略包含单一策略和多策略,通过多策略,可计算出多条规划路径(最多3条)。路线规划的动作结束后,我们可以进行下一步了。
路线规划的结果有两种情况,一种是多条路线,还有一种是一条路线。前者才会有选择的余地,后者直接绘制路线即可。所以我们接下来看看多条路线的结果应该怎么处理?
/** * 多条路线计算结果回调2 * @param ints */ @Override public void onCalculateMultipleRoutesSuccess(int[] ints) { //清空地图上上次计算的路径列表。 routeOverlays.clear(); //清空mRecyclerView的数据集 ways.clear(); //获取规划的路线 HashMap<Integer, AMapNaviPath> paths = mAMapNavi.getNaviPaths(); for (int i = 0; i < ints.length; i++) { AMapNaviPath path = paths.get(ints[i]); if (path != null) { //绘制路线 drawRoutes(ints[i], path); //保存数据 ways.add(path); } } //选择路线,默认为第一条路线 changeRoute(); }上面有两个函数,分别是绘制路线与选择路线。当路线规划结果有多条的时候,我们需要将每条线路绘制在地图上,然后需要选择线路,就是将多条线路中当前默认选择的线路加粗给绘制出来。所以接下来看看这两个函数:
/** * 绘制路线 * @param routeId * @param path */ private void drawRoutes(int routeId, AMapNaviPath path) { //路线规划成果的标志位 calculateSuccess = true; //更新地图的状态 amap.moveCamera(CameraUpdateFactory.changeTilt(0)); RouteOverLay routeOverLay = new RouteOverLay(amap, path, this); routeOverLay.setTrafficLine(false); routeOverLay.addToMap(); //保存路线ID与路径 routeOverlays.put(routeId, routeOverLay); } /** * 选择路线 */ public void changeRoute() { /** * 计算出来的路径只有一条 */ if (routeOverlays.size() == 1) { //必须告诉AMapNavi 你最后选择的哪条路 mAMapNavi.selectRouteId(routeOverlays.keyAt(0)); return; } //路线的标志位 if (routeIndex >= routeOverlays.size()) routeIndex = 0; int routeID = routeOverlays.keyAt(routeIndex); //突出选择的那条路 for (int i = 0; i < routeOverlays.size(); i++) { int key = routeOverlays.keyAt(i); routeOverlays.get(key).setTransparency(0.4f); } routeOverlays.get(routeID).setTransparency(1); /**把用户选择的那条路的权值弄高,使路线高亮显示的同时,重合路段不会变的透明**/ routeOverlays.get(routeID).setZindex(zindex++); //必须告诉AMapNavi 你最后选择的哪条路 mAMapNavi.selectRouteId(routeID); routeIndex++; }还有一个单条线路的规划结果,这个函数我们看看有什么不一样的?
/** * 单条路线计算结果回调2 */ @Override public void onCalculateRouteSuccess() { /** * 清空上次计算的路径列表。 */ routeOverlays.clear(); ways.clear(); AMapNaviPath path = mAMapNavi.getNaviPath(); /** * 单路径不需要进行路径选择,直接传入-1即可 */ drawRoutes(-1, path); }接下来的话我们接下来进入最后一个环节。
这里其实就没有什么技巧了,基本上都是苦力活。我们着挑两三个点来说吧!第一个肯定是tablayoout的点击事件,这个没有技术含量,主要是看看逻辑上是怎么处理的?
//添加Tab点击事件 mTabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() { @Override public void onTabSelected(TabLayout.Tab tab) { String tabName = tab.getText().toString(); if(tabName.equals("驾车")){ navigationType = 0; }else if(tabName.equals("步行")){ navigationType = 1; }else{ navigationType = 2; } clearRoute(); planRoute(); } @Override public void onTabUnselected(TabLayout.Tab tab) { } @Override public void onTabReselected(TabLayout.Tab tab) { } }); /** * 清除当前地图上算好的路线 */ private void clearRoute() { for (int i = 0; i < routeOverlays.size(); i++) { RouteOverLay routeOverlay = routeOverlays.valueAt(i); routeOverlay.removeFromMap(); } routeOverlays.clear(); ways.clear(); }还有一点就是多条线路的展示,我之前以为每次我们最多只有三条路线,不会超出一个屏幕宽度的数量3条,因此不会存在View复用的情况,但是事实显示我们估计错了!当我们选择了非默认线路1的时候,然后我们再次选择目的地,这个时候选中的样式就会有两条。这个时候怎么处理呢?就是每个ItemView我们都直接初始化,然后点击之后再修改相应的item相关属性。这里看看Adapter是处理这些数据的呢?
private CommonAdapter getAdapter() { return new CommonAdapter<AMapNaviPath>(this, R.layout.item_recycleview_naviways, ways) { /** * 初始化Item样式 */ private void initItemBackground(ViewHolder holder) { holder.getView(ll_itemview).setBackgroundResource(R.drawable.item_naviway_normal_bg); TextView tvTitle = holder.getView(R.id.ll_tv_labels); TextView tvTime = holder.getView(R.id.ll_tv_time); TextView tvLength = holder.getView(R.id.ll_tv_length); tvTitle.setTextColor(getResources().getColor(R.color.item_text_title_color)); tvLength.setTextColor(getResources().getColor(R.color.item_text_title_color)); tvTime.setTextColor(getResources().getColor(R.color.black)); tvTitle.setBackgroundResource(R.drawable.item_naviway_title_normal); } /** * 选中的背景色修改 */ private void selectedBackground(ViewHolder holder) { holder.getView(ll_itemview).setBackgroundResource(R.drawable.item_naviway_selected_bg); TextView tvTitle = holder.getView(R.id.ll_tv_labels); TextView tvTime = holder.getView(R.id.ll_tv_time); TextView tvLength = holder.getView(R.id.ll_tv_length); tvTitle.setTextColor(Color.WHITE); tvTime.setTextColor(getResources().getColor(R.color.blue)); tvLength.setTextColor(getResources().getColor(R.color.blue)); tvTitle.setBackgroundResource(R.drawable.item_naviway_title_selected); } /** * 清除选中的样式 */ private void cleanSelector() { if(lastPosition!=-1){ View view = mRecyclerView.getChildAt(lastPosition); view.setBackgroundResource(R.drawable.item_naviway_normal_bg); TextView tvTitle = (TextView) view.findViewById(R.id.ll_tv_labels); TextView tvTime = (TextView) view.findViewById(R.id.ll_tv_time); TextView tvLength = (TextView) view.findViewById(R.id.ll_tv_length); tvTitle.setTextColor(getResources().getColor(R.color.item_text_title_color)); tvLength.setTextColor(getResources().getColor(R.color.item_text_title_color)); tvTime.setTextColor(getResources().getColor(R.color.black)); tvTitle.setBackgroundResource(R.drawable.item_naviway_title_normal); } } @Override protected void convert(final ViewHolder holder, final AMapNaviPath aMapNaviPath, final int position) { String title = aMapNaviPath.getLabels(); if(title.split(",").length>=3){ title = "推荐"; } holder.setText(R.id.ll_tv_labels,title); holder.setText(R.id.ll_tv_time,getTime(aMapNaviPath.getAllTime())); holder.setText(R.id.ll_tv_length,getLength(aMapNaviPath.getAllLength())); holder.getView(ll_itemview).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { currentPosition = position; //已经选中,再次选中直接返回 if(lastPosition==currentPosition){ return; }else{ //当前的下标值赋值给当前选择的线路下标值 routeIndex = position; changeRoute(); selectedBackground(holder); cleanSelector(); } lastPosition = position; } }); if (position==0){ currentPosition = position; if(lastPosition==currentPosition){ return; }else{ routeIndex = position; changeRoute(); selectedBackground(holder); cleanSelector(); } lastPosition = position; }else{ initItemBackground(holder); } } }; }最后差点忘了一个点,就是这些数据是什么时候给展示出来的? 肯定是在路线规划结果,代码如下:
/** * 多条路线计算结果回调2 * @param ints */ @Override public void onCalculateMultipleRoutesSuccess(int[] ints) { //清空上次计算的路径列表。 routeOverlays.clear(); ways.clear(); HashMap<Integer, AMapNaviPath> paths = mAMapNavi.getNaviPaths(); for (int i = 0; i < ints.length; i++) { AMapNaviPath path = paths.get(ints[i]); if (path != null) { drawRoutes(ints[i], path); ways.add(path); } } if(ways.size()>0){ currentPosition = 0; lastPosition = -1; mAdapter.notifyDataSetChanged(); mRecyclerView.setVisibility(View.VISIBLE); oneWay.setVisibility(View.GONE); tvNavi.setText("开始导航"); }else if(ways.size()==1){ mRecyclerView.setVisibility(View.GONE); oneWay.setVisibility(View.VISIBLE); tvTime.setText(getTime(ways.get(0).getAllTime())); tvLength.setText(getLength(ways.get(0).getAllLength())); tvNavi.setText("开始导航"); }else{ mRecyclerView.setVisibility(View.GONE); tvNavi.setText("准备导航"); } changeRoute(); } /** * 单条路线计算结果回调2 */ @Override public void onCalculateRouteSuccess() { /** * 清空上次计算的路径列表。 */ routeOverlays.clear(); ways.clear(); AMapNaviPath path = mAMapNavi.getNaviPath(); /** * 单路径不需要进行路径选择,直接传入-1即可 */ drawRoutes(-1, path); mRecyclerView.setVisibility(View.GONE); oneWay.setVisibility(View.VISIBLE); tvTime.setText(getTime(path.getAllTime())); tvLength.setText(getLength(path.getAllLength())); tvNavi.setText("开始导航"); }明天还要最后一个实时导航的总结,整个过程一直比较纠结,不过总算搞定了80%了,还有20%还需要继续研究!
源码 使用框架:万能Adapter
2017-07-07 PS:上文中用的高德地图版本如下:
定位SDK是3.30搜索SDK是4.0.0导航SDK是5.0.0 刚刚在使用5.2.0的时候发现一个包将所有的jar包压缩在了一个包里面,且在路线规划时,路线规划成功的方法只有一个了(以前是两个,分为多条路线和一条路线),此处需要做一个简单修改,代码如下: @Override public void onCalculateRouteSuccess(int[] ints) { //清空上次计算的路径列表。 routeOverlays.clear(); ways.clear(); if(ints.length>1){ HashMap<Integer, AMapNaviPath> paths = mAMapNavi.getNaviPaths(); for (int i = 0; i < ints.length; i++) { AMapNaviPath path = paths.get(ints[i]); if (path != null) { drawRoutes(ints[i], path); ways.add(path); } } if(ways.size()>0){ currentPosition = 0; lastPosition = -1; mAdapter.notifyDataSetChanged(); mRecyclerView.setVisibility(View.VISIBLE); oneWay.setVisibility(View.GONE); tvNavi.setText("开始导航"); }else if(ways.size()==1){ mRecyclerView.setVisibility(View.GONE); oneWay.setVisibility(View.VISIBLE); tvTime.setText(getTime(ways.get(0).getAllTime())); tvLength.setText(getLength(ways.get(0).getAllLength())); tvNavi.setText("开始导航"); }else{ mRecyclerView.setVisibility(View.GONE); tvNavi.setText("准备导航"); } changeRoute(); }else{ AMapNaviPath path = mAMapNavi.getNaviPath(); /** * 单路径不需要进行路径选择,直接传入-1即可 */ drawRoutes(-1, path); mRecyclerView.setVisibility(View.GONE); oneWay.setVisibility(View.VISIBLE); tvTime.setText(getTime(path.getAllTime())); tvLength.setText(getLength(path.getAllLength())); tvNavi.setText("开始导航"); } }