Google Places for Android 入门指南
原文出处:http://itangqi.me/android/google-places-for-android-guide/
前言
最近由于项目的原因,接触到了 Google Places 的使用与开发,学习并在项目中实现了简单的地点定位与自动补全功能。
在整个实践过程中,发现除了官方文档外,网上可以参考的中文教程寥寥无几且质量都不高,遂决定自己写篇入门指南(其实也没多少干货啦),希望可以帮助到,有这方面需求的同学们。下面,我将以一个实际的例子,图文并茂的带着你快速了解并使用上 Google Places .
在 Google Developers Console 中创建 API 项目
如果你之前从未在应用中使用过 Google 提供的相关服务(API),那么请直接使用系统引导帮你完成整个流程并自动激活 Google Places API for Android ,请点击此链接,按下面截图所示进行操作,便可轻松完成创建。
-
选择「Create a new project」
-
选择「Go to Credentials」
-
此处直接选择「Create」,既默认所有应用均可使用同样的「API key」
-
好啦,此时主角「API key」便自动生成了
-
如果你还需要新增(使用)其他服务(API),请在「APIs」里进行查看与添加
配置你的应用
使用 Google Places API for Android 的所有应用均需执行以下配置步骤。
添加 GOOGLE PLAY 服务
要访问 Google Places API for Android,应用的开发项目必须包含 Google Play 服务。通过 SDK 管理器下载并安装 Google Play 服务组件,然后将库添加至您的项目。
-buidl.gradle (app)
dependencies {
compile 'com.google.android.gms:play-services:7.5.0'
}
添加相应权限
-AndroidManifest.xml
<!-- PlacePicker requires the ACCESS_FINE_LOCATION permission and a geo API key.-->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<!-- PlacePicker also requires OpenGL ES version 2 -->
<uses-feature
android:glEsVersion="0x00020000"
android:required="true" />
添加 API 密钥
请按照以下代码示例所示,将你的 API 密钥添加至 Manifest ,并将 API_KEY 替换为您自己的 API 密钥:
-AndroidManifest.xml
<application>
...
<meta-data
android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version" />
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="API_KEY"/>
</application>
当前地点
获取当前位置
要查找本地商家或设备的最后已知所在地点,请调用 PlaceDetectionApi.getCurrentPlace()。
您可以选择指定一个 PlaceFilter,以将结果限制为一个或多个地点 ID(最多 10 个),或者仅选择当前打开的地点。如果未指定筛选器,则不会筛选结果。
API 会在 PendingResult 中返回 PlaceLikelihoodBuffer。PlaceLikelihoodBuffer 包含表示类似地点的 PlaceLikelihood 对象列表。对于每个地点,结果中都包含指示地点是正确地点的可能性信息。如果没有与筛选条件对应的已知地点,缓冲区可能为空。
您可以调用 PlaceLikelihood.getPlace() 来检索 Place 对象,调用PlaceLikelihood.getLikelihood() 来获取地点的可能性评分。值越高,表示该地点是最佳匹配项的可能性越大。
地点自动完成
要获取预测地点名称和/或地址的列表,请调用 GeoDataApi.getAutocompletePredictions(),传递以下参数:
-
必填:query 字符串包含用户键入的文本。
-
必填:LatLngBounds 对象,将结果限制为通过纬度和经度边界指定的特定区域。
-
可选:AutocompleteFilter,包含一组地点类型,您可以使用它们将结果限制为一种或多种地点类型,如商店、学校、邮局等。有关可用地点类型的列表,请参见支持的类型列表。
API 会在 PendingResult 中返回AutocompletePredictionBuffer。AutocompletePredictionBuffer 包含表示预测地点的AutocompletePrediction 对象列表。如果没有与查询和筛选条件对应的已知地点,缓冲区可能为空。
对于每个预测地点,都可以调用以下方法来检索地点详情:
-
getDescription() – 返回地点说明。
-
getMatchedSubstrings() – 通过与此地点匹配的 query 返回子字符串列表。例如,您可以使用这些子字符串突出显示用户查询中的匹配文本。
-
getDescription() – 返回预测地点的地点 ID。地点 ID 是唯一标识地点的文本标识符。有关地点 ID 的详细信息,请参阅地点 ID 概览。
-
`getPlaceTypes() – 返回与此地点关联的地点类型列表。
具体实现
-activity_main.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="match_parent"
android:layout_margin="@dimen/margin_16dp"
android:orientation="vertical">
<AutoCompleteTextView
android:id="@+id/autocomplete_places"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:hint="@string/autocomplete_hint" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingTop="@dimen/margin_8dp">
<Button
android:id="@+id/ll_current_location"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:text="@string/get_current_place"
android:textAppearance="?android:attr/textAppearanceSmall" />
</LinearLayout>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right"
android:src="@drawable/powered_by_google_light" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/selected_place"
android:textAppearance="?android:attr/textAppearanceMedium" />
<TextView
android:id="@+id/place_details"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:autoLink="all"
android:paddingTop="@dimen/margin_8dp"
android:text=""
android:textAppearance="?android:attr/textAppearanceMedium" />
<TextView
android:id="@+id/place_attribution"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:autoLink="all"
android:paddingTop="@dimen/margin_16dp"
android:text=""
android:textAppearance="?android:attr/textAppearanceSmall" />
</LinearLayout>
-PlaceAutocompleteAdapter.java
public class PlaceAutocompleteAdapter
extends ArrayAdapter<PlaceAutocompleteAdapter.PlaceAutocomplete> implements Filterable {
private static final String TAG = "PlaceAutocompleteAdapter";
/**
* Current results returned by this adapter.
*/
private ArrayList<PlaceAutocomplete> mResultList;
/**
* Handles autocomplete requests.
*/
private GoogleApiClient mGoogleApiClient;
/**
* The bounds used for Places Geo Data autocomplete API requests.
*/
private LatLngBounds mBounds;
/**
* The autocomplete filter used to restrict queries to a specific set of place types.
*/
private AutocompleteFilter mPlaceFilter;
/**
* Initializes with a resource for text rows and autocomplete query bounds.
*
* @see ArrayAdapter#ArrayAdapter(Context, int)
*/
public PlaceAutocompleteAdapter(Context context, int resource, GoogleApiClient googleApiClient,
LatLngBounds bounds, AutocompleteFilter filter) {
super(context, resource);
mGoogleApiClient = googleApiClient;
mBounds = bounds;
mPlaceFilter = filter;
}
/**
* Sets the bounds for all subsequent queries.
*/
public void setBounds(LatLngBounds bounds) {
mBounds = bounds;
}
/**
* Returns the number of results received in the last autocomplete query.
*/
@Override
public int getCount() {
return mResultList.size();
}
/**
* Returns an item from the last autocomplete query.
*/
@Override
public PlaceAutocomplete getItem(int position) {
return mResultList.get(position);
}
/**
* Returns the filter for the current set of autocomplete results.
*/
@Override
public Filter getFilter() {
Filter filter = new Filter() {
@Override
protected FilterResults performFiltering(CharSequence constraint) {
FilterResults results = new FilterResults();
// Skip the autocomplete query if no constraints are given.
if (constraint != null) {
// Query the autocomplete API for the (constraint) search string.
mResultList = getAutocomplete(constraint);
if (mResultList != null) {
// The API successfully returned results.
results.values = mResultList;
results.count = mResultList.size();
}
}
return results;
}
@Override
protected void publishResults(CharSequence constraint, FilterResults results) {
if (results != null && results.count > 0) {
// The API returned at least one result, update the data.
notifyDataSetChanged();
} else {
// The API did not return any results, invalidate the data set.
notifyDataSetInvalidated();
}
}
};
return filter;
}
private ArrayList<PlaceAutocomplete> getAutocomplete(CharSequence constraint) {
if (mGoogleApiClient.isConnected()) {
Log.i(TAG, "Starting autocomplete query for: " + constraint);
// Submit the query to the autocomplete API and retrieve a PendingResult that will
// contain the results when the query completes.
PendingResult<AutocompletePredictionBuffer> results =
Places.GeoDataApi
.getAutocompletePredictions(mGoogleApiClient, constraint.toString(),
mBounds, mPlaceFilter);
// This method should have been called off the main UI thread. Block and wait for at most 60s
// for a result from the API.
AutocompletePredictionBuffer autocompletePredictions = results
.await(60, TimeUnit.SECONDS);
// Confirm that the query completed successfully, otherwise return null
final Status status = autocompletePredictions.getStatus();
if (!status.isSuccess()) {
Toast.makeText(getContext(), "Error contacting API: " + status.toString(),
Toast.LENGTH_SHORT).show();
Log.e(TAG, "Error getting autocomplete prediction API call: " + status.toString());
autocompletePredictions.release();
return null;
}
Log.i(TAG, "Query completed. Received " + autocompletePredictions.getCount()
+ " predictions.");
// Copy the results into our own data structure, because we can't hold onto the buffer.
// AutocompletePrediction objects encapsulate the API response (place ID and description).
Iterator<AutocompletePrediction> iterator = autocompletePredictions.iterator();
ArrayList resultList = new ArrayList<>(autocompletePredictions.getCount());
while (iterator.hasNext()) {
AutocompletePrediction prediction = iterator.next();
// Get the details of this prediction and copy it into a new PlaceAutocomplete object.
resultList.add(new PlaceAutocomplete(prediction.getPlaceId(),
prediction.getDescription()));
}
// Release the buffer now that all data has been copied.
autocompletePredictions.release();
return resultList;
}
Log.e(TAG, "Google API client is not connected for autocomplete query.");
return null;
}
/**
* Holder for Places Geo Data Autocomplete API results.
*/
class PlaceAutocomplete {
public CharSequence placeId;
public CharSequence description;
PlaceAutocomplete(CharSequence placeId, CharSequence description) {
this.placeId = placeId;
this.description = description;
}
@Override
public String toString() {
return description.toString();
}
}
}
-MainActivity.java
public class MainActivity extends AppCompatActivity
implements GoogleApiClient.OnConnectionFailedListener {
/**
* GoogleApiClient wraps our service connection to Google Play Services and provides access
* to the user's sign in state as well as the Google's APIs.
*/
private static final int GOOGLE_API_CLIENT_ID = 0;
protected GoogleApiClient mGoogleApiClient;
private PlaceAutocompleteAdapter mAdapter;
private AutoCompleteTextView mAutocompleteView;
private TextView mPlaceDetailsText;
private TextView mPlaceDetailsAttribution;
private Button mCurrentLocation;
private static final String TAG = "MainActivity";
private static final LatLngBounds BOUNDS_GREATER_SYDNEY = new LatLngBounds(
new LatLng(-34.041458, 150.790100), new LatLng(-33.682247, 151.383362));
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Construct a GoogleApiClient for the {@link Places#GEO_DATA_API} using AutoManage
// functionality, which automatically sets up the API client to handle Activity lifecycle
// events. If your activity does not extend FragmentActivity, make sure to call connect()
// and disconnect() explicitly.
mGoogleApiClient = new GoogleApiClient.Builder(this)
.enableAutoManage(this, GOOGLE_API_CLIENT_ID /* clientId */, this)
.addApi(Places.GEO_DATA_API)
.addApi(Places.PLACE_DETECTION_API)
.build();
setContentView(R.layout.activity_main);
// Retrieve the AutoCompleteTextView that will display Place suggestions.
mAutocompleteView = (AutoCompleteTextView)
findViewById(R.id.autocomplete_places);
// Register a listener that receives callbacks when a suggestion has been selected
mAutocompleteView.setOnItemClickListener(mAutocompleteClickListener);
// Retrieve the TextViews that will display details and attributions of the selected place.
mPlaceDetailsText = (TextView) findViewById(R.id.place_details);
mPlaceDetailsAttribution = (TextView) findViewById(R.id.place_attribution);
// CurrentLocation
mCurrentLocation = (Button) findViewById(R.id.ll_current_location);
mCurrentLocation.setOnClickListener(mOnClickListener);
// Set up the adapter that will retrieve suggestions from the Places Geo Data API that cover
// the entire world.
mAdapter = new PlaceAutocompleteAdapter(this, android.R.layout.simple_list_item_1,
mGoogleApiClient, BOUNDS_GREATER_SYDNEY, null);
mAutocompleteView.setAdapter(mAdapter);
}
private View.OnClickListener mOnClickListener = new View.OnClickListener() {
@Override
public void onClick(View view) {
PendingResult<PlaceLikelihoodBuffer> result = Places.PlaceDetectionApi
.getCurrentPlace(mGoogleApiClient, null);
result.setResultCallback(new ResultCallback<PlaceLikelihoodBuffer>() {
@Override
public void onResult(PlaceLikelihoodBuffer likelyPlaces) {
if (!likelyPlaces.getStatus().isSuccess()) {
// Request did not complete successfully
Log.e(TAG, "Place query did not complete. Error: " + likelyPlaces.getStatus().toString());
likelyPlaces.release();
return;
}
String placeName = String.format("%s", likelyPlaces.get(0).getPlace().getName());
String placeAttributuion = String.format("%s", likelyPlaces.get(0).getPlace().getAddress());
mPlaceDetailsText.setText(placeName);
mPlaceDetailsAttribution.setText(placeAttributuion);
likelyPlaces.release();
}
});
}
};
/**
* Listener that handles selections from suggestions from the AutoCompleteTextView that
* displays Place suggestions.
* Gets the place id of the selected item and issues a request to the Places Geo Data API
* to retrieve more details about the place.
*
* @see com.google.android.gms.location.places.GeoDataApi#getPlaceById(com.google.android.gms.common.api.GoogleApiClient,
* String...)
*/
private AdapterView.OnItemClickListener mAutocompleteClickListener
= new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
/*
Retrieve the place ID of the selected item from the Adapter.
The adapter stores each Place suggestion in a PlaceAutocomplete object from which we
read the place ID.
*/
final PlaceAutocompleteAdapter.PlaceAutocomplete item = mAdapter.getItem(position);
final String placeId = String.valueOf(item.placeId);
Log.i(TAG, "Autocomplete item selected: " + item.description);
/*
Issue a request to the Places Geo Data API to retrieve a Place object with additional
details about the place.
*/
PendingResult<PlaceBuffer> placeResult = Places.GeoDataApi
.getPlaceById(mGoogleApiClient, placeId);
placeResult.setResultCallback(mUpdatePlaceDetailsCallback);
Log.i(TAG, "Called getPlaceById to get Place details for " + item.placeId);
}
};
/**
* Callback for results from a Places Geo Data API query that shows the first place result in
* the details view on screen.
*/
private ResultCallback<PlaceBuffer> mUpdatePlaceDetailsCallback
= new ResultCallback<PlaceBuffer>() {
@Override
public void onResult(PlaceBuffer places) {
if (!places.getStatus().isSuccess()) {
// Request did not complete successfully
Log.e(TAG, "Place query did not complete. Error: " + places.getStatus().toString());
places.release();
return;
}
// Get the Place object from the buffer.
final Place place = places.get(0);
// Format details of the place for display and show it in a TextView.
mPlaceDetailsText.setText(formatPlaceDetails(getResources(), place.getName(),
place.getId(), place.getAddress(), place.getPhoneNumber(),
place.getWebsiteUri()));
// Display the third party attributions if set.
final CharSequence thirdPartyAttribution = places.getAttributions();
if (thirdPartyAttribution == null) {
mPlaceDetailsAttribution.setVisibility(View.GONE);
} else {
mPlaceDetailsAttribution.setVisibility(View.VISIBLE);
mPlaceDetailsAttribution.setText(Html.fromHtml(thirdPartyAttribution.toString()));
}
Log.i(TAG, "Place details received: " + place.getName());
places.release();
}
};
private static Spanned formatPlaceDetails(Resources res, CharSequence name, String id,
CharSequence address, CharSequence phoneNumber, Uri websiteUri) {
Log.e(TAG, res.getString(R.string.place_details, name, id, address, phoneNumber,
websiteUri));
return Html.fromHtml(res.getString(R.string.place_details, name, id, address, phoneNumber,
websiteUri));
}
/**
* Called when the Activity could not connect to Google Play services and the auto manager
* could resolve the error automatically.
* In this case the API is not available and notify the user.
*
* @param connectionResult can be inspected to determine the cause of the failure
*/
@Override
public void onConnectionFailed(ConnectionResult connectionResult) {
Log.e(TAG, "onConnectionFailed: ConnectionResult.getErrorCode() = "
+ connectionResult.getErrorCode());
// TODO(Developer): Check error code and notify the user of error state and resolution.
Toast.makeText(this,
"Could not connect to Google API Client: Error " + connectionResult.getErrorCode(),
Toast.LENGTH_SHORT).show();
MainActivity.this.finish();
}
}
最后
-
友情提醒:以上的功能,手机需要安装 Google Play services 才能正常使用,至于如何安装,就交给万能的 Google 吧:)
-
如本文存在任何错误或遗漏,欢迎指正。
-
我最爱交朋友了,所以欢迎在社交网络上互粉!!!