当前项目 Debug 签名信息:
- SHA1: XXXXXX
- 包名:
com.example.baidunavidemo
在百度地图开放平台申请 Android AK 时填写这两个值即可。
申请到 AK 后,在项目根目录 gradle.properties 中添加:
BAIDU_MAP_AK=你的百度地图AK
package com.example.baidunavidemo;
import android.os.Handler;
import android.os.Looper;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.widget.AutoCompleteTextView;
import android.widget.Toast;
import com.baidu.mapapi.search.sug.OnGetSuggestionResultListener;
import com.baidu.mapapi.search.sug.SuggestionResult;
import com.baidu.mapapi.search.sug.SuggestionSearch;
import com.baidu.mapapi.search.sug.SuggestionSearchOption;
import java.util.ArrayList;
import java.util.List;
final class AddressSearchController {
interface CityProvider {
String getCity();
}
interface SelectionListener {
void onSelectionChanged(SuggestionItem item);
}
private final AutoCompleteTextView input;
private final SuggestionAdapter adapter;
private final SuggestionSearch search;
private final CityProvider cityProvider;
private final SelectionListener listener;
private final Handler handler = new Handler(Looper.getMainLooper());
private Runnable pendingSearch;
private boolean internalTextChange;
private SuggestionItem selectedItem;
AddressSearchController(AutoCompleteTextView input,
CityProvider cityProvider,
SelectionListener listener) {
this.input = input;
this.cityProvider = cityProvider;
this.listener = listener;
this.adapter = new SuggestionAdapter(input.getContext());
this.input.setAdapter(adapter);
this.search = SuggestionSearch.newInstance();
this.search.setOnGetSuggestionResultListener(createResultListener());
bindInput();
}
SuggestionItem getSelectedItem() {
return selectedItem;
}
private void bindInput() {
input.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) { }
@Override
public void afterTextChanged(Editable editable) {
if (internalTextChange) {
return;
}
selectedItem = null;
listener.onSelectionChanged(null);
if (pendingSearch != null) {
handler.removeCallbacks(pendingSearch);
}
String keyword = editable.toString().trim();
if (keyword.isEmpty()) {
adapter.replaceAll(new ArrayList<>());
input.dismissDropDown();
return;
}
pendingSearch = () -> requestSuggestions(keyword);
handler.postDelayed(pendingSearch, 350L);
}
});
input.setOnItemClickListener((parent, view, position, id) -> {
SuggestionItem item = adapter.getItem(position);
if (item.point == null) {
Toast.makeText(input.getContext(), "该地址缺少坐标,请选择其他结果", Toast.LENGTH_SHORT).show();
return;
}
selectedItem = item;
internalTextChange = true;
input.setText(item.title);
input.setSelection(input.length());
internalTextChange = false;
listener.onSelectionChanged(item);
});
}
private void requestSuggestions(String keyword) {
String city = cityProvider.getCity();
if (TextUtils.isEmpty(city)) {
city = "全国";
}
search.requestSuggestion(new SuggestionSearchOption()
.keyword(keyword)
.city(city)
.citylimit(false));
}
private OnGetSuggestionResultListener createResultListener() {
return result -> {
List<SuggestionItem> suggestions = new ArrayList<>();
if (result != null && result.getAllSuggestions() != null) {
for (SuggestionResult.SuggestionInfo info : result.getAllSuggestions()) {
if (info == null || TextUtils.isEmpty(info.getKey())) {
continue;
}
String subtitle = joinAddress(info.getCity(), info.getDistrict(), info.getAddress());
suggestions.add(new SuggestionItem(info.getKey(), subtitle, info.getPt()));
}
}
adapter.replaceAll(suggestions);
if (!suggestions.isEmpty() && input.hasFocus()) {
input.showDropDown();
}
};
}
private String joinAddress(String city, String district, String address) {
StringBuilder value = new StringBuilder();
appendPart(value, city);
appendPart(value, district);
appendPart(value, address);
return value.toString();
}
private void appendPart(StringBuilder builder, String value) {
if (TextUtils.isEmpty(value)) {
return;
}
if (builder.length() > 0 && builder.indexOf(value) < 0) {
builder.append(" · ");
}
if (builder.indexOf(value) < 0) {
builder.append(value);
}
}
void destroy() {
if (pendingSearch != null) {
handler.removeCallbacks(pendingSearch);
}
search.destroy();
}
}
package com.example.baidunavidemo;
import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.text.TextUtils;
import android.view.inputmethod.InputMethodManager;
import android.content.Context;
import android.widget.AutoCompleteTextView;
import android.widget.TextView;
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 com.baidu.navisdk.adapter.BNRoutePlanNode;
import com.baidu.navisdk.adapter.BaiduNaviManagerFactory;
import com.baidu.navisdk.adapter.IBNRoutePlanManager;
import com.google.android.material.button.MaterialButton;
import com.google.android.material.textfield.TextInputEditText;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private static final int REQUEST_NAV_PERMISSIONS = 1001;
private TextInputEditText cityInput;
private TextView statusText;
private MaterialButton startButton;
private AddressSearchController startController;
private AddressSearchController endController;
private boolean pendingNavigation;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
cityInput = findViewById(R.id.cityInput);
statusText = findViewById(R.id.statusText);
startButton = findViewById(R.id.startNavigationButton);
startButton.setOnClickListener(v -> startNavigationWithPermissionCheck());
showPrivacyDialog();
}
private void showPrivacyDialog() {
new AlertDialog.Builder(this)
.setTitle(R.string.privacy_title)
.setMessage(R.string.privacy_message)
.setCancelable(false)
.setNegativeButton(R.string.decline, (dialog, which) -> finish())
.setPositiveButton(R.string.agree, (dialog, which) -> initializeSdk())
.show();
}
private void initializeSdk() {
statusText.setText("正在初始化百度地图与导航 SDK...");
NaviApplication application = (NaviApplication) getApplication();
application.agreePrivacyAndInitialize((success, message) -> runOnUiThread(() -> {
statusText.setText(message);
if (application.isMapReady() && startController == null) {
bindAddressSearch();
requestNavigationPermissions();
}
updateStartButton();
}));
}
private void bindAddressSearch() {
AutoCompleteTextView startInput = findViewById(R.id.startInput);
AutoCompleteTextView endInput = findViewById(R.id.endInput);
AddressSearchController.CityProvider cityProvider = () -> {
String value = cityInput.getText() == null ? "" : cityInput.getText().toString().trim();
return TextUtils.isEmpty(value) ? "全国" : value;
};
startController = new AddressSearchController(startInput, cityProvider, item -> updateStartButton());
endController = new AddressSearchController(endInput, cityProvider, item -> updateStartButton());
}
private void updateStartButton() {
NaviApplication application = (NaviApplication) getApplication();
boolean hasRoute = startController != null
&& endController != null
&& startController.getSelectedItem() != null
&& endController.getSelectedItem() != null;
startButton.setEnabled(application.isNaviReady() && hasRoute);
}
private void startNavigationWithPermissionCheck() {
if (!hasRequiredLocationPermissions()) {
pendingNavigation = true;
requestNavigationPermissions();
return;
}
planRouteAndOpenGuide();
}
private void planRouteAndOpenGuide() {
NaviApplication application = (NaviApplication) getApplication();
if (!application.isNaviReady()) {
Toast.makeText(this, "导航 SDK 尚未初始化完成", Toast.LENGTH_SHORT).show();
return;
}
SuggestionItem start = startController == null ? null : startController.getSelectedItem();
SuggestionItem end = endController == null ? null : endController.getSelectedItem();
if (start == null || end == null || start.point == null || end.point == null) {
Toast.makeText(this, "请从联想结果中分别选择起点和终点", Toast.LENGTH_SHORT).show();
return;
}
hideKeyboard();
statusText.setText("正在规划驾车路线...");
startButton.setEnabled(false);
BNRoutePlanNode startNode = new BNRoutePlanNode.Builder()
.latitude(start.point.latitude)
.longitude(start.point.longitude)
.name(start.title)
.description(start.subtitle)
.build();
BNRoutePlanNode endNode = new BNRoutePlanNode.Builder()
.latitude(end.point.latitude)
.longitude(end.point.longitude)
.name(end.title)
.description(end.subtitle)
.build();
List<BNRoutePlanNode> nodes = new ArrayList<>();
nodes.add(startNode);
nodes.add(endNode);
BaiduNaviManagerFactory.getRoutePlanManager().routePlan(
nodes,
IBNRoutePlanManager.RoutePlanPreference.ROUTE_PLAN_PREFERENCE_DEFAULT,
null,
routePlanHandler);
}
private final Handler routePlanHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(@NonNull Message message) {
switch (message.what) {
case IBNRoutePlanManager.MSG_NAVI_ROUTE_PLAN_START:
statusText.setText("正在计算路线,请稍候...");
break;
case IBNRoutePlanManager.MSG_NAVI_ROUTE_PLAN_SUCCESS:
statusText.setText("路线规划成功,准备进入导航...");
break;
case IBNRoutePlanManager.MSG_NAVI_ROUTE_PLAN_TO_NAVI:
statusText.setText("导航中");
startActivity(new Intent(MainActivity.this, NaviGuideActivity.class));
updateStartButton();
break;
case IBNRoutePlanManager.MSG_NAVI_ROUTE_PLAN_FAILED:
statusText.setText("路线规划失败,请检查网络、AK 和起终点");
Toast.makeText(MainActivity.this, "路线规划失败", Toast.LENGTH_LONG).show();
updateStartButton();
break;
default:
break;
}
}
};
private void requestNavigationPermissions() {
List<String> permissions = new ArrayList<>();
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
!= PackageManager.PERMISSION_GRANTED) {
permissions.add(Manifest.permission.ACCESS_FINE_LOCATION);
}
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION)
!= PackageManager.PERMISSION_GRANTED) {
permissions.add(Manifest.permission.ACCESS_COARSE_LOCATION);
}
if (Build.VERSION.SDK_INT >= 33
&& ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS)
!= PackageManager.PERMISSION_GRANTED) {
permissions.add(Manifest.permission.POST_NOTIFICATIONS);
}
if (!permissions.isEmpty()) {
ActivityCompat.requestPermissions(this,
permissions.toArray(new String[0]),
REQUEST_NAV_PERMISSIONS);
}
}
private boolean hasRequiredLocationPermissions() {
return ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
== PackageManager.PERMISSION_GRANTED;
}
@Override
public void onRequestPermissionsResult(int requestCode,
@NonNull String[] permissions,
@NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode != REQUEST_NAV_PERMISSIONS) {
return;
}
if (hasRequiredLocationPermissions()) {
if (pendingNavigation) {
pendingNavigation = false;
planRouteAndOpenGuide();
}
} else {
pendingNavigation = false;
Toast.makeText(this, "真实导航需要精确定位权限", Toast.LENGTH_LONG).show();
}
}
private void hideKeyboard() {
InputMethodManager manager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
if (manager != null && getCurrentFocus() != null) {
manager.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0);
}
}
@Override
protected void onDestroy() {
if (startController != null) {
startController.destroy();
}
if (endController != null) {
endController.destroy();
}
super.onDestroy();
}
}
package com.example.baidunavidemo;
import android.app.Application;
import android.speech.tts.TextToSpeech;
import android.speech.tts.UtteranceProgressListener;
import com.baidu.mapapi.CoordType;
import com.baidu.mapapi.SDKInitializer;
import com.baidu.navisdk.adapter.BaiduNaviManagerFactory;
import com.baidu.navisdk.adapter.IBNTTSManager;
import com.baidu.navisdk.adapter.IBaiduNaviManager;
import com.baidu.navisdk.adapter.struct.BNaviInitConfig;
import com.baidu.navisdk.comapi.tts.IBNTTSPlayerListener;
import com.baidu.navisdk.comapi.tts.c;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
/**
* 统一管理百度 SDK 的隐私合规初始化和导航状态。
* 注意:任何百度 SDK API 都只能在用户明确同意隐私政策后调用。
*/
public class NaviApplication extends Application {
public interface InitCallback {
void onResult(boolean success, String message);
}
private final List<InitCallback> pendingCallbacks = new ArrayList<>();
private boolean initializing;
private boolean mapReady;
private boolean naviReady;
private TextToSpeech textToSpeech;
private volatile boolean ttsReady;
private volatile int ttsState = IBNTTSPlayerListener.PLAYER_STATE_NOT_INIT;
private volatile String currentSpeech = "";
private volatile String stoppedSpeechId = "";
public synchronized boolean isMapReady() {
return mapReady;
}
public synchronized boolean isNaviReady() {
return naviReady;
}
public synchronized void agreePrivacyAndInitialize(InitCallback callback) {
if (naviReady) {
callback.onResult(true, "导航 SDK 已就绪");
return;
}
pendingCallbacks.add(callback);
if (initializing) {
return;
}
initializing = true;
try {
SDKInitializer.setAgreePrivacy(this, true);
SDKInitializer.initialize(this);
SDKInitializer.setCoordType(CoordType.BD09LL);
mapReady = true;
} catch (Throwable error) {
finishInitialization(false, "地图 SDK 初始化失败:" + safeMessage(error));
return;
}
File externalDir = getExternalFilesDir(null);
String rootPath = externalDir != null
? externalDir.getAbsolutePath()
: getFilesDir().getAbsolutePath();
BNaviInitConfig config = new BNaviInitConfig.Builder()
.sdcardRootPath(rootPath)
.naviInitListener(new IBaiduNaviManager.INaviInitListener() {
private boolean authFailed;
@Override
public void onAuthResult(int status, String message) {
if (status != 0) {
authFailed = true;
finishInitialization(false, "AK 鉴权失败(" + status + "):" + message);
}
}
@Override
public void initStart() {
// 初始化进度由首页状态文字展示。
}
@Override
public void initSuccess() {
if (authFailed) {
return;
}
naviReady = true;
initializeAndroidTts();
finishInitialization(true, "导航 SDK 已就绪,请选择起点和终点");
}
@Override
public void initFailed(int errorCode) {
finishInitialization(false, "导航 SDK 初始化失败,错误码:" + errorCode);
}
})
.build();
try {
BaiduNaviManagerFactory.getBaiduNaviManager().init(this, config);
} catch (Throwable error) {
finishInitialization(false, "导航 SDK 初始化异常:" + safeMessage(error));
}
}
private synchronized void finishInitialization(boolean success, String message) {
if (success) {
naviReady = true;
}
initializing = false;
List<InitCallback> callbacks = new ArrayList<>(pendingCallbacks);
pendingCallbacks.clear();
for (InitCallback callback : callbacks) {
callback.onResult(success, message);
}
}
/** 使用系统 TextToSpeech 作为百度导航的外置 TTS,避免额外申请语音 AppId。 */
private void initializeAndroidTts() {
textToSpeech = new TextToSpeech(this, status -> {
ttsReady = status == TextToSpeech.SUCCESS;
ttsState = ttsReady
? IBNTTSPlayerListener.PLAYER_STATE_IDLE
: IBNTTSPlayerListener.PLAYER_STATE_ERROR;
if (ttsReady) {
textToSpeech.setLanguage(Locale.SIMPLIFIED_CHINESE);
textToSpeech.setSpeechRate(1.0f);
textToSpeech.setOnUtteranceProgressListener(new UtteranceProgressListener() {
@Override
public void onStart(String utteranceId) {
ttsState = IBNTTSPlayerListener.PLAYER_STATE_PLAYING;
}
@Override
public void onDone(String utteranceId) {
ttsState = IBNTTSPlayerListener.PLAYER_STATE_IDLE;
stoppedSpeechId = utteranceId == null ? "" : utteranceId;
currentSpeech = "";
}
@Override
public void onError(String utteranceId) {
ttsState = IBNTTSPlayerListener.PLAYER_STATE_ERROR;
stoppedSpeechId = utteranceId == null ? "" : utteranceId;
}
});
}
});
BaiduNaviManagerFactory.getTTSManager().initTTS(
new IBNTTSManager.IBNOuterTTSPlayerCallback() {
@Override
public int playTTSText(String speech,
String extra,
int preempt,
String speechId) {
if (!ttsReady || textToSpeech == null) {
return 0;
}
currentSpeech = speech == null ? "" : speech;
int queueMode = preempt == 1
? TextToSpeech.QUEUE_FLUSH
: TextToSpeech.QUEUE_ADD;
int result = textToSpeech.speak(speech, queueMode, null, speechId);
return result == TextToSpeech.SUCCESS ? 1 : 0;
}
@Override
public int getTTSState() {
return ttsState;
}
@Override
public String getCurTTSSpeech() {
return currentSpeech;
}
@Override
public String getStopTTSSpeechId() {
return stoppedSpeechId;
}
@Override
public int playXDTTSText(boolean preempt, c ttsData) {
return super.playXDTTSText(ttsData);
}
});
}
private String safeMessage(Throwable error) {
return error.getMessage() == null ? error.getClass().getSimpleName() : error.getMessage();
}
@Override
public void onTerminate() {
if (textToSpeech != null) {
textToSpeech.stop();
textToSpeech.shutdown();
}
super.onTerminate();
}
}
package com.example.baidunavidemo;
import android.os.Bundle;
import android.content.res.Configuration;
import android.view.View;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import com.baidu.navisdk.adapter.BNaviCommonParams;
import com.baidu.navisdk.adapter.BaiduNaviManagerFactory;
import com.baidu.navisdk.adapter.struct.BNGuideConfig;
/** 将百度专业导航 View 直接嵌入当前 App,而不是跳转百度地图客户端。 */
public class NaviGuideActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle params = new Bundle();
params.putBoolean(BNaviCommonParams.ProGuideKey.IS_REALNAVI, true);
params.putBoolean(BNaviCommonParams.ProGuideKey.IS_SUPPORT_FULL_SCREEN, true);
BNGuideConfig config = new BNGuideConfig.Builder()
.params(params)
.build();
View guideView = BaiduNaviManagerFactory.getRouteGuideManager().onCreate(this, config);
setContentView(guideView);
}
@Override
protected void onStart() {
super.onStart();
BaiduNaviManagerFactory.getRouteGuideManager().onStart();
}
@Override
protected void onResume() {
super.onResume();
BaiduNaviManagerFactory.getRouteGuideManager().onResume();
}
@Override
protected void onPause() {
BaiduNaviManagerFactory.getRouteGuideManager().onPause();
super.onPause();
}
@Override
protected void onStop() {
BaiduNaviManagerFactory.getRouteGuideManager().onStop();
super.onStop();
}
@Override
protected void onDestroy() {
BaiduNaviManagerFactory.getRouteGuideManager().onDestroy(false);
super.onDestroy();
}
@Override
public void onBackPressed() {
BaiduNaviManagerFactory.getRouteGuideManager().onBackPressed(false);
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
BaiduNaviManagerFactory.getRouteGuideManager().onConfigurationChanged(newConfig);
}
}
package com.example.baidunavidemo;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.Filter;
import android.widget.Filterable;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;
final class SuggestionAdapter extends BaseAdapter implements Filterable {
private final LayoutInflater inflater;
private final List<SuggestionItem> items = new ArrayList<>();
SuggestionAdapter(Context context) {
inflater = LayoutInflater.from(context);
}
void replaceAll(List<SuggestionItem> newItems) {
items.clear();
items.addAll(newItems);
notifyDataSetChanged();
}
@Override
public int getCount() {
return items.size();
}
@Override
public SuggestionItem getItem(int position) {
return items.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
convertView = inflater.inflate(R.layout.item_suggestion, parent, false);
holder = new ViewHolder();
holder.title = convertView.findViewById(R.id.suggestionTitle);
holder.subtitle = convertView.findViewById(R.id.suggestionSubtitle);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
SuggestionItem item = getItem(position);
holder.title.setText(item.title);
holder.subtitle.setText(item.subtitle);
holder.subtitle.setVisibility(item.subtitle.isEmpty() ? View.GONE : View.VISIBLE);
return convertView;
}
/** 结果已由百度 SuggestionSearch 过滤,此处禁止 AutoCompleteTextView 二次本地过滤。 */
@Override
public Filter getFilter() {
return new Filter() {
@Override
protected FilterResults performFiltering(CharSequence constraint) {
FilterResults results = new FilterResults();
results.values = items;
results.count = items.size();
return results;
}
@Override
protected void publishResults(CharSequence constraint, FilterResults results) {
notifyDataSetChanged();
}
};
}
private static class ViewHolder {
TextView title;
TextView subtitle;
}
}
package com.example.baidunavidemo;
import com.baidu.mapapi.model.LatLng;
final class SuggestionItem {
final String title;
final String subtitle;
final LatLng point;
SuggestionItem(String title, String subtitle, LatLng point) {
this.title = title;
this.subtitle = subtitle;
this.point = point;
}
String displayText() {
return title + (subtitle.isEmpty() ? "" : " · " + subtitle);
}
@Override
public String toString() {
return title;
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/surface">
<View
android:id="@+id/headerBackground"
android:layout_width="0dp"
android:layout_height="240dp"
android:background="@drawable/bg_header"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/badge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="24dp"
android:background="@drawable/bg_badge"
android:text="BAIDU NAVI · JAVA"
android:textColor="@color/cyan_300"
android:textSize="11sp"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="18dp"
android:text="@string/title"
android:textColor="@color/white"
android:textSize="30sp"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/badge" />
<TextView
android:id="@+id/subtitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="7dp"
android:layout_marginEnd="24dp"
android:text="@string/subtitle"
android:textColor="#BFD1E8"
android:textSize="14sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/title" />
<androidx.cardview.widget.CardView
android:id="@+id/searchCard"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="18dp"
android:layout_marginTop="18dp"
android:layout_marginEnd="18dp"
app:cardCornerRadius="20dp"
app:cardElevation="8dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/subtitle">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="18dp">
<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/city_hint"
app:boxCornerRadiusBottomEnd="12dp"
app:boxCornerRadiusBottomStart="12dp"
app:boxCornerRadiusTopEnd="12dp"
app:boxCornerRadiusTopStart="12dp">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/cityInput"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="text"
android:maxLines="1"
android:text="全国" />
</com.google.android.material.textfield.TextInputLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<View
android:layout_width="12dp"
android:layout_height="12dp"
android:layout_marginEnd="12dp"
android:background="@drawable/bg_dot_start" />
<AutoCompleteTextView
android:id="@+id/startInput"
android:layout_width="0dp"
android:layout_height="52dp"
android:layout_weight="1"
android:background="@android:color/transparent"
android:completionThreshold="1"
android:dropDownHeight="wrap_content"
android:hint="@string/start_hint"
android:imeOptions="actionNext"
android:inputType="text"
android:maxLines="1"
android:paddingHorizontal="4dp"
android:textColor="@color/text_primary"
android:textColorHint="@color/text_secondary"
android:textSize="16sp" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginStart="24dp"
android:background="@color/divider" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal">
<View
android:layout_width="12dp"
android:layout_height="12dp"
android:layout_marginEnd="12dp"
android:background="@drawable/bg_dot_end" />
<AutoCompleteTextView
android:id="@+id/endInput"
android:layout_width="0dp"
android:layout_height="52dp"
android:layout_weight="1"
android:background="@android:color/transparent"
android:completionThreshold="1"
android:dropDownHeight="wrap_content"
android:hint="@string/end_hint"
android:imeOptions="actionDone"
android:inputType="text"
android:maxLines="1"
android:paddingHorizontal="4dp"
android:textColor="@color/text_primary"
android:textColorHint="@color/text_secondary"
android:textSize="16sp" />
</LinearLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>
<TextView
android:id="@+id/statusText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="28dp"
android:layout_marginTop="20dp"
android:layout_marginEnd="28dp"
android:gravity="center"
android:text="等待同意隐私政策"
android:textColor="@color/text_secondary"
android:textSize="13sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/searchCard" />
<com.google.android.material.button.MaterialButton
android:id="@+id/startNavigationButton"
android:layout_width="0dp"
android:layout_height="58dp"
android:layout_marginStart="24dp"
android:layout_marginEnd="24dp"
android:layout_marginBottom="28dp"
android:enabled="false"
android:text="@string/start_nav"
android:textAllCaps="false"
android:textSize="17sp"
android:textStyle="bold"
app:backgroundTint="@color/blue_600"
app:cornerRadius="16dp"
app:icon="@drawable/ic_arrow"
app:iconGravity="textEnd"
app:iconPadding="12dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
item_suggestion.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/white"
android:minHeight="64dp"
android:orientation="vertical"
android:paddingStart="16dp"
android:paddingTop="10dp"
android:paddingEnd="16dp"
android:paddingBottom="10dp">
<TextView
android:id="@+id/suggestionTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:textColor="@color/text_primary"
android:textSize="15sp"
android:textStyle="bold" />
<TextView
android:id="@+id/suggestionSubtitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="3dp"
android:ellipsize="end"
android:maxLines="1"
android:textColor="@color/text_secondary"
android:textSize="12sp" />
</LinearLayout>


