diff --git a/ln_jq_app/android/app/src/main/AndroidManifest.xml b/ln_jq_app/android/app/src/main/AndroidManifest.xml index 7a6ef64..c7cf8d3 100644 --- a/ln_jq_app/android/app/src/main/AndroidManifest.xml +++ b/ln_jq_app/android/app/src/main/AndroidManifest.xml @@ -14,7 +14,12 @@ - + + + + + + @@ -30,6 +35,11 @@ + + stationMarkers = new ArrayList<>(); public NativeMapView(Context context, int id, Object args) { this.mContext = context; - // 隐私合规接口,必须在创建MapView之前设置 + // 尝试获取Activity引用 + mActivity = getActivityFromContext(context); + MapsInitializer.updatePrivacyShow(context, true, true); MapsInitializer.updatePrivacyAgree(context, true); + container = new FrameLayout(context); + container.setClickable(true); + container.setFocusable(true); + mapView = new MapView(context); - // 在 Flutter PlatformView 中,savedInstanceState 通常为 null - mapView.onCreate(null); + mapView.onCreate(null); aMap = mapView.getMap(); - // 设置地图的 UI 选项 + container.addView(mapView, new FrameLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); + + initServices(context); + initOverlays(context); setupMapUi(); - // 注册到 MainActivity 以便同步生命周期 + // 通知MainActivity if (context instanceof MainActivity) { ((MainActivity) context).setMapView(this); } + + Log.d(TAG, "NativeMapView初始化完成"); + } + + /** + * 初始化服务 + */ + private void initServices(Context context) { + try { + geocoderSearch = new GeocodeSearch(context); + geocoderSearch.setOnGeocodeSearchListener(this); + routeSearch = new RouteSearch(context); + routeSearch.setRouteSearchListener(this); + } catch (AMapException e) { + Log.e(TAG, "服务初始化失败", e); + } + } + + /** + * 初始化覆盖层UI + */ + private void initOverlays(Context context) { + LinearLayout searchBox = new LinearLayout(context); + searchBox.setOrientation(LinearLayout.VERTICAL); + searchBox.setBackground(getRoundedDrawable(Color.WHITE, 12)); + searchBox.setElevation(dp2px(8)); + int p = dp2px(15); + searchBox.setPadding(p, p, p, p); + searchBox.setClickable(true); + searchBox.setFocusable(true); + + startInput = createInput(context, "起点: 正在定位..."); + searchBox.addView(startInput); + + View vSpace = new View(context); + searchBox.addView(vSpace, new LinearLayout.LayoutParams(1, dp2px(10))); + + LinearLayout endRow = new LinearLayout(context); + endRow.setOrientation(LinearLayout.HORIZONTAL); + endRow.setGravity(Gravity.CENTER_VERTICAL); + + endInput = createInput(context, "终点: 请输入目的地"); + endRow.addView(endInput, new LinearLayout.LayoutParams(0, dp2px(42), 1f)); + + Button routeBtn = new Button(context); + routeBtn.setText("路径规划"); + routeBtn.setTextColor(Color.WHITE); + routeBtn.setAllCaps(false); + routeBtn.setTextSize(14); + routeBtn.setBackground(getRoundedDrawable(Color.parseColor("#017143"), 6)); + routeBtn.setOnClickListener(v -> startRouteSearch()); + endRow.addView(routeBtn, new LinearLayout.LayoutParams(dp2px(90), dp2px(42))); + + searchBox.addView(endRow); + + FrameLayout.LayoutParams searchParams = new FrameLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); + searchParams.setMargins(dp2px(15), dp2px(50), dp2px(15), 0); + container.addView(searchBox, searchParams); + + ImageButton locBtn = new ImageButton(context); + locBtn.setImageResource(android.R.drawable.ic_menu_mylocation); + locBtn.setBackground(getRoundedDrawable(Color.WHITE, 30)); + locBtn.setElevation(dp2px(4)); + locBtn.setOnClickListener(v -> { + if (currentLatLng != null) { + aMap.animateCamera(CameraUpdateFactory.newLatLngZoom(currentLatLng, 15f)); + } + }); + FrameLayout.LayoutParams locParams = new FrameLayout.LayoutParams(dp2px(50), dp2px(50)); + locParams.setMargins(0, 0, dp2px(20), dp2px(120)); + locParams.gravity = Gravity.BOTTOM | Gravity.END; + container.addView(locBtn, locParams); + } + + private EditText createInput(Context context, String hint) { + EditText et = new EditText(context); + et.setHint(hint); + et.setTextSize(14f); + et.setTextColor(Color.BLACK); + et.setPadding(dp2px(12), dp2px(10), dp2px(12), dp2px(10)); + et.setBackground(getRoundedDrawable(Color.parseColor("#F5F5F5"), 6)); + et.setSingleLine(true); + et.setImeOptions(EditorInfo.IME_ACTION_DONE); + et.setFocusable(true); + et.setFocusableInTouchMode(true); + return et; + } + + private GradientDrawable getRoundedDrawable(int color, int radiusDp) { + GradientDrawable shape = new GradientDrawable(); + shape.setShape(GradientDrawable.RECTANGLE); + shape.setCornerRadius(dp2px(radiusDp)); + shape.setColor(color); + return shape; + } + + private int dp2px(float dp) { + return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, mContext.getResources().getDisplayMetrics()); } private void setupMapUi() { aMap.setLocationSource(this); aMap.setMyLocationEnabled(true); + aMap.setOnMarkerClickListener(this); MyLocationStyle myLocationStyle = new MyLocationStyle(); - myLocationStyle.interval(2000); - // 连续定位并将视角移动到地图中心点,定位蓝点跟随设备移动 - myLocationStyle.myLocationType(MyLocationStyle.LOCATION_TYPE_LOCATE); + + // --- 放大定位图标 --- + try { + Bitmap carBitmap = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.car); + if (carBitmap != null) { + // 放大到 80dp + int iconSize = dp2px(25); + Bitmap scaledBitmap = Bitmap.createScaledBitmap(carBitmap, iconSize, iconSize, true); + myLocationStyle.myLocationIcon(BitmapDescriptorFactory.fromBitmap(scaledBitmap)); + } + } catch (Exception e) { + Log.e(TAG, "设置大图标失败", e); + } + + myLocationStyle.anchor(0.5f, 0.5f); + myLocationStyle.myLocationType(MyLocationStyle.LOCATION_TYPE_LOCATION_ROTATE_NO_CENTER); myLocationStyle.showMyLocation(true); myLocationStyle.strokeColor(Color.TRANSPARENT); myLocationStyle.radiusFillColor(Color.TRANSPARENT); aMap.setMyLocationStyle(myLocationStyle); - - // 显示缩放按钮 + aMap.getUiSettings().setZoomControlsEnabled(true); + aMap.getUiSettings().setScaleControlsEnabled(true); + aMap.getUiSettings().setLogoPosition(AMapOptions.LOGO_POSITION_BOTTOM_LEFT); } - @Override - public View getView() { - return mapView; - } - + // ==================== LocationSource 接口实现 ==================== @Override public void activate(OnLocationChangedListener listener) { mListener = listener; @@ -88,56 +291,405 @@ public class NativeMapView implements PlatformView, LocationSource, AMapLocation public void startLocation() { if (mlocationClient == null) { try { - AMapLocationClient.updatePrivacyShow(mContext, true, true); - AMapLocationClient.updatePrivacyAgree(mContext, true); mlocationClient = new AMapLocationClient(mContext); - AMapLocationClientOption mLocationOption = new AMapLocationClientOption(); + AMapLocationClientOption option = new AMapLocationClientOption(); mlocationClient.setLocationListener(this); - mLocationOption.setLocationMode(AMapLocationClientOption.AMapLocationMode.Hight_Accuracy); - mlocationClient.setLocationOption(mLocationOption); + option.setLocationMode(AMapLocationClientOption.AMapLocationMode.Hight_Accuracy); + mlocationClient.setLocationOption(option); mlocationClient.startLocation(); + Log.d(TAG, "定位启动成功"); } catch (Exception e) { - Log.e(TAG, "startLocation error", e); + Log.e(TAG, "定位启动失败", e); } } } @Override - public void onLocationChanged(AMapLocation amapLocation) { - if (mListener != null && amapLocation != null) { - if (amapLocation.getErrorCode() == 0) { - mListener.onLocationChanged(amapLocation); - // 首次定位成功后缩放地图 - // aMap.moveCamera(CameraUpdateFactory.zoomTo(15)); - } else { - Log.e(TAG, "定位失败," + amapLocation.getErrorCode() + ": " + amapLocation.getErrorInfo()); + public void onLocationChanged(AMapLocation loc) { + if (loc != null) { + if (mListener != null) { + mListener.onLocationChanged(loc); + } + currentLatLng = new LatLng(loc.getLatitude(), loc.getLongitude()); + + if (loc.getErrorCode() == 0) { + if (isFirstLocation) { + isFirstLocation = false; + startPoint = currentLatLng; + aMap.moveCamera(CameraUpdateFactory.newLatLngZoom(currentLatLng, 14f)); + getAddressByLatlng(currentLatLng); + fetchRecommendStation(loc); + fetchNearbyStations(loc); + } } } } - public void onResume() { - if (mapView != null) { - mapView.onResume(); + // ==================== 逆地理编码 ==================== + private void getAddressByLatlng(LatLng latLng) { + LatLonPoint point = new LatLonPoint(latLng.latitude, latLng.longitude); + RegeocodeQuery query = new RegeocodeQuery(point, 200, GeocodeSearch.AMAP); + geocoderSearch.getFromLocationAsyn(query); + } + + @Override + public void onRegeocodeSearched(RegeocodeResult result, int rCode) { + if (rCode == AMapException.CODE_AMAP_SUCCESS && result != null && result.getRegeocodeAddress() != null) { + RegeocodeAddress addr = result.getRegeocodeAddress(); + String fullAddr = addr.getFormatAddress(); + + // 优化地址显示逻辑 + startName = formatAddress(fullAddr,result); + + new Handler(Looper.getMainLooper()).post(() -> { + startInput.setText(startName); + startInput.setSelection(startName.length()); // 光标移到末尾 + }); + + Log.d(TAG, "逆地理编码成功: " + startName); + } else { + Log.e(TAG, "逆地理编码失败: code=" + rCode + ", result=" + (result != null ? "null" : "has data")); } } + /** + * 格式化地址显示,移除重复的前缀并限制长度 + */ + private String formatAddress(String fullAddress,RegeocodeResult result) { + if (fullAddress == null || fullAddress.isEmpty()) { + return "未知地点"; + } + + // 获取各级地址信息 + String province = null; + String city = null; + String district = null; + String township = null; + + try { + if (result.getRegeocodeAddress() != null) { + RegeocodeAddress addr = result.getRegeocodeAddress(); + province = addr.getProvince(); + city = addr.getCity(); + district = addr.getDistrict(); + township = addr.getTownship(); + } + } catch (Exception e) { + Log.e(TAG, "获取地址信息失败", e); + } + + String formattedAddr = fullAddress; + + // 按优先级移除重复前缀(省、市、区、乡) + String[] prefixes = {province, city, district, township}; + for (String prefix : prefixes) { + if (prefix != null && !prefix.isEmpty() && formattedAddr.startsWith(prefix)) { + formattedAddr = formattedAddr.substring(prefix.length()); + Log.d(TAG, "移除前缀: " + prefix + " -> " + formattedAddr); + } + } + + // 限制地址长度并添加省略号 + if (formattedAddr.length() > 25) { + formattedAddr = formattedAddr.substring(0, 25) + "..."; + Log.d(TAG, "地址长度截断: " + formattedAddr); + } + + return formattedAddr; + } + + @Override + public void onGeocodeSearched(GeocodeResult result, int rCode) { + } + + // ==================== API请求 ==================== + + private void fetchRecommendStation(AMapLocation loc) { + try { + JSONObject json = new JSONObject(); + json.put("province", loc.getProvince() != null ? loc.getProvince() : ""); + json.put("city", loc.getCity() != null && !loc.getCity().isEmpty() ? loc.getCity() : ""); + json.put("district", loc.getDistrict() != null ? loc.getDistrict() : ""); + json.put("longitude", String.valueOf(loc.getLongitude())); + json.put("latitude", String.valueOf(loc.getLatitude())); + + RequestBody body = RequestBody.create(json.toString(), MediaType.parse("application/json; charset=utf-8")); + Request request = new Request.Builder() + .url("https://beta-esg.api.lnh2e.com/appointment/station/getStationInfoByArea") + .post(body) + .build(); + + httpClient.newCall(request).enqueue(new Callback() { + @Override + public void onFailure(@NonNull Call call, @NonNull IOException e) { + } + + @Override + public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException { + if (response.isSuccessful() && response.body() != null) { + try { + JSONObject res = new JSONObject(response.body().string()); + if (res.getInt("code") == 0 && !res.isNull("data")) { + JSONObject data = res.getJSONObject("data"); + endPoint = new LatLng(data.getDouble("latitude"), data.getDouble("longitude")); + endName = data.getString("name"); + String addr = data.optString("address", ""); + new Handler(Looper.getMainLooper()).post(() -> { + endInput.setText(addr); + markStation(endPoint, endName, true); + }); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + } + }); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private void fetchNearbyStations(AMapLocation loc) { + try { + JSONObject json = new JSONObject(); + json.put("longitude", String.valueOf(loc.getLongitude())); + json.put("latitude", String.valueOf(loc.getLatitude())); + + RequestBody body = RequestBody.create(json.toString(), MediaType.parse("application/json; charset=utf-8")); + Request request = new Request.Builder() + .url("https://beta-esg.api.lnh2e.com/appointment/station/getNearbyHydrogenStationsByLocation") + .post(body) + .build(); + + httpClient.newCall(request).enqueue(new Callback() { + @Override + public void onFailure(@NonNull Call call, @NonNull IOException e) { + } + + @Override + public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException { + if (response.isSuccessful() && response.body() != null) { + try { + JSONObject res = new JSONObject(response.body().string()); + if (res.getInt("code") == 0 && !res.isNull("data")) { + JSONArray array = res.getJSONArray("data"); + new Handler(Looper.getMainLooper()).post(() -> { + for (int i = 0; i < array.length(); i++) { + try { + JSONObject item = array.getJSONObject(i); + markStation(new LatLng(item.getDouble("latitude"), item.getDouble("longitude")), + item.getString("name"), false); + } catch (Exception ignored) { + } + } + }); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + } + }); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private void markStation(LatLng latLng, String name, boolean isRecommend) { + MarkerOptions opt = new MarkerOptions() + .position(latLng).title(name) + .icon(BitmapDescriptorFactory.defaultMarker(isRecommend ? BitmapDescriptorFactory.HUE_RED : BitmapDescriptorFactory.HUE_GREEN)); + Marker m = aMap.addMarker(opt); + m.setObject(latLng); + stationMarkers.add(m); + } + + @Override + public boolean onMarkerClick(Marker marker) { + if (marker.getObject() instanceof LatLng) { + endPoint = (LatLng) marker.getObject(); + endName = marker.getTitle(); + endInput.setText(endName); + startRouteSearch(); + } + return true; + } + + // ==================== 路径规划 ==================== + + private void startRouteSearch() { + if (startPoint == null || endPoint == null) { + Toast.makeText(mContext, "正在定位中,请稍后...", Toast.LENGTH_SHORT).show(); + return; + } + + Poi start = new Poi(startName, startPoint, ""); + Poi end = new Poi(endName, endPoint, ""); + + AmapNaviParams params = new AmapNaviParams(start, null, end, AmapNaviType.DRIVER, AmapPageType.ROUTE); + + try { + AMapNavi mAMapNavi = AMapNavi.getInstance(mContext); + AMapCarInfo carInfo = new AMapCarInfo(); + carInfo.setCarNumber("沪AGK2267"); + carInfo.setCarType("1"); + carInfo.setVehicleAxis("6"); + carInfo.setVehicleHeight("3.32"); + carInfo.setVehicleLength("6.9"); + carInfo.setVehicleWidth("2.26"); + carInfo.setVehicleSize("2"); + carInfo.setVehicleLoad("4.5"); + carInfo.setVehicleWeight("4.5"); + carInfo.setRestriction(true); + carInfo.setVehicleLoadSwitch(true); + mAMapNavi.setCarInfo(carInfo); + } catch (com.amap.api.maps.AMapException e) { + Log.e(TAG, "设置车辆信息失败", e); + } + + params.setRouteStrategy(PathPlanningStrategy.DRIVING_MULTIPLE_ROUTES_DEFAULT); + + if (mActivity != null) { + AmapNaviPage.getInstance().showRouteActivity(mActivity, params, new INaviInfoCallback() { + @Override + public void onInitNaviFailure() { + Log.e(TAG, "导航初始化失败"); + } + + @Override + public void onGetNavigationText(String s) { + } + + @Override + public void onLocationChange(AMapNaviLocation location) { + } + + @Override + public void onArriveDestination(boolean b) { + } + + @Override + public void onStartNavi(int i) { + } + + @Override + public void onCalculateRouteSuccess(int[] ints) { + } + + @Override + public void onCalculateRouteFailure(int i) { + } + + @Override + public void onStopSpeaking() { + } + + @Override + public void onReCalculateRoute(int i) { + + } + + @Override + public void onArrivedWayPoint(int i) { + } + + @Override + public void onExitPage(int i) { + } + + @Override + public void onStrategyChanged(int i) { + } + + @Override + public void onMapTypeChanged(int i) { + } + + @Override + public void onNaviDirectionChanged(int i) { + } + + @Override + public void onDayAndNightModeChanged(int i) { + } + + @Override + public void onBroadcastModeChanged(int i) { + } + + @Override + public void onScaleAutoChanged(boolean b) { + } + + @Override + public View getCustomNaviBottomView() { + return null; + } + + @Override + public View getCustomNaviView() { + return null; + } + + @Override + public View getCustomMiddleView() { + return null; + } + }); + } + } + + @Override + public void onDriveRouteSearched(DriveRouteResult result, int rCode) { + } + + @Override + public void onBusRouteSearched(BusRouteResult r, int c) { + } + + @Override + public void onWalkRouteSearched(WalkRouteResult r, int c) { + } + + @Override + public void onRideRouteSearched(RideRouteResult r, int c) { + } + + private Activity getActivityFromContext(Context context) { + if (context instanceof Activity) + return (Activity) context; + if (context instanceof android.content.ContextWrapper) { + Context base = ((android.content.ContextWrapper) context).getBaseContext(); + if (base instanceof Activity) + return (Activity) base; + } + return null; + } + + public void onResume() { + mapView.onResume(); + } + public void onPause() { - if (mapView != null) { - mapView.onPause(); - } + mapView.onPause(); } - public void onSaveInstanceState(Bundle outState) { - if (mapView != null) { - mapView.onSaveInstanceState(outState); - } + public void onSaveInstanceState(Bundle out) { + mapView.onSaveInstanceState(out); + } + + @Override + public View getView() { + return container; } @Override public void dispose() { - if (mapView != null) { - mapView.onDestroy(); + if (mlocationClient != null) { + mlocationClient.stopLocation(); + mlocationClient.onDestroy(); } - deactivate(); + mapView.onDestroy(); } } diff --git a/ln_jq_app/android/app/src/main/res/drawable/amap_loc.xml b/ln_jq_app/android/app/src/main/res/drawable/amap_loc.xml deleted file mode 100644 index a58e47e..0000000 --- a/ln_jq_app/android/app/src/main/res/drawable/amap_loc.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - diff --git a/ln_jq_app/android/app/src/main/res/drawable/car.png b/ln_jq_app/android/app/src/main/res/drawable/car.png new file mode 100644 index 0000000..5f5ba0a Binary files /dev/null and b/ln_jq_app/android/app/src/main/res/drawable/car.png differ diff --git a/ln_jq_app/android/app/src/main/res/drawable/ic_end_marker.xml b/ln_jq_app/android/app/src/main/res/drawable/ic_end_marker.xml deleted file mode 100644 index 58bb442..0000000 --- a/ln_jq_app/android/app/src/main/res/drawable/ic_end_marker.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - diff --git a/ln_jq_app/android/app/src/main/res/drawable/ic_location.xml b/ln_jq_app/android/app/src/main/res/drawable/ic_location.xml deleted file mode 100644 index 4e99139..0000000 --- a/ln_jq_app/android/app/src/main/res/drawable/ic_location.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - diff --git a/ln_jq_app/android/app/src/main/res/drawable/ic_route.xml b/ln_jq_app/android/app/src/main/res/drawable/ic_route.xml deleted file mode 100644 index e16e2fe..0000000 --- a/ln_jq_app/android/app/src/main/res/drawable/ic_route.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - diff --git a/ln_jq_app/android/app/src/main/res/drawable/ic_start_marker.xml b/ln_jq_app/android/app/src/main/res/drawable/ic_start_marker.xml deleted file mode 100644 index 0af42cf..0000000 --- a/ln_jq_app/android/app/src/main/res/drawable/ic_start_marker.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - diff --git a/ln_jq_app/android/app/src/main/res/drawable/ic_station_marker.xml b/ln_jq_app/android/app/src/main/res/drawable/ic_station_marker.xml deleted file mode 100644 index ef73731..0000000 --- a/ln_jq_app/android/app/src/main/res/drawable/ic_station_marker.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - diff --git a/ln_jq_app/android/app/src/main/res/drawable/ic_tag.png b/ln_jq_app/android/app/src/main/res/drawable/ic_tag.png new file mode 100644 index 0000000..dcae650 Binary files /dev/null and b/ln_jq_app/android/app/src/main/res/drawable/ic_tag.png differ