功能实现
跳转方式实现
功能描述:通过初始化百度地图API,获取当前位置的经纬度做为起点,所输入的地址做为目的地,然后封装成Uri格式,使用隐式Intent调用百度地图APP,最终实现算路导航。
权限声明
声明导航所需要具备的权限,例如:网路权限、位置权限等
private void InitPermission(){ List<String> PermissionList = new ArrayList<>(); //判断权限是否授权 if (ContextCompat.checkSelfPermission( Function.this, Manifest.permission.ACCESS_FINE_LOCATION ) != PackageManager.PERMISSION_GRANTED) { PermissionList.add( Manifest.permission.ACCESS_FINE_LOCATION ); } if (ContextCompat.checkSelfPermission( Function.this, Manifest.permission.READ_PHONE_STATE ) != PackageManager.PERMISSION_GRANTED) { PermissionList.add( Manifest.permission.READ_PHONE_STATE ); } if (ContextCompat.checkSelfPermission( Function.this, Manifest.permission.WRITE_EXTERNAL_STORAGE ) != PackageManager.PERMISSION_GRANTED) { PermissionList.add( Manifest.permission.WRITE_EXTERNAL_STORAGE ); } if (!PermissionList.isEmpty()) { String[] Permissions = PermissionList.toArray( new String[PermissionList.size()] );//转化为数组 ActivityCompat.requestPermissions( Function.this, Permissions, 1 );//一次性申请权限 } else { /*****************如果权限都已经声明,开始配置参数*****************/ requestLocation(); } }
判断手机内是否安装百度地图
百度地图包名
public static final String BAIDUMAPPACKAGE = "com.baidu.BaiduMap"; // 百度地图包名
通过以百度地图包名为索引,查找手机内是否存在该应用,并返回boolean值
public static boolean isBaiduMapInstalled(){ return isInstallPackage(BAIDUMAPPACKAGE); } private static boolean isInstallPackage(String packageName) { return new File("/data/data/" + packageName).exists(); }
实现跳转至百度地图
以获取自身的经纬度为导航起点,以输入的地址做为导航终点。最后通过Intent隐式跳转,唤起百度地图APP
public void Navigation(String EndAddress){ Uri uri = Uri.parse( "baidumap://map/direction?origin=" + Latitude + "," + Longitude + "&" + "destination=" + EndAddress + "&mode=driving&package=com.baidu.BaiduMap;end" ); startActivity( new Intent( Intent.ACTION_VIEW, uri ) ); }
非跳转方式实现
功能描述:首先通过将输入的地址转为经纬度形式,然后通过百度地图导航API进行算路节点计算,然后完成导航规划
UI设计
背景
在drawable目录下创建一个文件,并实现一个圆角矩形
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <solid android:color="#ffffff"/> <corners android:radius="10dp"/> </shape>
圆点
在drawable目录下创建一个文件,并实现一个实心圆
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval"> <solid android:color="#4CAF50"/> <size android:height="10dp" android:width="10dp"/>
布局代码
<?xml version="1.0" encoding="utf-8"?> <LinearLayout 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=".Activity.SelectNodeActivity" android:orientation="vertical" android:background="#F1EDED"> <include layout="@layout/titlebar"/> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="10dp" android:background="@drawable/white_radiu_bg"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@drawable/grey_radiu_bg" android:layout_margin="20dp" android:padding="10dp"> <LinearLayout android:layout_width="0dp" android:layout_weight="7" android:layout_height="wrap_content" android:orientation="vertical"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:padding="@dimen/navi_dimens_5dp"> <View android:layout_width="10dp" android:layout_height="10dp" android:background="@drawable/green_point" android:layout_gravity="center" android:layout_marginRight="5dp"/> <EditText android:id="@+id/startAddress" android:layout_width="wrap_content" android:layout_height="wrap_content" android:hint="please enter starting point" android:textColor="#000000" android:textSize="15sp" android:layout_gravity="center" android:layout_marginLeft="5dp" android:background="@null"/> </LinearLayout> <!--分割线--> <View android:layout_width="match_parent" android:layout_height="1dp" android:background="#DBCACA" android:layout_marginTop="5dp" android:layout_marginBottom="5dp"/> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:padding="@dimen/navi_dimens_5dp"> <View android:layout_width="10dp" android:layout_height="10dp" android:background="@drawable/red_point" android:layout_gravity="center" android:layout_marginRight="5dp"/> <EditText android:id="@+id/endAddress" android:layout_width="wrap_content" android:layout_height="wrap_content" android:hint="please enter ending point" android:textColor="#000000" android:textSize="15sp" android:layout_marginLeft="5dp" android:background="@null" android:layout_gravity="center"/> </LinearLayout> </LinearLayout> <LinearLayout android:id="@+id/exchangeStr" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:layout_gravity="center" android:layout_marginLeft="10dp" android:onClick="ExchangeStr"> <ImageView android:layout_width="25dp" android:layout_height="25dp" android:src="@drawable/icon_exchange" android:layout_gravity="center"/> </LinearLayout> </LinearLayout> </LinearLayout> <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="50dp"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@drawable/white_radiu_bg" android:layout_margin="10dp" android:padding="10dp" android:layout_alignParentBottom="true"> <Button android:id="@+id/exeProcess" android:layout_width="match_parent" android:layout_height="30dp" android:background="@drawable/green_radiu_bg" android:text="Start Navigation" android:textAllCaps="false" android:textColor="#ffffff" android:layout_margin="10dp" android:onClick="ExeProcess"/> </LinearLayout> </RelativeLayout> </LinearLayout>
功能实现
在介绍功能前,首先先介绍两个概念,即地理编码与反地理编码
地理编码:地址信息转换为地理坐标(经纬度)
反地理编码:地理坐标(经纬度)转换为地址信息
权限声明
将所需要声明的权限列为一个数组
private static final String[] authBaseArr = { Manifest.permission.RECORD_AUDIO, Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS, Manifest.permission.WRITE_EXTERNAL_STORAGE };
遍历数组,判断权限是否声明,并返回boolean值做为判断标识符
private void InitPermission( // 申请权限 if (Build.VERSION.SDK_INT >= 23) { if (!hasBasePhoneAuth()) { requestPermissions(authBaseArr, authBaseRequestCode); } } } private boolean hasBasePhoneAuth() { PackageManager pm = this.getPackageManager(); for (String auth : authBaseArr) { if (pm.checkPermission(auth, this.getPackageName()) != PackageManager .PERMISSION_GRANTED) { return false; } } return true; }
初始化BroadCast
private void InitBroadCastReceiver() { IntentFilter filter = new IntentFilter(); filter.addAction("com.navi.ready"); mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { BNDemoFactory.getInstance().initCarInfo(); BNDemoFactory.getInstance().initRoutePlanNode(); } }; registerReceiver(mReceiver, filter); }
初始化车辆信息
若没有手动修改车辆信息,则以如下信息为基础
public void initCarInfo() { // 驾车车牌设置 // BaiduNaviManagerFactory.getCommonSettingManager().setCarNum("京A88888"); // 货车信息 BNTruckInfo truckInfo = new BNTruckInfo.Builder() .plate("京A88888") .axlesNumber(2) .axlesWeight(1) .emissionLimit(VehicleConstant.EmissionStandard.S3) .length(5) .weight(2) .loadWeight(1) .oilCost("40000") .plateType(VehicleConstant.PlateType.BLUE) .powerType(VehicleConstant.PowerType.OIL) .truckType(VehicleConstant.TruckType.HEAVY) .height(2) .width(2.5f) .build(); // 该接口会做本地持久化,在应用中设置一次即可 BaiduNaviManagerFactory.getCommonSettingManager().setTruckInfo(truckInfo); // 摩托车信息 BNMotorInfo motorInfo = new BNMotorInfo.Builder() .plate("京A88888") .plateType(VehicleConstant.PlateType.BLUE) .motorType(VehicleConstant.MotorType.OIL) .displacement("") .build(); // 该接口会做本地持久化,在应用中设置一次即可 BaiduNaviManagerFactory.getCommonSettingManager().setMotorInfo(motorInfo); // BaiduNaviManagerFactory.getCommonSettingManager().setTestEnvironment(false); BaiduNaviManagerFactory.getCommonSettingManager().setNodeClick(true); }
初始化节点信息
若没有手动修改路径节点信息,则以如下信息为基础
public void initRoutePlanNode() { startNode = new BNRoutePlanNode.Builder() .latitude(40.041690) .longitude(116.306333) .name("百度大厦") .description("百度大厦") .build(); endNode = new BNRoutePlanNode.Builder() .latitude(39.908560) .longitude(116.397609) .name("北京天安门") .description("北京天安门") .build(); }
节点信息交换
即将起点地址信息与终点地址信息完成互换,利用一个临时变量存储一方数据,完成互换
public void ExchangeStr(View view) { String Start = startAddress.getText().toString().trim(); String End = endAddress.getText().toString().trim(); startAddress.setText( End ); endAddress.setText( Start ); }
地址信息转地理信息(经纬度)
Geocoder为百度地图提供的一个地址信息与地理信息相互转换的一个API
geocoder = new Geocoder( SelectNodeActivity.this );
通过用户键入的地址信息,由getFromLocationName()方法进行转换,并返回一个Address对象;需要注意的是键入的地址信息应当较为精准,例如xxx省xxx市xxx区xxx地,这样所转换的地理信息,精度才不会偏移太大
private String GetNodeValue(String Address){ builder = new StringBuilder( ); try { List<android.location.Address> addresses = geocoder.getFromLocationName(Address,1 ); double start_latitude = addresses.get( 0 ).getLatitude();//纬度 double start_longitude = addresses.get( 0 ).getLongitude();//经度 builder.append( start_longitude ).append( "," ).append( start_latitude ); } catch (IOException e) { Toast.makeText( SelectNodeActivity.this,"error",Toast.LENGTH_SHORT).show(); e.printStackTrace(); } return builder.toString(); }
保存地理信息
通过使用一个简单工厂单例类保存所转换的地理信息(经纬度)
private void AddrToCoordinate(){ boolean flag = Geocoder.isPresent(); if (!flag) Toast.makeText( SelectNodeActivity.this,"error",Toast.LENGTH_SHORT ).show(); else { String start = GetNodeValue(startAddress.getText().toString().trim()); String end = GetNodeValue(endAddress.getText().toString().trim()); //TODO 设置起点经纬度 if (!TextUtils.isEmpty(start)) { BNDemoFactory.getInstance().setStartNode(this, start); Log.d( "start:",start ); } //TODO 设置终点经纬度 if (!TextUtils.isEmpty(end)) { BNDemoFactory.getInstance().setEndNode(this, end); Log.d( "end:",end ); } } }
算路导航
private void routePlanToNavi(final Bundle bundle) { List<BNRoutePlanNode> list = new ArrayList<>(); //TODO 在主函数中获取经纬度,开始算路导航 list.add(BNDemoFactory.getInstance().getStartNode(this)); list.add(BNDemoFactory.getInstance().getEndNode(this)); // 关闭电子狗 if (BaiduNaviManagerFactory.getCruiserManager().isCruiserStarted()) { BaiduNaviManagerFactory.getCruiserManager().stopCruise(); } BaiduNaviManagerFactory.getRoutePlanManager().routePlanToNavi( list, IBNRoutePlanManager.RoutePlanPreference.ROUTE_PLAN_PREFERENCE_DEFAULT, bundle, handler); }
节点计算需要耗不少的内存空间以及时间,故使用异步通信,不影响主线程的分发以及UI的刷新
private Handler handler = new Handler( Looper.getMainLooper()) { @Override public void handleMessage(@NonNull Message msg) { switch (msg.what) { case IBNRoutePlanManager.MSG_NAVI_ROUTE_PLAN_START: Toast.makeText(SelectNodeActivity.this, "算路开始", Toast.LENGTH_SHORT).show(); ControlBoardWindow.getInstance().showControl("算路开始"); break; case IBNRoutePlanManager.MSG_NAVI_ROUTE_PLAN_SUCCESS: Toast.makeText(SelectNodeActivity.this, "算路成功", Toast.LENGTH_SHORT).show(); ControlBoardWindow.getInstance().showControl("算路成功"); // 躲避限行消息 Bundle infoBundle = (Bundle) msg.obj; if (infoBundle != null) { String info = infoBundle .getString( BNaviCommonParams.BNRouteInfoKey.TRAFFIC_LIMIT_INFO); Log.e("OnSdkDemo", "info = " + info); } break; case IBNRoutePlanManager.MSG_NAVI_ROUTE_PLAN_FAILED: ControlBoardWindow.getInstance().showControl("算路失败"); Toast.makeText(SelectNodeActivity.this.getApplicationContext(), "算路失败", Toast.LENGTH_SHORT).show(); break; case IBNRoutePlanManager.MSG_NAVI_ROUTE_PLAN_TO_NAVI: Toast.makeText(SelectNodeActivity.this.getApplicationContext(), "算路成功准备进入导航", Toast.LENGTH_SHORT).show(); ControlBoardWindow.getInstance().showControl("算路成功准备进入导航"); switch (mPageType) { case BNDemoUtils.NORMAL: BNDemoUtils.gotoNavi(SelectNodeActivity.this); break; case BNDemoUtils.ANALOG: BNDemoUtils.gotoAnalog(SelectNodeActivity.this); break; case BNDemoUtils.EXTGPS: BNDemoUtils.gotoExtGps(SelectNodeActivity.this); break; default: break; } break; default: break; } } };
销毁
当使用完成之后,应该将可销毁的内容放入onDestroy内,避免内存空间的浪费
@Override protected void onDestroy() { super.onDestroy(); unregisterReceiver(mReceiver); stopService(new Intent(this, ForegroundService.class)); }