目前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()'、
最新发布