java项目web.xml配置文件中的“web-app“标签标红问题的解决方案

本文档解析了在Java项目中遇到的web.xml配置错误提示,指出错误源于标签顺序不符合web-app_2_3.dtd规范。解决方案是遵循该规范调整标签顺序。了解并遵循正确的配置顺序对于理解Java Web技术至关重要。

在java项目中的web.xml配置文件中出现如上图所示的错误提示时:要小心啦!

错误的原因是:web.xml中所设置的标签顺序不符合web-app头信息的规范
所谓的web.xml头部配置:

<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

web.xml的配置要严格遵守规范的。如上图所示描述了web-app_2_3.dtd的规范引入,所以在配置此web.xml文件的内容时要严格遵守规范,所谓规范就是web.xml中的标签顺序有着严格的设定。需要按照顺序对标签进行引用。

解决方案:

1.按照引入的web-app_2_3.dtd规范文档内容将标签的顺序调整正确吗,问题就会解决。

2.对引入的web-app_2_3.dtd规范文档进行注释掉,这样做的话治标不治本,想多了解jav技术的还是推荐第一种方案。

目前SearchResultActivity代码如下:package com.example.bus; import android.Manifest; import android.content.Intent; import android.content.pm.PackageManager; import android.graphics.drawable.GradientDrawable; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.text.Editable; import android.text.TextWatcher; import android.util.Log; import android.view.View; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.TextView; import android.widget.Toast; import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import com.amap.api.maps.AMap; import com.amap.api.maps.CameraUpdateFactory; import com.amap.api.maps.MapView; import com.amap.api.maps.UiSettings; import com.amap.api.maps.model.LatLng; import com.amap.api.maps.model.Marker; import com.amap.api.maps.model.MarkerOptions; import com.amap.api.maps.model.MyLocationStyle; import com.amap.api.services.core.LatLonPoint; import com.amap.api.services.core.PoiItem; import com.amap.api.services.poisearch.PoiResult; import com.amap.api.services.poisearch.PoiSearch; import com.amap.api.services.geocoder.GeocodeSearch; import com.amap.api.services.geocoder.RegeocodeQuery; import com.amap.api.services.geocoder.RegeocodeResult; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.regex.Pattern; import androidx.activity.OnBackPressedCallback; import android.graphics.drawable.Drawable; import android.graphics.drawable.LayerDrawable; import android.app.AlertDialog; import android.view.ViewGroup; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; public class SearchResultActivity extends AppCompatActivity implements PoiSearch.OnPoiSearchListener, GeocodeSearch.OnGeocodeSearchListener { private Button searchBtn, goToBtn, btnToggleMode; private RecyclerView resultListView; private List<PoiItem> poiList = new ArrayList<>(); private ResultAdapter adapter; public static final String EXTRA_SEARCH_TYPE = "search_type"; private int searchType = 0; // 0=普通,1=线路 private final Map<String, PoiItem> suggestionPoiCache = new ConcurrentHashMap<>(); // 地图相关 private MapView mapView; private AMap aMap; private Marker selectedMarker; // 输入提示 private GeocodeSearch geocodeSearch; // 当前城市 private String currentCity = ""; // 是否已与地图交互 private boolean userHasInteracted = false; private static final int LOCATION_PERMISSION_REQUEST_CODE = 1001; // 空状态提示视图 private TextView emptyView; // 【关键新增】保存定位得到的“我的位置” private double myCurrentLat = 0; private double myCurrentLng = 0; private boolean isLocationReady = false; // 缓存关键词 private String pendingKeyword = null; // ✅ 实时建议助手 private RealTimePoiSuggestHelper suggestHelper; // 🔽 新增缓存字段 private List<PoiItem> nationalResults = new ArrayList<>(); private List<PoiItem> localResults = new ArrayList<>(); private List<PoiItem> nearbyResults = new ArrayList<>(); private boolean isNearbyLoaded = false; private boolean isInNearbyMode = false; // ✅ 将搜索输入框提升为成员变量 private android.widget.AutoCompleteTextView searchInput; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_search_result); initViews(); setupMap(savedInstanceState); String keyword = getIntent().getStringExtra("keyword"); if (keyword != null && !keyword.isEmpty()) { searchInput.setText(keyword); // 立即显示在输入框中 } try { geocodeSearch = new GeocodeSearch(this); geocodeSearch.setOnGeocodeSearchListener(this); } catch (Exception e) { e.printStackTrace(); } // 保留原来的 pendingKeyword 用于后续搜索控制 pendingKeyword = keyword; // 接收搜索类型 searchType = getIntent().getIntExtra(EXTRA_SEARCH_TYPE, 0); // 初始化 suggestHelper suggestHelper = new RealTimePoiSuggestHelper(this); suggestHelper.setCurrentCity(currentCity); suggestHelper.setUseDistanceSort(true); suggestHelper.setPoiCallback(poiItems -> { if (poiItems.length > 0) { List<String> titles = new ArrayList<>(); suggestionPoiCache.clear(); for (PoiItem item : poiItems) { String title = item.getTitle(); titles.add(title); suggestionPoiCache.put(title, item); // 仍可缓存用于后续优化 } ArrayAdapter<String> adapter = new ArrayAdapter<>( SearchResultActivity.this, android.R.layout.simple_dropdown_item_1line, titles ) { @Override public View getView(int position, View convertView, ViewGroup parent) { View view = super.getView(position, convertView, parent); view.setOnClickListener(v -> { String selectedText = getItem(position); if (selectedText != null) { searchInput.setText(selectedText); searchInput.clearFocus(); InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE); if (imm != null) { imm.hideSoftInputFromWindow(searchInput.getWindowToken(), 0); } // ✅【关键修复】不再直接 handleSelectedPoiItem // 而是触发完整搜索流程 searchBtn.performClick(); } }); return view; } }; new Handler(Looper.getMainLooper()).post(() -> { searchInput.setAdapter(adapter); if (getCurrentFocus() == searchInput) { searchInput.showDropDown(); } }); } else { new Handler(Looper.getMainLooper()).post(() -> searchInput.setAdapter(null) ); } }); // 使用 OnBackPressedDispatcher 兼容手势返回 getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) { @Override public void handleOnBackPressed() { Intent result = new Intent(); String currentText = searchInput.getText().toString().trim(); result.putExtra("updated_keyword", currentText); // 🔥 加上这一行即可! result.putExtra(SearchResultActivity.EXTRA_SEARCH_TYPE, searchType); setResult(RESULT_OK, result); finish(); } }); // =========== 初始化搜索图与点击事件 =========== updateSearchIcon(); // 设置初始图和 hint searchInput.setOnTouchListener((v, event) -> { if (event.getAction() == android.view.MotionEvent.ACTION_UP) { Drawable[] drawables = searchInput.getCompoundDrawables(); Drawable leftDrawable = drawables[0]; if (leftDrawable instanceof LayerDrawable) { int totalIconWidth = searchInput.getCompoundPaddingLeft(); if (event.getX() < totalIconWidth && event.getX() > 0) { showSearchTypeDialog(); return true; } } } return false; }); // ================= END ================= } /** * ✅ 更新搜索框左侧图:主图 + 右侧小下拉箭头 + 竖线分隔符 * 与 HomeFragment 完全保持一致的视觉效果和布局逻辑 */ private void updateSearchIcon() { // 搜索类型常量(建议提取成全局常量,这里为了独立可运行保留) final int SEARCH_TYPE_NORMAL = 0; final int SEARCH_TYPE_LINE = 1; int mainIconRes = searchType == SEARCH_TYPE_NORMAL ? R.drawable.ic_location : R.drawable.ic_subway; Drawable mainDrawable = ContextCompat.getDrawable(this, mainIconRes); Drawable dropdownArrow = ContextCompat.getDrawable(this, R.drawable.ic_arrow_drop_down); if (mainDrawable == null || dropdownArrow == null) return; float density = getResources().getDisplayMetrics().density; // ======== 尺寸定义(与 HomeFragment 严格一致)======== int iconSize = (int) (20 * density); // 图大小统一为 20dp int gapBetween = 0; // 主图与箭头之间无间隙 int dividerWidth = (int) (1 * density); // 竖线宽度 1px int paddingAfterDivider = (int) (2 * density); // 竖线到文字的距离 = 12dp int lineHeight = searchInput.getLineHeight(); int drawableTop = (lineHeight - iconSize) / 2; // 垂直居中 // 扩展竖线高度(上下多出 6dp) int extend = (int)(6 * density); int extendedDividerHeight = lineHeight + 2 * extend; // 创建 LayerDrawable 所需图层 GradientDrawable verticalDividerShape = new GradientDrawable(); verticalDividerShape.setShape(GradientDrawable.RECTANGLE); verticalDividerShape.setColor(0xFFCCCCCC); // 浅灰 LayerDrawable layerDrawable = new LayerDrawable(new Drawable[]{ mainDrawable, dropdownArrow, verticalDividerShape }); // 计算位置 int arrowLeft = iconSize + gapBetween; // 箭头紧贴主图右侧 int dividerLeft = arrowLeft + iconSize; // 分割线在箭头之后 int totalWidth = dividerLeft + dividerWidth + paddingAfterDivider; // 总宽 // 设置主图 int rightOfMain = totalWidth - iconSize; layerDrawable.setLayerSize(0, iconSize, iconSize); layerDrawable.setLayerInset(0, 0, drawableTop, rightOfMain, drawableTop); // 设置箭头图 layerDrawable.setLayerSize(1, iconSize, iconSize); layerDrawable.setLayerInset(1, arrowLeft, drawableTop, totalWidth - arrowLeft - iconSize, drawableTop); // 设置竖线 layerDrawable.setLayerSize(2, dividerWidth, extendedDividerHeight); layerDrawable.setLayerInset( 2, dividerLeft, -extend, totalWidth - dividerLeft - dividerWidth, -extend ); // 设置整个 LayerDrawable 的 bounds layerDrawable.setBounds(0, 0, totalWidth, lineHeight); // 应用到 EditText searchInput.setCompoundDrawables(layerDrawable, null, null, null); searchInput.setCompoundDrawablePadding(paddingAfterDivider); } /** * 弹出搜索类型选择对话框 */ private void showSearchTypeDialog() { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle("选择搜索类型"); String[] items = {"📍 地点搜索", "🚇 公交线路搜索"}; Drawable[] icons = { ContextCompat.getDrawable(this, R.drawable.ic_location), ContextCompat.getDrawable(this, R.drawable.ic_subway) }; ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.select_dialog_singlechoice, items) { @Override public View getView(int position, View convertView, ViewGroup parent) { TextView tv = (TextView) super.getView(position, convertView, parent); tv.setCompoundDrawablesRelativeWithIntrinsicBounds(icons[position], null, null, null); tv.setCompoundDrawablePadding(16); return tv; } }; builder.setAdapter(adapter, (dialog, which) -> { int oldType = searchType; searchType = which == 0 ? 0 : 1; // 0=普通,1=线路 if (oldType != searchType) { updateSearchIcon(); Toast.makeText(this, searchType == 0 ? "已切换为【地点】模式" : "已切换为【线路】模式", Toast.LENGTH_SHORT).show(); } }); builder.show(); } private void initViews() { searchBtn = findViewById(R.id.search_btn); resultListView = findViewById(R.id.result_list); goToBtn = findViewById(R.id.btn_go_to); btnToggleMode = findViewById(R.id.btn_toggle_mode); emptyView = findViewById(R.id.empty_view); searchInput = findViewById(R.id.search_input); goToBtn.setEnabled(false); adapter = new ResultAdapter(poiList, this::onPoiItemSelected); resultListView.setLayoutManager(new LinearLayoutManager(this)); resultListView.setAdapter(adapter); resultListView.setVisibility(View.GONE); emptyView.setVisibility(View.GONE); goToBtn.setOnClickListener(v -> { if (selectedMarker == null) { Toast.makeText(this, "请先选择一个位置", Toast.LENGTH_SHORT).show(); return; } LatLng targetPos = selectedMarker.getPosition(); if (!isLocationReady) { Toast.makeText(this, "正在获取您的位置,请稍后再试", Toast.LENGTH_SHORT).show(); return; } Intent intent = new Intent(SearchResultActivity.this, RoutePlanActivity.class); intent.putExtra("start_lat", myCurrentLat); intent.putExtra("start_lng", myCurrentLng); intent.putExtra("target_lat", targetPos.latitude); intent.putExtra("target_lng", targetPos.longitude); intent.putExtra(RoutePlanActivity.EXTRA_SOURCE, RoutePlanActivity.SOURCE_FROM_SEARCH_RESULT); startActivity(intent); finish(); }); btnToggleMode.setOnClickListener(v -> { if (isInNearbyMode) { exitNearbyMode(); } else { enterNearbyMode(); } }); } private void setupMap(Bundle savedInstanceState) { mapView = findViewById(R.id.map_view); mapView.onCreate(savedInstanceState); aMap = mapView.getMap(); if (aMap != null) { initMapSettings(); } else { new Handler(Looper.getMainLooper()).post(() -> { aMap = mapView.getMap(); if (aMap != null) { initMapSettings(); } else { waitAMapReady(); } }); } } private void waitAMapReady() { new Handler(Looper.getMainLooper()).postDelayed(new Runnable() { int retry = 0; @Override public void run() { if (mapView == null) return; aMap = mapView.getMap(); if (aMap != null) { initMapSettings(); } else if (retry++ < 30) { new Handler(Looper.getMainLooper()).postDelayed(this, 100); } } }, 100); } private void initMapSettings() { UiSettings uiSettings = aMap.getUiSettings(); uiSettings.setZoomControlsEnabled(true); uiSettings.setCompassEnabled(true); uiSettings.setScrollGesturesEnabled(true); uiSettings.setMyLocationButtonEnabled(true); new Handler(Looper.getMainLooper()).post(() -> aMap.moveCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(35.8617, 104.1954), 4f)) ); enableMyLocationLayer(); } private void enableMyLocationLayer() { if (aMap == null) return; if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) { MyLocationStyle myLocationStyle = new MyLocationStyle(); myLocationStyle.myLocationType(MyLocationStyle.LOCATION_TYPE_LOCATION_ROTATE_NO_CENTER); aMap.setMyLocationStyle(myLocationStyle); aMap.setMyLocationEnabled(true); AMap.OnMyLocationChangeListener listener = location -> { if (location != null && !userHasInteracted) { LatLng curLatlng = new LatLng(location.getLatitude(), location.getLongitude()); myCurrentLat = location.getLatitude(); myCurrentLng = location.getLongitude(); isLocationReady = true; suggestHelper.setLocationBias(myCurrentLat, myCurrentLng); aMap.animateCamera(CameraUpdateFactory.newLatLngZoom(curLatlng, 16f), 500, null); userHasInteracted = true; LatLonPoint point = new LatLonPoint(myCurrentLat, myCurrentLng); RegeocodeQuery query = new RegeocodeQuery(point, 200, GeocodeSearch.AMAP); try { geocodeSearch.getFromLocationAsyn(query); } catch (Exception e) { e.printStackTrace(); } new Handler(Looper.getMainLooper()).postDelayed(() -> { if (pendingKeyword != null && !pendingKeyword.isEmpty()) { performSearchWithKeyword(pendingKeyword); } }, 800); aMap.setOnMyLocationChangeListener(null); } }; aMap.setOnMyLocationChangeListener(listener); } else { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, LOCATION_PERMISSION_REQUEST_CODE); } } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == LOCATION_PERMISSION_REQUEST_CODE) { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { enableMyLocationLayer(); } } } private void setupSearchSuggestion() { Handler handler = new Handler(Looper.getMainLooper()); final Runnable[] pendingRequest = new Runnable[1]; // ✅ 使用数组包装 // 初始化 suggestHelper(已在 onCreate 中创建,这里确保设置参数) suggestHelper.setCurrentCity(currentCity); suggestHelper.setUseDistanceSort(true); // 设置建议回调 suggestHelper.setCallback(suggestions -> { if (suggestions.length > 0) { ArrayAdapter<String> adapter = new ArrayAdapter<>( this, android.R.layout.simple_dropdown_item_1line, suggestions ) { @Override public View getView(int position, View convertView, ViewGroup parent) { View view = super.getView(position, convertView, parent); view.setOnClickListener(v -> { String selectedText = getItem(position); if (selectedText != null && !selectedText.isEmpty()) { searchInput.setText(selectedText); searchInput.clearFocus(); InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE); if (imm != null) { imm.hideSoftInputFromWindow(searchInput.getWindowToken(), 0); } // 🔥 直接触发搜索(与 HomeFragment 一致) searchBtn.performClick(); } }); return view; } }; new Handler(Looper.getMainLooper()).post(() -> { searchInput.setAdapter(adapter); if (getCurrentFocus() == searchInput) { searchInput.showDropDown(); } }); } else { new Handler(Looper.getMainLooper()).post(() -> searchInput.setAdapter(null) ); } }); // 输入监听 + 防抖 searchInput.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {} @Override public void onTextChanged(CharSequence s, int start, int before, int count) { if (pendingRequest[0] != null) { handler.removeCallbacks(pendingRequest[0]); } if (s.length() == 0) { searchInput.setAdapter(null); return; } boolean hasPermission = ContextCompat.checkSelfPermission(SearchResultActivity.this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED; if (!hasPermission) { searchInput.setAdapter(null); return; } // ✅ 将新任务赋值给 pendingRequest[0] pendingRequest[0] = () -> { String keyword = s.toString().trim(); CityManager.ParsedQuery parsed = CityManager.parse(keyword); String searchKeyword = parsed.keyword.isEmpty() ? keyword : parsed.keyword; String explicitCity = parsed.targetCity; // 如果指定了城市,则不走 suggestHelper if (!explicitCity.isEmpty()) { new Handler(Looper.getMainLooper()).post(() -> searchInput.setAdapter(null)); return; } suggestHelper.setCurrentCity(currentCity); suggestHelper.requestSuggestions(searchKeyword); }; handler.postDelayed(pendingRequest[0], 300); } @Override public void afterTextChanged(Editable s) {} }); // 回车和按钮事件保持不变 searchInput.setOnEditorActionListener((v, actionId, event) -> { if ((actionId & EditorInfo.IME_MASK_ACTION) == EditorInfo.IME_ACTION_SEARCH) { searchBtn.performClick(); return true; } return false; }); searchBtn.setOnClickListener(v -> { String keyword = searchInput.getText().toString().trim(); if (!keyword.isEmpty()) { performSearch(keyword); } else { Toast.makeText(this, "请输入关键词", Toast.LENGTH_SHORT).show(); } }); } private void performSearchWithKeyword(String keyword) { searchInput.setText(keyword); searchInput.clearFocus(); searchBtn.performClick(); } private void performSearch(String keyword) { if (keyword.isEmpty()) return; // 【重置状态】清空缓存数据 nationalResults.clear(); localResults.clear(); nearbyResults.clear(); isNearbyLoaded = false; isInNearbyMode = false; btnToggleMode.setText("📍 附近"); emptyView.setText("🔍 搜索中..."); emptyView.setVisibility(View.VISIBLE); resultListView.setVisibility(View.GONE); // ✅【核心修改】不再使用正则自动识别,完全由 searchType 控制行为 if (searchType == 1) { // 🔧 进入纯线路模式 doLineModeSearch(keyword); return; // 提前返回,不执行下面的普通搜索逻辑 } // ❌ 原有普通搜索逻辑保持不变(全国+本地合并) CityManager.ParsedQuery parsed = CityManager.parse(keyword); String searchKeyword = parsed.keyword.isEmpty() ? keyword : parsed.keyword; String explicitCity = parsed.targetCity; if (!explicitCity.isEmpty()) { PoiSearch.Query query = new PoiSearch.Query(searchKeyword, "", explicitCity); query.setPageSize(20); try { PoiSearch search = new PoiSearch(this, query); search.setOnPoiSearchListener(this); search.searchPOIAsyn(); } catch (Exception e) { Toast.makeText(this, "搜索失败", Toast.LENGTH_SHORT).show(); } } else { nationalResults.clear(); localResults.clear(); PoiSearch.Query nationalQuery = new PoiSearch.Query(searchKeyword, "", ""); nationalQuery.setPageSize(20); try { PoiSearch nationalSearch = new PoiSearch(this, nationalQuery); nationalSearch.setOnPoiSearchListener(new PoiSearch.OnPoiSearchListener() { @Override public void onPoiSearched(PoiResult result, int rCode) { if (rCode == 1000 && result != null && result.getPois() != null) { nationalResults.clear(); nationalResults.addAll(result.getPois()); } requestLocalSearch(searchKeyword); } @Override public void onPoiItemSearched(PoiItem item, int rCode) {} }); nationalSearch.searchPOIAsyn(); } catch (Exception e) { e.printStackTrace(); requestLocalSearch(searchKeyword); } } } /** * ✅【新增】纯线路模式搜索 * 功能: * - 搜索包含“站”字的相关 POI * - 分页加载确保完整性 * - 过滤非站点类结果 * - 排序后绘制线路和站点记 */ private void doLineModeSearch(String keyword) { // ✅ 提取数字作为主关键词(如“4号线”) String numberPart = keyword.replaceAll("[^\\d]", ""); String baseKeyword = numberPart.isEmpty() ? keyword : numberPart + "号线"; // ✅ 判断 POI 类型 String poiType = ""; if (keyword.contains("地铁") || keyword.contains("轨道")) { poiType = "subway"; } else if (keyword.contains("公交")) { poiType = "bus_station"; } else if (keyword.contains("轻轨")) { poiType = "light_rail"; } else if (keyword.contains("云巴")) { poiType = "bus_station"; } // ✅ 构造查询 PoiSearch.Query query = new PoiSearch.Query(baseKeyword, poiType, currentCity); query.setPageSize(50); // 高分页数 query.setPageNum(0); try { PoiSearch search = new PoiSearch(this, query); search.setOnPoiSearchListener(new PoiSearch.OnPoiSearchListener() { private final List<PoiItem> allStations = new ArrayList<>(); @Override public void onPoiSearched(PoiResult result, int rCode) { if (rCode == 1000 && result != null && result.getPois() != null) { List<PoiItem> pageItems = result.getPois(); for (PoiItem item : pageItems) { // ✅【严格过滤】只保留题中含“站”的 POI(排除A出口/B口/便利店等) if (item.getTitle().contains("站")) { allStations.add(item); } } // ✅ 分页加载直到最后一页 if (result.getPageCount() > query.getPageNum() + 1) { query.setPageNum(query.getPageNum() + 1); try { PoiSearch nextSearch = new PoiSearch(SearchResultActivity.this, query); nextSearch.setOnPoiSearchListener(this); nextSearch.searchPOIAsyn(); } catch (Exception e) { finishLineSearch(allStations); } } else { finishLineSearch(allStations); } } else { emptyView.setText("⚠️ 未找到相关线路站点"); emptyView.setVisibility(View.VISIBLE); resultListView.setVisibility(View.GONE); } } @Override public void onPoiItemSearched(PoiItem item, int rCode) {} // ✅【完成搜索】去重、排序、更新 UI 和地图 private void finishLineSearch(List<PoiItem> stations) { if (stations.isEmpty()) { emptyView.setText("⚠️ 未找到相关站点"); emptyView.setVisibility(View.VISIBLE); resultListView.setVisibility(View.GONE); return; } // ✅ 去重:基于 POI ID Set<String> seen = new HashSet<>(); List<PoiItem> uniqueStations = new ArrayList<>(); for (PoiItem item : stations) { if (seen.add(item.getPoiId())) { uniqueStations.add(item); } } // ✅ 按站号排序(尝试提取数字) uniqueStations.sort((a, b) -> { int numA = extractNumber(a.getTitle()); int numB = extractNumber(b.getTitle()); if (numA != -1 && numB != -1) { return Integer.compare(numA, numB); } return a.getTitle().compareTo(b.getTitle()); }); // ✅ 更新列表(仅显示站点) updateResultList(uniqueStations); // ✅ 绘制线路图(重点功能) drawLineRouteOnMap(uniqueStations); } }); search.searchPOIAsyn(); } catch (Exception e) { e.printStackTrace(); emptyView.setText("⚠️ 线路搜索异常"); emptyView.setVisibility(View.VISIBLE); resultListView.setVisibility(View.GONE); } } // ✅ 提取字符串中的第一个整数(用于排序) private int extractNumber(String s) { try { return Integer.parseInt(s.replaceAll("\\D+", "")); } catch (NumberFormatException e) { return -1; } } /** * ✅ 使用首尾站发起公交路线请求,绘制线路 + 所有站点打点 */ private void drawLineRouteOnMap(List<PoiItem> stations) { if (stations.isEmpty()) return; // ✅ 清空地图 aMap.clear(); // ✅ 添加所有站点为色小圆点记 for (PoiItem station : stations) { LatLonPoint point = station.getLatLonPoint(); if (point == null) continue; aMap.addMarker(new MarkerOptions() .position(new LatLng(point.getLatitude(), point.getLongitude())) .title(station.getTitle()) .icon(BitmapDescriptorFactory.fromResource(R.drawable.ic_station_dot)) // 小点图 ); } // ✅ 如果只有一个站,则聚焦即可 if (stations.size() < 2) { PoiItem only = stations.get(0); LatLonPoint p = only.getLatLonPoint(); if (p != null) { aMap.animateCamera(CameraUpdateFactory.newLatLngZoom( new LatLng(p.getLatitude(), p.getLongitude()), 14f)); } return; } // ✅ 取首尾站发起公交路线请求以获取真实路径 PoiItem startStation = stations.get(0); PoiItem endStation = stations.get(stations.size() - 1); LatLonPoint start = startStation.getLatLonPoint(); LatLonPoint end = endStation.getLatLonPoint(); if (start == null || end == null) return; RouteSearch routeSearch = new RouteSearch(this); RouteSearch.FromAndTo fromAndTo = new RouteSearch.FromAndTo(start, end); RouteSearch.BusRouteQuery query = new RouteSearch.BusRouteQuery(fromAndTo, RouteSearch.BUS_DEFAULT, currentCity, 0); routeSearch.setRouteSearchListener(new RouteSearch.OnRouteSearchListener() { @Override public void onBusRouteSearched(BusRouteResult result, int rCode) { if (rCode == 1000 && result != null && !result.getPaths().isEmpty()) { BusPath path = result.getPaths().get(0); List<LatLng> points = new ArrayList<>(); for (BusStep step : path.getSteps()) { if (step.getBusLines() != null && !step.getBusLines().isEmpty()) { List<LatLonPoint> linePoints = step.getBusLines().get(0).getPolyline(); for (LatLonPoint lp : linePoints) { points.add(new LatLng(lp.getLatitude(), lp.getLongitude())); } } } if (!points.isEmpty()) { PolylineOptions options = new PolylineOptions() .color(0xFFE91E63) // 粉色主线 .width(8f) .addAll(points); aMap.addPolyline(options); } } // ✅ 即使没拿到路线,也聚焦到站点区域 focusMapOnStations(stations); } @Override public void onDriveRouteSearched(com.amap.api.services.route.DriveRouteResult r, int c) {} @Override public void onWalkRouteSearched(com.amap.api.services.route.WalkRouteResult r, int c) {} @Override public void onRideRouteSearched(com.amap.api.services.route.RideRouteResult r, int c) {} }); routeSearch.calculateBusRouteAsyn(query); } /** * 聚焦地图到所有站点范围 */ private void focusMapOnStations(List<PoiItem> stations) { LatLngBounds.Builder builder = new LatLngBounds.Builder(); for (PoiItem s : stations) { LatLonPoint p = s.getLatLonPoint(); if (p != null) { builder.include(new LatLng(p.getLatitude(), p.getLongitude())); } } try { aMap.animateCamera(CameraUpdateFactory.newLatLngBounds(builder.build(), 100)); } catch (Exception e) { // bounds 可能太小或为空 } } private void fallbackToOriginalSearch(String keyword) { // 直接调用原搜索逻辑 CityManager.ParsedQuery parsed = CityManager.parse(keyword); String searchKeyword = parsed.keyword.isEmpty() ? keyword : parsed.keyword; String explicitCity = parsed.targetCity; if (!explicitCity.isEmpty()) { PoiSearch.Query query = new PoiSearch.Query(searchKeyword, "", explicitCity); query.setPageSize(20); try { PoiSearch search = new PoiSearch(this, query); search.setOnPoiSearchListener(SearchResultActivity.this); search.searchPOIAsyn(); } catch (Exception e) { emptyView.setText("⚠️ 未找到相关地点"); emptyView.setVisibility(View.VISIBLE); resultListView.setVisibility(View.GONE); } } else { nationalResults.clear(); localResults.clear(); PoiSearch.Query nationalQuery = new PoiSearch.Query(searchKeyword, "", ""); nationalQuery.setPageSize(20); try { PoiSearch ns = new PoiSearch(this, nationalQuery); ns.setOnPoiSearchListener(new PoiSearch.OnPoiSearchListener() { @Override public void onPoiSearched(PoiResult result, int rCode) { if (rCode == 1000 && result != null && result.getPois() != null) { nationalResults.clear(); nationalResults.addAll(result.getPois()); } requestLocalSearch(searchKeyword); } @Override public void onPoiItemSearched(PoiItem item, int rCode) {} }); ns.searchPOIAsyn(); } catch (Exception e) { requestLocalSearch(searchKeyword); } } } private void requestLocalSearch(String keyword) { if (currentCity.isEmpty()) { showCombinedResults(); return; } PoiSearch.Query localQuery = new PoiSearch.Query(keyword, "", currentCity); localQuery.setPageSize(20); try { PoiSearch localSearch = new PoiSearch(this, localQuery); localSearch.setOnPoiSearchListener(new PoiSearch.OnPoiSearchListener() { @Override public void onPoiSearched(PoiResult result, int rCode) { if (rCode == 1000 && result != null && result.getPois() != null) { localResults.clear(); localResults.addAll(result.getPois()); } showCombinedResults(); } @Override public void onPoiItemSearched(PoiItem item, int rCode) {} }); localSearch.searchPOIAsyn(); } catch (Exception e) { e.printStackTrace(); showCombinedResults(); } } /** * 排序规则: * 1. 核心关键词匹配度最高优先 * 2. 若匹配度相同 → 本市结果优先 * 3. 高匹配度全国结果可排在低匹配度本地结果前 */ private void showCombinedResults() { String keyword = searchInput.getText().toString().trim(); CityManager.ParsedQuery parsed = CityManager.parse(keyword); final String coreKeyword = (parsed.keyword.isEmpty() ? keyword : parsed.keyword).toLowerCase().trim(); Set<String> seen = new HashSet<>(); List<PoiItem> combined = new ArrayList<>(); // 合并去重 for (PoiItem item : localResults) { if (seen.add(item.getPoiId())) { combined.add(item); } } for (PoiItem item : nationalResults) { if (seen.add(item.getPoiId())) { combined.add(item); } } // ✅ 排序:使用 final 变量,并将评分逻辑封装在局部 final 引用中 final String finalCoreKeyword = coreKeyword; combined.sort((a, b) -> { int scoreA = calculateMatchScore(a.getTitle(), finalCoreKeyword); int scoreB = calculateMatchScore(b.getTitle(), finalCoreKeyword); if (scoreA != scoreB) { return Integer.compare(scoreB, scoreA); // 高分优先 } boolean aIsLocal = isSameCity(getDisplayCity(a), currentCity); boolean bIsLocal = isSameCity(getDisplayCity(b), currentCity); if (aIsLocal && !bIsLocal) return -1; if (!aIsLocal && bIsLocal) return 1; return 0; }); adapter.clearExtraText(); for (PoiItem item : combined) { String city = getDisplayCity(item); adapter.setExtraText(item, " | " + city); } updateResultList(combined); } /** * 计算题与关键词的核心匹配得分 * 规则: * - 完全包含关键词:+50 * - 开头匹配加分 * - 分词命中加分 */ private int calculateMatchScore(String title, String keyword) { if (title == null || keyword == null || title.isEmpty() || keyword.isEmpty()) { return 0; } title = title.toLowerCase(); int score = 0; // 完整包含关键词 if (title.contains(keyword)) { score += 50; // 越靠前分越高 int index = title.indexOf(keyword); if (index == 0) score += 20; else if (index < 4) score += 10; } // 分词命中(如“北京 大学”拆开都出现) String[] words = keyword.split("\\s+"); int matchCount = 0; for (String word : words) { if (word.length() > 1 && title.contains(word)) { matchCount++; } } if (matchCount == words.length) { score += 30; // 全部命中 } else { score += matchCount * 8; // 部分命中也有分 } return score; } private String getDisplayCity(PoiItem item) { if (item == null) return "未知城市"; String city = item.getCityName(); if (city != null && !city.isEmpty() && !city.equals("[]")) { return city; } String adName = item.getAdName(); if (adName != null && !adName.isEmpty() && !adName.equals("[]")) { return adName; } String province = item.getProvinceName(); if (province != null && !province.isEmpty()) { return province; } return "未知城市"; } private void enterNearbyMode() { if (!isLocationReady) { Toast.makeText(this, "正在获取位置...", Toast.LENGTH_SHORT).show(); return; } String keyword = searchInput.getText().toString().trim(); CityManager.ParsedQuery parsed = CityManager.parse(keyword); String explicitCity = parsed.targetCity; String searchKeyword = parsed.keyword.isEmpty() ? keyword : parsed.keyword; // 🔴【关键】如果指定城市非当前城市,则禁止 nearby if (!explicitCity.isEmpty() && !isSameCity(explicitCity, currentCity)) { nearbyResults.clear(); localResults.clear(); nationalResults.clear(); poiList.clear(); adapter.notifyDataSetChanged(); emptyView.setText("📍 所选城市非当前所在城市\n无法搜索附近"); emptyView.setVisibility(View.VISIBLE); resultListView.setVisibility(View.GONE); isInNearbyMode = false; return; } // 只有同城才允许加载 nearby if (!isNearbyLoaded) { startNearbySearch(searchKeyword); } else { showNearbyResults(); } isInNearbyMode = true; btnToggleMode.setText("🌐 全范围"); } private boolean isSameCity(String city1, String city2) { if (city1 == null || city2 == null) return false; String c1 = city1.endsWith("市") ? city1.substring(0, city1.length() - 1) : city1; String c2 = city2.endsWith("市") ? city2.substring(0, city2.length() - 1) : city2; return c1.equals(c2); } private void exitNearbyMode() { showCombinedResults(); isInNearbyMode = false; btnToggleMode.setText("📍 附近"); } // ✅【重点增强】增加内部防护 private void startNearbySearch(String keyword) { String rawKeyword = searchInput.getText().toString().trim(); CityManager.ParsedQuery parsed = CityManager.parse(rawKeyword); String explicitCity = parsed.targetCity; // 🔒 再次检查是否跨城(防御性编程) if (!explicitCity.isEmpty() && !isSameCity(explicitCity, currentCity)) { Log.w("SearchResult", "拒绝发起跨城 nearby 搜索: " + explicitCity); emptyView.setText("📍 所选城市非当前所在城市\n无法搜索附近"); emptyView.setVisibility(View.VISIBLE); resultListView.setVisibility(View.GONE); return; } LatLonPoint center = new LatLonPoint(myCurrentLat, myCurrentLng); PoiSearch.Query query = new PoiSearch.Query(keyword, "", ""); query.setPageSize(20); try { PoiSearch nearbySearch = new PoiSearch(this, query); nearbySearch.setBound(new PoiSearch.SearchBound(center, 3000)); nearbySearch.setOnPoiSearchListener(new PoiSearch.OnPoiSearchListener() { @Override public void onPoiSearched(PoiResult res, int code) { if (code == 1000 && res != null && res.getPois() != null && !res.getPois().isEmpty()) { nearbyResults.clear(); nearbyResults.addAll(sortByDistance(res.getPois(), myCurrentLat, myCurrentLng)); isNearbyLoaded = true; showNearbyResults(); } else { emptyView.setText("⚠️ 附近未找到地点"); emptyView.setVisibility(View.VISIBLE); resultListView.setVisibility(View.GONE); } } @Override public void onPoiItemSearched(PoiItem item, int rCode) {} }); nearbySearch.searchPOIAsyn(); } catch (Exception e) { e.printStackTrace(); emptyView.setText("⚠️ 附近搜索失败"); emptyView.setVisibility(View.VISIBLE); resultListView.setVisibility(View.GONE); } } private void showNearbyResults() { List<PoiItem> list = new ArrayList<>(nearbyResults); adapter.clearExtraText(); LatLng me = new LatLng(myCurrentLat, myCurrentLng); for (PoiItem item : nearbyResults) { double dist = com.amap.api.maps.AMapUtils.calculateLineDistance(toLatLng(item.getLatLonPoint()), me); String distText = dist < 1000 ? ((int) dist) + "m" : String.format("%.1fkm", dist / 1000); adapter.setExtraText(item, " | " + distText); } updateResultList(list); } private void onPoiItemSelected(PoiItem item) { LatLng latLng = new LatLng(item.getLatLonPoint().getLatitude(), item.getLatLonPoint().getLongitude()); if (selectedMarker != null) { selectedMarker.remove(); selectedMarker = null; } selectedMarker = aMap.addMarker(new MarkerOptions() .position(latLng) .title("终点:" + item.getTitle()) .icon(com.amap.api.maps.model.BitmapDescriptorFactory.defaultMarker( com.amap.api.maps.model.BitmapDescriptorFactory.HUE_RED))); aMap.animateCamera(CameraUpdateFactory.newLatLngZoom(latLng, 14f)); goToBtn.setEnabled(true); } private LatLng toLatLng(LatLonPoint point) { if (point == null) return null; return new LatLng(point.getLatitude(), point.getLongitude()); } private List<PoiItem> sortByDistance(List<PoiItem> list, double lat, double lng) { LatLng me = new LatLng(lat, lng); return list.stream() .sorted((a, b) -> { double da = com.amap.api.maps.AMapUtils.calculateLineDistance(toLatLng(a.getLatLonPoint()), me); double db = com.amap.api.maps.AMapUtils.calculateLineDistance(toLatLng(b.getLatLonPoint()), me); return Double.compare(da, db); }) .collect(java.util.stream.Collectors.toList()); } @Override public void onPoiSearched(PoiResult result, int rCode) { String keyword = searchInput.getText().toString().trim(); CityManager.ParsedQuery parsed = CityManager.parse(keyword); if (parsed.targetCity.isEmpty()) return; if (rCode == 1000 && result != null && result.getPois() != null && !result.getPois().isEmpty()) { updateResultList(result.getPois()); } else { if (rCode == 1000) { emptyView.setText("⚠️ 未找到相关地点"); } else { // 是 API 错误 handleSearchError(rCode); emptyView.setText("⚠️ 未找到相关地点"); } emptyView.setVisibility(View.VISIBLE); resultListView.setVisibility(View.GONE); } } private void updateResultList(List<PoiItem> list) { poiList.clear(); poiList.addAll(list); adapter.notifyDataSetChanged(); resultListView.scrollToPosition(0); emptyView.setVisibility(list.isEmpty() ? View.VISIBLE : View.GONE); resultListView.setVisibility(list.isEmpty() ? View.GONE : View.VISIBLE); // 清理 ResultAdapter 中的附加文本(避免显示“ | 北京市”) adapter.clearExtraText(); if (!list.isEmpty()) { adapter.setSelected(0); onPoiItemSelected(list.get(0)); } } @Override public void onPoiItemSearched(PoiItem item, int rCode) {} @Override public void onRegeocodeSearched(RegeocodeResult result, int rCode) { if (result == null || result.getRegeocodeQuery() == null) return; LatLonPoint point = result.getRegeocodeQuery().getPoint(); if (rCode == 1000 && result.getRegeocodeAddress() != null) { String city = result.getRegeocodeAddress().getCity(); String updatedCity = (city != null && !city.isEmpty()) ? city : result.getRegeocodeAddress().getProvince(); if (Math.abs(point.getLatitude() - myCurrentLat) < 0.0001 && Math.abs(point.getLongitude() - myCurrentLng) < 0.0001) { currentCity = updatedCity; if (suggestHelper != null) { suggestHelper.setCurrentCity(currentCity); } Log.d("SearchResultActivity", "🎯 currentCity 已更新为: " + currentCity); } } else { Log.e("SearchResultActivity", "❌ 反编译失败: rCode=" + rCode); } } private void handleSearchError(int rCode) { String msg; switch (rCode) { case 12: msg = "API Key 错误"; break; case 27: msg = "网络连接失败"; break; case 30: msg = "SHA1 或包名错误"; break; case 33: msg = "请求频繁"; break; default: msg = "搜索失败: " + rCode; break; } Toast.makeText(this, msg, Toast.LENGTH_SHORT).show(); } @Override public void onGeocodeSearched(com.amap.api.services.geocoder.GeocodeResult geocodeResult, int i) {} @Override protected void onResume() { super.onResume(); mapView.onResume(); setupSearchSuggestion(); } @Override protected void onPause() { super.onPause(); mapView.onPause(); } @Override protected void onDestroy() { super.onDestroy(); mapView.onDestroy(); geocodeSearch = null; } @Override protected void onSaveInstanceState(@NonNull Bundle outState) { super.onSaveInstanceState(outState); mapView.onSaveInstanceState(outState); } @Override public boolean onSupportNavigateUp() { onBackPressed(); return true; } } 报错:Cannot resolve symbol 'BitmapDescriptorFactory'、Cannot resolve symbol 'RouteSearch'、Cannot resolve method 'setRouteSearchListener(RouteSearch.OnRouteSearchListener)'、Cannot resolve symbol 'BusRouteResult'、Cannot resolve method 'getPaths()'、Cannot resolve symbol 'BusPath'、Cannot resolve method 'getBusLines()'、Cannot resolve symbol 'PolylineOptions'、'addPolyline(com.amap.api.maps.model.PolylineOptions)' in 'com.amap.api.maps.AMap' cannot be applied to '(PolylineOptions)'、Cannot resolve method 'calculateBusRouteAsyn(BusRouteQuery)'、Cannot resolve symbol 'LatLngBounds'、Cannot resolve method 'include(LatLng)'、Cannot resolve method 'build()'、
最新发布
12-17
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值