不对,先说第一个问题,第一次点击搜索框的时候他不会弹出提示需要精准定位权限。我还想问一下MapFragment这边是怎么实现的,好像这边的话比较稳妥,代码如下:package com.example.bus.ui.map;
import android.Manifest;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.text.Editable;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.ArrayAdapter;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.constraintlayout.widget.ConstraintSet;
import com.amap.api.maps.AMap;
import com.amap.api.maps.AMapUtils;
import com.amap.api.maps.CameraUpdateFactory;
import com.amap.api.maps.MapView;
import com.amap.api.maps.UiSettings;
import com.amap.api.maps.model.BitmapDescriptorFactory;
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 com.example.bus.CityManager;
import com.example.bus.R;
import com.example.bus.RealTimePoiSuggestHelper;
import com.example.bus.RoutePlanActivity;
import com.example.bus.ResultAdapter;
import com.example.bus.databinding.FragmentMapBinding;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
public class MapFragment extends Fragment implements PoiSearch.OnPoiSearchListener, GeocodeSearch.OnGeocodeSearchListener {
private FragmentMapBinding binding;
private MapView mapView;
private AMap aMap;
private RealTimePoiSuggestHelper activeSuggestHelper; // ← 我们会在这里初始化
// 数据
private List<PoiItem> poiList = new ArrayList<>();
private ResultAdapter adapter;
// 当前阶段:1=选择起点, 2=选择终点
private int selectionStage = 0;
// 缓存已选 POI
private PoiItem selectedStartPoi = null;
private PoiItem selectedEndPoi = null;
private Marker startMarker = null;
private Marker endMarker = null;
// 缓存关键词
private String lastStartKeyword = "";
private String lastEndKeyword = "";
// ✅ 当前城市
private String currentCity = "";
private static final int LOCATION_PERMISSION_REQUEST_CODE = 1001;
// ✅ 标记是否已居中我的位置
private boolean userHasInteracted = false;
// ✅ 反地理编码(仅用于“我的位置”)
private GeocodeSearch geocodeSearch;
// 【关键新增】保存定位得到的“我的位置”
private double myCurrentLat = 0;
private double myCurrentLng = 0;
private boolean isLocationReady = false; // 定位是否完成
// 🔽 新增缓存字段
private List<PoiItem> nationalResults = new ArrayList<>(); // 全国结果
private List<PoiItem> localResults = new ArrayList<>(); // 本市结果
private List<PoiItem> nearbyResults = new ArrayList<>(); // 附近结果
private boolean isNearbyLoaded = false; // 是否已加载 nearby 数据
private boolean isInNearbyMode = false; // 是否处于附近模式
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
binding = FragmentMapBinding.inflate(inflater, container, false);
View root = binding.getRoot();
mapView = binding.mapView;
mapView.onCreate(savedInstanceState);
initViews();
setupMap(savedInstanceState);
setupSearchSuggestion(); // ← 这个方法被重写了
return root;
}
private void initViews() {
adapter = new ResultAdapter(poiList, this::onPoiItemSelected);
binding.resultList.setLayoutManager(new LinearLayoutManager(requireContext()));
binding.resultList.setAdapter(adapter);
binding.mapSearch.setOnClickListener(v -> performSearch());
binding.btnSwitchTarget.setOnClickListener(v -> {
if (selectionStage == 1) {
showEndpointSelection(binding.mapInput2.getText().toString().trim());
} else if (selectionStage == 2) {
showStartpointSelection(binding.mapInput1.getText().toString().trim());
}
});
binding.btnGoTo.setOnClickListener(v -> {
if (selectedStartPoi != null && selectedEndPoi != null) {
Intent intent = new Intent(requireContext(), RoutePlanActivity.class);
intent.putExtra(RoutePlanActivity.EXTRA_SOURCE, RoutePlanActivity.SOURCE_FROM_MAP_DIRECT);
intent.putExtra("start_lat", selectedStartPoi.getLatLonPoint().getLatitude());
intent.putExtra("start_lng", selectedStartPoi.getLatLonPoint().getLongitude());
intent.putExtra("target_lat", selectedEndPoi.getLatLonPoint().getLatitude());
intent.putExtra("target_lng", selectedEndPoi.getLatLonPoint().getLongitude());
intent.putExtra("target_title", selectedEndPoi.getTitle());
startActivity(intent);
} else {
Toast.makeText(requireContext(), "请完成起点和终点的选择", Toast.LENGTH_SHORT).show();
}
});
binding.btnToggleMode.setOnClickListener(v -> {
if (isInNearbyMode) {
exitNearbyMode();
} else {
enterNearbyMode();
}
});
}
private void performSearch() {
String startKeyword = binding.mapInput1.getText().toString().trim();
String endKeyword = binding.mapInput2.getText().toString().trim();
if (startKeyword.isEmpty()) {
Toast.makeText(requireContext(), "请输入起点", Toast.LENGTH_SHORT).show();
return;
}
if (endKeyword.isEmpty()) {
Toast.makeText(requireContext(), "请输入终点", Toast.LENGTH_SHORT).show();
return;
}
if (startKeyword.equals(lastStartKeyword) &&
endKeyword.equals(lastEndKeyword) &&
selectedStartPoi != null &&
selectedEndPoi != null) {
binding.btnGoTo.performClick();
return;
}
binding.containerResultList.setVisibility(View.VISIBLE);
binding.buttonGroup.setVisibility(View.VISIBLE);
ConstraintSet constraintSet = new ConstraintSet();
constraintSet.clone(binding.getRoot());
constraintSet.connect(
R.id.map_view, ConstraintSet.BOTTOM,
R.id.container_result_list, ConstraintSet.TOP,
0
);
constraintSet.applyTo(binding.getRoot());
userHasInteracted = true;
View currentFocus = requireActivity().getCurrentFocus();
if (currentFocus != null) {
currentFocus.clearFocus();
InputMethodManager imm = (InputMethodManager) requireContext().getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(currentFocus.getWindowToken(), 0);
}
if (!startKeyword.equals(lastStartKeyword)) {
lastStartKeyword = startKeyword;
lastEndKeyword = endKeyword;
showStartpointSelection(startKeyword);
} else if (!endKeyword.equals(lastEndKeyword)) {
lastEndKeyword = endKeyword;
showEndpointSelection(endKeyword);
} else if (selectedStartPoi == null) {
showStartpointSelection(startKeyword);
} else {
showEndpointSelection(endKeyword);
}
}
private void showStartpointSelection(String keyword) {
selectionStage = 1;
binding.btnSwitchTarget.setText("选择终点");
binding.btnGoTo.setEnabled(false);
binding.btnToggleMode.setText("📍 附近");
binding.emptyView.setText("🔍 搜索起点中...");
binding.emptyView.setVisibility(View.VISIBLE);
binding.resultList.setVisibility(View.GONE);
doSearch(keyword);
}
private void showEndpointSelection(String keyword) {
selectionStage = 2;
binding.btnSwitchTarget.setText("选择起点");
binding.btnGoTo.setEnabled(false);
binding.btnToggleMode.setText("📍 附近");
binding.emptyView.setText("🔍 搜索终点中...");
binding.emptyView.setVisibility(View.VISIBLE);
binding.resultList.setVisibility(View.GONE);
doSearch(keyword);
}
private void doSearch(String keyword) {
if (keyword.isEmpty()) return;
nationalResults.clear();
localResults.clear();
nearbyResults.clear();
isNearbyLoaded = false;
isInNearbyMode = false;
binding.btnToggleMode.setText("📍 附近");
binding.emptyView.setText("🔍 搜索中...");
binding.emptyView.setVisibility(View.VISIBLE);
binding.resultList.setVisibility(View.GONE);
// ✅ 线路判断
boolean isLineQuery = keyword.matches(".*?(地铁|公交|轻轨|云巴|磁浮|有轨电车|brt|apm).*?\\d+.*") ||
Pattern.compile("(s|r|x)\\d+", Pattern.CASE_INSENSITIVE).matcher(keyword).find() ||
keyword.contains("云巴") || keyword.contains("轻轨");
if (isLineQuery) {
String numberPart = keyword.replaceAll("[^\\d]", "");
String baseKeyword = numberPart.isEmpty() ? keyword : numberPart + "号线";
String poiType = keyword.contains("地铁") || keyword.contains("轨道") ? "subway" :
keyword.contains("公交") ? "bus_station" :
keyword.contains("云巴") ? "bus_station" :
keyword.contains("轻轨") ? "light_rail" : "";
PoiSearch.Query query = new PoiSearch.Query(baseKeyword, poiType, currentCity.isEmpty() ? "" : currentCity);
query.setPageSize(50);
try {
PoiSearch search = new PoiSearch(requireContext(), query);
search.setOnPoiSearchListener(new PoiSearch.OnPoiSearchListener() {
@Override
public void onPoiSearched(PoiResult result, int rCode) {
if (rCode == 1000 && result != null && result.getPois() != null) {
List<PoiItem> filtered = new ArrayList<>();
String lowerKey = keyword.toLowerCase();
for (PoiItem item : result.getPois()) {
String title = item.getTitle().toLowerCase();
if (title.contains(lowerKey) || title.contains(baseKeyword.toLowerCase())) {
filtered.add(item);
}
}
if (!filtered.isEmpty()) {
updateResultList(filtered);
return;
}
}
// 失败 → 降级
fallbackToOriginalSearch(keyword);
}
@Override
public void onPoiItemSearched(PoiItem item, int rCode) {}
});
search.searchPOIAsyn();
return;
} catch (Exception e) {
// 忽略异常,继续走原逻辑
}
}
// ❌ 原有逻辑(完全保留)
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(requireContext(), query);
search.setOnPoiSearchListener(this);
search.searchPOIAsyn();
} catch (Exception e) {
Toast.makeText(requireContext(), "搜索失败", Toast.LENGTH_SHORT).show();
}
} else {
binding.emptyView.setText("🔍 搜索中...");
binding.emptyView.setVisibility(View.VISIBLE);
binding.resultList.setVisibility(View.GONE);
PoiSearch.Query nationalQuery = new PoiSearch.Query(searchKeyword, "", "");
nationalQuery.setPageSize(20);
try {
PoiSearch nationalSearch = new PoiSearch(requireContext(), 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);
}
}
}
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(requireContext(), query);
search.setOnPoiSearchListener(MapFragment.this);
search.searchPOIAsyn();
} catch (Exception e) {
binding.emptyView.setText("⚠️ 未找到相关地点");
binding.emptyView.setVisibility(View.VISIBLE);
binding.resultList.setVisibility(View.GONE);
}
} else {
nationalResults.clear();
localResults.clear();
PoiSearch.Query nationalQuery = new PoiSearch.Query(searchKeyword, "", "");
nationalQuery.setPageSize(20);
try {
PoiSearch ns = new PoiSearch(requireContext(), 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(requireContext(), 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();
}
}
private void showCombinedResults() {
List<PoiItem> combined = new ArrayList<>();
Set<String> seen = new HashSet<>();
adapter.clearExtraText();
for (PoiItem item : localResults) {
if (seen.add(item.getPoiId())) {
combined.add(item);
String city = getDisplayCity(item);
adapter.setExtraText(item, " | " + city);
}
}
for (PoiItem item : nationalResults) {
if (seen.add(item.getPoiId())) {
combined.add(item);
String city = getDisplayCity(item);
adapter.setExtraText(item, " | " + city);
}
}
updateResultList(combined);
}
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(requireContext(), "正在获取位置...", Toast.LENGTH_SHORT).show();
return;
}
String keyword = getCurrentKeyword();
CityManager.ParsedQuery parsed = CityManager.parse(keyword);
String explicitCity = parsed.targetCity;
String searchKeyword = parsed.keyword.isEmpty() ? keyword : parsed.keyword;
if (!explicitCity.isEmpty() && !isSameCity(explicitCity, currentCity)) {
nearbyResults.clear();
localResults.clear();
nationalResults.clear();
poiList.clear();
adapter.notifyDataSetChanged();
binding.emptyView.setText("📍 所选城市非当前所在城市\n无法搜索附近");
binding.emptyView.setVisibility(View.VISIBLE);
binding.resultList.setVisibility(View.GONE);
isInNearbyMode = false;
return;
}
if (!isNearbyLoaded) {
startNearbySearch(searchKeyword);
} else {
showNearbyResults();
}
isInNearbyMode = true;
binding.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;
binding.btnToggleMode.setText("📍 附近");
}
private void startNearbySearch(String keyword) {
String rawKeyword = getCurrentKeyword();
CityManager.ParsedQuery parsed = CityManager.parse(rawKeyword);
String explicitCity = parsed.targetCity;
if (!explicitCity.isEmpty() && !isSameCity(explicitCity, currentCity)) {
Log.w("MapFragment", "拒绝发起跨城 nearby 搜索: " + explicitCity);
binding.emptyView.setText("📍 所选城市非当前所在城市\n无法搜索附近");
binding.emptyView.setVisibility(View.VISIBLE);
binding.resultList.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(requireContext(), 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 {
binding.emptyView.setText("⚠️ 附近未找到地点");
binding.emptyView.setVisibility(View.VISIBLE);
binding.resultList.setVisibility(View.GONE);
}
}
@Override
public void onPoiItemSearched(PoiItem item, int rCode) {}
});
nearbySearch.searchPOIAsyn();
} catch (Exception e) {
e.printStackTrace();
binding.emptyView.setText("⚠️ 附近搜索失败");
binding.emptyView.setVisibility(View.VISIBLE);
binding.resultList.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 = 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 (selectionStage == 1) {
if (startMarker != null) startMarker.remove();
startMarker = aMap.addMarker(new MarkerOptions()
.position(latLng)
.title("起点:" + item.getTitle())
.icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_GREEN)));
selectedStartPoi = item;
aMap.animateCamera(CameraUpdateFactory.newLatLngZoom(latLng, 14f));
updateGoToButtonState();
} else if (selectionStage == 2) {
if (endMarker != null) endMarker.remove();
endMarker = aMap.addMarker(new MarkerOptions()
.position(latLng)
.title("终点:" + item.getTitle())
.icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_RED)));
selectedEndPoi = item;
aMap.animateCamera(CameraUpdateFactory.newLatLngZoom(latLng, 14f));
updateGoToButtonState();
}
userHasInteracted = true;
}
private void updateGoToButtonState() {
binding.btnGoTo.setEnabled(selectedStartPoi != null && selectedEndPoi != null);
}
@Override
public void onPoiSearched(PoiResult result, int rCode) {
String keyword = getCurrentKeyword();
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 {
handleSearchError(rCode);
binding.emptyView.setText("⚠️ 未找到相关地点");
binding.emptyView.setVisibility(View.VISIBLE);
binding.resultList.setVisibility(View.GONE);
}
}
private String getCurrentKeyword() {
return selectionStage == 1 ?
binding.mapInput1.getText().toString().trim() :
binding.mapInput2.getText().toString().trim();
}
private void updateResultList(List<PoiItem> list) {
poiList.clear();
poiList.addAll(list);
adapter.notifyDataSetChanged();
binding.resultList.scrollToPosition(0);
binding.emptyView.setVisibility(list.isEmpty() ? View.VISIBLE : View.GONE);
binding.resultList.setVisibility(list.isEmpty() ? View.GONE : View.VISIBLE);
if (!list.isEmpty()) {
adapter.setSelected(0);
onPoiItemSelected(list.get(0));
}
}
@Override
public void onPoiItemSearched(PoiItem item, int rCode) {}
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 = AMapUtils.calculateLineDistance(toLatLng(a.getLatLonPoint()), me);
double db = AMapUtils.calculateLineDistance(toLatLng(b.getLatLonPoint()), me);
return Double.compare(da, db);
})
.collect(java.util.stream.Collectors.toList());
}
private void setupMap(Bundle savedInstanceState) {
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();
}
});
}
try {
geocodeSearch = new GeocodeSearch(requireContext());
geocodeSearch.setOnGeocodeSearchListener(this);
} catch (Exception e) {
e.printStackTrace();
}
}
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(39.909186, 116.397411), 10f))
);
enableMyLocationLayer();
}
private void enableMyLocationLayer() {
if (aMap == null) return;
if (ContextCompat.checkSelfPermission(requireContext(), 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());
if (activeSuggestHelper != null) {
activeSuggestHelper.setLocationBias(location.getLatitude(), location.getLongitude());
}
myCurrentLat = location.getLatitude();
myCurrentLng = location.getLongitude();
isLocationReady = true;
aMap.animateCamera(CameraUpdateFactory.newLatLngZoom(curLatlng, 16f));
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();
}
aMap.setOnMyLocationChangeListener(null);
}
};
aMap.setOnMyLocationChangeListener(listener);
} else {
ActivityCompat.requestPermissions(requireActivity(),
new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
LOCATION_PERMISSION_REQUEST_CODE);
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if (requestCode == LOCATION_PERMISSION_REQUEST_CODE) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
if (aMap != null) {
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());
if (activeSuggestHelper != null) {
activeSuggestHelper.setLocationBias(location.getLatitude(), location.getLongitude());
}
myCurrentLat = location.getLatitude();
myCurrentLng = location.getLongitude();
isLocationReady = true;
aMap.animateCamera(CameraUpdateFactory.newLatLngZoom(curLatlng, 16f));
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();
}
aMap.setOnMyLocationChangeListener(null);
}
};
aMap.setOnMyLocationChangeListener(listener);
}
}
}
}
@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 (activeSuggestHelper != null) {
activeSuggestHelper.setCurrentCity(currentCity);
}
Log.d("MapFragment", "🎯 currentCity 已更新为: " + currentCity);
}
} else {
Log.e("MapFragment", "❌ 反编译失败: rCode=" + rCode);
}
}
@Override
public void onGeocodeSearched(com.amap.api.services.geocoder.GeocodeResult geocodeResult, int i) {}
@Override
public void onResume() {
super.onResume();
mapView.onResume();
if (!userHasInteracted) {
enableMyLocationLayer();
}
setupSearchSuggestion();
}
@Override
public void onPause() {
super.onPause();
mapView.onPause();
}
@Override
public void onDestroyView() {
super.onDestroyView();
mapView.onDestroy();
geocodeSearch = null;
binding = null;
}
@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
mapView.onSaveInstanceState(outState);
}
private void setupSearchSuggestion() {
Handler handler = new Handler(Looper.getMainLooper());
Runnable[] pendingRunnable1 = {null};
Runnable[] pendingRunnable2 = {null};
// 输入框1:起点输入建议
binding.mapInput1.addTextChangedListener(new android.text.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 (pendingRunnable1[0] != null) {
handler.removeCallbacks(pendingRunnable1[0]);
}
if (s.length() == 0) {
binding.mapInput1.setAdapter(null);
return;
}
String keyword = s.toString().trim();
pendingRunnable1[0] = () -> fetchSuggestionTitles(keyword, binding.mapInput1);
handler.postDelayed(pendingRunnable1[0], 300);
}
@Override
public void afterTextChanged(Editable s) {}
});
// 输入框2:终点输入建议
binding.mapInput2.addTextChangedListener(new android.text.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 (pendingRunnable2[0] != null) {
handler.removeCallbacks(pendingRunnable2[0]);
}
if (s.length() == 0) {
binding.mapInput2.setAdapter(null);
return;
}
String keyword = s.toString().trim();
pendingRunnable2[0] = () -> fetchSuggestionTitles(keyword, binding.mapInput2);
handler.postDelayed(pendingRunnable2[0], 300);
}
@Override
public void afterTextChanged(Editable s) {}
});
// 软键盘搜索事件
binding.mapInput1.setOnEditorActionListener((v, actionId, event) -> {
if ((actionId & EditorInfo.IME_MASK_ACTION) == EditorInfo.IME_ACTION_SEARCH) {
performSearch();
return true;
}
return false;
});
binding.mapInput2.setOnEditorActionListener((v, actionId, event) -> {
if ((actionId & EditorInfo.IME_MASK_ACTION) == EditorInfo.IME_ACTION_SEARCH) {
performSearch();
return true;
}
return false;
});
// 如果已定位成功,不需要再设 bias(因为我们不再使用 suggestHelper)
}
private void fetchSuggestionTitles(String keyword, android.widget.AutoCompleteTextView inputView) {
if (keyword.isEmpty()) 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(requireContext(), query);
search.setOnPoiSearchListener(new PoiSearch.OnPoiSearchListener() {
@Override
public void onPoiSearched(PoiResult result, int rCode) {
if (rCode == 1000 && result != null && result.getPois() != null) {
List<String> titles = result.getPois().stream()
.map(PoiItem::getTitle)
.collect(java.util.stream.Collectors.toList());
updateSuggestionAdapter(titles.toArray(new String[0]), inputView);
} else {
updateSuggestionAdapter(new String[0], inputView);
}
}
@Override
public void onPoiItemSearched(PoiItem item, int rCode) {}
});
search.searchPOIAsyn();
} catch (Exception e) {
updateSuggestionAdapter(new String[0], inputView);
}
return;
}
// 否则走两级查询:national + local
List<PoiItem> tempNational = new ArrayList<>();
List<PoiItem> tempLocal = new ArrayList<>();
PoiSearch.Query nationalQuery = new PoiSearch.Query(searchKeyword, "", "");
nationalQuery.setPageSize(20);
try {
PoiSearch nationalSearch = new PoiSearch(requireContext(), nationalQuery);
nationalSearch.setOnPoiSearchListener(new PoiSearch.OnPoiSearchListener() {
@Override
public void onPoiSearched(PoiResult result, int rCode) {
if (rCode == 1000 && result != null && result.getPois() != null) {
tempNational.addAll(result.getPois());
}
requestLocalForSuggestion(searchKeyword, tempNational, tempLocal, inputView);
}
@Override
public void onPoiItemSearched(PoiItem item, int rCode) {}
});
nationalSearch.searchPOIAsyn();
} catch (Exception e) {
requestLocalForSuggestion(searchKeyword, tempNational, tempLocal, inputView);
}
}
private void requestLocalForSuggestion(String keyword, List<PoiItem> national, List<PoiItem> local, android.widget.AutoCompleteTextView inputView) {
if (currentCity.isEmpty()) {
showSuggestionResult(national, local, inputView);
return;
}
PoiSearch.Query localQuery = new PoiSearch.Query(keyword, "", currentCity);
localQuery.setPageSize(20);
try {
PoiSearch localSearch = new PoiSearch(requireContext(), localQuery);
localSearch.setOnPoiSearchListener(new PoiSearch.OnPoiSearchListener() {
@Override
public void onPoiSearched(PoiResult result, int rCode) {
if (rCode == 1000 && result != null && result.getPois() != null) {
local.addAll(result.getPois());
}
showSuggestionResult(national, local, inputView);
}
@Override
public void onPoiItemSearched(PoiItem item, int rCode) {}
});
localSearch.searchPOIAsyn();
} catch (Exception e) {
showSuggestionResult(national, local, inputView);
}
}
private void showSuggestionResult(List<PoiItem> national, List<PoiItem> local, android.widget.AutoCompleteTextView inputView) {
Set<String> seen = new HashSet<>();
List<String> combined = new ArrayList<>();
for (PoiItem item : local) {
if (seen.add(item.getPoiId())) {
combined.add(item.getTitle());
}
}
for (PoiItem item : national) {
if (seen.add(item.getPoiId())) {
combined.add(item.getTitle());
}
}
updateSuggestionAdapter(combined.toArray(new String[0]), inputView);
}
private void updateSuggestionAdapter(String[] suggestions, android.widget.AutoCompleteTextView inputView) {
if (suggestions.length > 0) {
ArrayAdapter<String> adapter = new ArrayAdapter<>(
requireContext(),
android.R.layout.simple_dropdown_item_1line,
suggestions
) {
@Override
public View getView(int position, View convertView, android.view.ViewGroup parent) {
View view = super.getView(position, convertView, parent);
// 为每个 item 添加点击事件
view.setOnClickListener(v -> {
String selectedText = getItem(position);
if (selectedText != null) {
inputView.setText(selectedText);
inputView.clearFocus();
// 隐藏软键盘
InputMethodManager imm = (InputMethodManager) requireContext()
.getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(inputView.getWindowToken(), 0);
// ✅ 模拟点击搜索按钮(触发 performSearch)
performSearch();
}
});
return view;
}
};
new Handler(Looper.getMainLooper()).post(() -> {
inputView.setAdapter(adapter);
if (requireActivity().getCurrentFocus() == inputView) {
inputView.showDropDown();
}
});
} else {
new Handler(Looper.getMainLooper()).post(() -> inputView.setAdapter(null));
}
}
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(requireContext(), msg, Toast.LENGTH_SHORT).show();
}
}
而HomeFragmenet代码如下:package com.example.bus.ui.home;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.ArrayAdapter;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import com.example.bus.MainActivity;
import com.example.bus.RealTimePoiSuggestHelper;
import com.example.bus.SearchResultActivity;
import com.example.bus.databinding.FragmentHomeBinding;
public class HomeFragment extends Fragment {
private FragmentHomeBinding binding;
private HomeViewModel homeViewModel;
private static final int TIPS_DELAY = 300; // 防抖延迟
private Handler handler = new Handler(Looper.getMainLooper());
private Runnable pendingRequest;
@Override
public View onCreateView(@NonNull LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
binding = FragmentHomeBinding.inflate(inflater, container, false);
View root = binding.getRoot();
// 初始化 ViewModel
homeViewModel = new ViewModelProvider(this).get(HomeViewModel.class);
homeViewModel.getText().observe(getViewLifecycleOwner(), text -> {
binding.textHome.setText(text);
});
setupSearchSuggestion();
setupSearchAction();
setupSearchBoxWithPermissionControl();
return root;
}
private void setupSearchBoxWithPermissionControl() {
binding.homeInput.setOnClickListener(v -> {
MainActivity activity = (MainActivity) requireActivity();
activity.ensureFineLocationPermission(() -> {
// 权限已获得,现在才允许 EditText 获取焦点并弹出软键盘
binding.homeInput.post(() -> {
binding.homeInput.setFocusableInTouchMode(true);
binding.homeInput.requestFocus();
InputMethodManager imm = (InputMethodManager) requireContext()
.getSystemService(Context.INPUT_METHOD_SERVICE);
if (imm != null) {
imm.showSoftInput(binding.homeInput, InputMethodManager.SHOW_IMPLICIT);
}
});
});
});
}
/**
* 设置输入建议功能(suggestion)
*/
private void setupSearchSuggestion() {
// 创建建议助手
RealTimePoiSuggestHelper suggestHelper = new RealTimePoiSuggestHelper(requireContext());
suggestHelper.setCurrentCity("全国"); // 可改为动态城市
suggestHelper.setCallback(suggestions -> {
if (suggestions.length > 0) {
ArrayAdapter<String> adapter = new ArrayAdapter<>(
requireContext(),
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()) {
binding.homeInput.setText(selectedText);
binding.homeInput.clearFocus();
// 隐藏软键盘
InputMethodManager imm = (InputMethodManager) requireContext()
.getSystemService(Context.INPUT_METHOD_SERVICE);
if (imm != null) {
imm.hideSoftInputFromWindow(binding.homeInput.getWindowToken(), 0);
}
// 直接触发搜索(跳转到 SearchResultActivity)
performSearch();
}
});
return view;
}
};
// 主线程更新 UI
new Handler(Looper.getMainLooper()).post(() -> {
binding.homeInput.setAdapter(adapter);
if (requireActivity().getCurrentFocus() == binding.homeInput) {
binding.homeInput.showDropDown();
}
});
} else {
new Handler(Looper.getMainLooper()).post(() ->
binding.homeInput.setAdapter(null)
);
}
});
// 防抖控制
handler = new Handler(Looper.getMainLooper());
pendingRequest = null;
binding.homeInput.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 != null) {
handler.removeCallbacks(pendingRequest);
}
if (s.length() == 0) {
binding.homeInput.setAdapter(null);
return;
}
boolean hasPermission = requireContext().checkSelfPermission(android.Manifest.permission.ACCESS_FINE_LOCATION)
== android.content.pm.PackageManager.PERMISSION_GRANTED;
if (!hasPermission) {
// 没有权限 → 不请求建议,也不显示任何提示
binding.homeInput.setAdapter(null);
return;
}
pendingRequest = () -> suggestHelper.requestSuggestions(s.toString());
handler.postDelayed(pendingRequest, TIPS_DELAY);
}
@Override
public void afterTextChanged(Editable s) {}
});
}
/**
* 设置搜索动作:回车 & 按钮点击
*/
private void setupSearchAction() {
// 回车搜索
binding.homeInput.setOnEditorActionListener((v, actionId, event) -> {
if ((actionId & EditorInfo.IME_MASK_ACTION) == EditorInfo.IME_ACTION_SEARCH) {
performSearch();
return true;
}
return false;
});
// 搜索按钮点击
binding.homeSearch.setOnClickListener(v -> performSearch());
}
private void performSearch() {
String keyword = binding.homeInput.getText().toString().trim();
if (keyword.isEmpty()) {
Toast.makeText(requireContext(), "请输入关键词", Toast.LENGTH_SHORT).show();
return;
}
MainActivity activity = (MainActivity) requireActivity();
activity.ensureFineLocationPermission(() -> {
Intent intent = new Intent(requireContext(), SearchResultActivity.class);
intent.putExtra("keyword", keyword);
startActivity(intent);
});
}
@Override
public void onDestroyView() {
super.onDestroyView();
binding = null;
// 清理防抖任务
if (handler != null) {
handler.removeCallbacksAndMessages(null);
}
}
}
MainActivity代码如下:package com.example.bus;
import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.navigation.NavController;
import androidx.navigation.Navigation;
import androidx.navigation.ui.AppBarConfiguration;
import androidx.navigation.ui.NavigationUI;
import com.example.bus.databinding.ActivityMainBinding;
import com.google.android.material.bottomnavigation.BottomNavigationView;
public class MainActivity extends AppCompatActivity {
private ActivityMainBinding binding;
private static final String SAVED_NAV_ID = "saved_nav_id";
private static final int LOCATION_PERMISSION_REQUEST_CODE = 1001;
private static final String PREF_FIRST_LAUNCH = "pref_first_launch";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
boolean isFirstLaunch = getSharedPreferences("app_config", MODE_PRIVATE)
.getBoolean(PREF_FIRST_LAUNCH, true);
if (isFirstLaunch) {
getSharedPreferences("app_config", MODE_PRIVATE)
.edit()
.putBoolean(PREF_FIRST_LAUNCH, false)
.apply();
}
BottomNavigationView navView = findViewById(R.id.nav_view);
AppBarConfiguration appBarConfiguration = new AppBarConfiguration.Builder(
R.id.navigation_home,
R.id.navigation_map,
R.id.navigation_settings)
.build();
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment_activity_main);
NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration);
NavigationUI.setupWithNavController(navView, navController);
// 设置 BottomNavigationView 的监听器
navView.setOnItemSelectedListener(item -> {
int itemId = item.getItemId();
if (itemId == R.id.navigation_map) {
// 只有点击地图 tab 才走权限 + 跳转逻辑
ensureFineLocationPermission(() -> {
if (navController.getCurrentDestination().getId() != R.id.navigation_map) {
navController.navigate(R.id.navigation_map);
}
});
} else {
navController.navigate(itemId);
}
return true;
});
if (savedInstanceState != null) {
int savedId = savedInstanceState.getInt(SAVED_NAV_ID, R.id.navigation_home);
navView.setSelectedItemId(savedId);
}
}
/**
* 统一权限请求方法,权限通过后执行 onGranted
*/
public void ensureFineLocationPermission(Runnable onGranted) {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
== PackageManager.PERMISSION_GRANTED) {
onGranted.run();
} else {
requestLocationPermission(onGranted);
}
}
private void requestLocationPermission(Runnable onGranted) {
if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.ACCESS_FINE_LOCATION)) {
new AlertDialog.Builder(this)
.setTitle("需要精确定位权限")
.setMessage("为了提供更好的服务,我们需要获取您的精确位置。\n否则将无法使用地图及搜索等相关功能。\n\n请务必选择【仅使用期间允许】或【本次使用允许】。")
.setPositiveButton("去允许", (d, w) -> startRequestPermission(onGranted))
.setNegativeButton("取消", null)
.show();
} else {
startRequestPermission(onGranted);
}
}
private void startRequestPermission(Runnable onGranted) {
// 使用成员变量保存回调
this.pendingRunnable = onGranted;
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
LOCATION_PERMISSION_REQUEST_CODE);
}
private Runnable pendingRunnable;
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == LOCATION_PERMISSION_REQUEST_CODE) {
boolean granted = grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED;
if (granted) {
Toast.makeText(this, "已获得精确定位权限", Toast.LENGTH_SHORT).show();
// 执行传进来的任务(可能是跳转 SearchResultActivity 或进入 MapFragment)
if (pendingRunnable != null) {
pendingRunnable.run();
pendingRunnable = null;
}
} else {
boolean hasCoarse = ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION)
== PackageManager.PERMISSION_GRANTED;
if (hasCoarse) {
new AlertDialog.Builder(this)
.setTitle("需要精确位置")
.setMessage("检测到您使用的是【大致位置】,这会导致地图及搜索等相关功能无法正常使用。\n\n" +
"请在设置中将定位权限修改为【精确位置】。")
.setPositiveButton("去设置", (d, w) -> openAppSettings())
.setNegativeButton("取消", null)
.show();
} else {
Toast.makeText(this, "定位权限未授予,部分功能受限", Toast.LENGTH_LONG).show();
}
pendingRunnable = null; // 清理
}
}
}
private void openAppSettings() {
Intent intent = new Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.setData(Uri.parse("package:" + getPackageName()));
startActivity(intent);
}
@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
BottomNavigationView navView = findViewById(R.id.nav_view);
outState.putInt(SAVED_NAV_ID, navView.getSelectedItemId());
}
}
难道这几个逻辑不一样吗?
最新发布