在打车、运动、导航类应用中,基于当前位置信息在地图上进行展示,并根据位置信息的变化,绘制运动轨迹的场景,现在非常流行。直观的运动轨迹展示,可以给用户带来更好的用户体验。
通过本篇Codelab,我们将基于华为定位服务(Location Kit)、华为地图服务(Map Kit),以用户跑步运动的应用场景为例,详细的为开发者讲解实现运动轨迹这个业务场景的开发过程。
在这个Codelab中,您将实现通过华为定位服务、地图服务服务实现:
集成HUAWEI HMS Core能力,需要完成以下准备工作
具体操作,请按照《HUAWEI HMS Core集成准备》中详细说明来完成。
华为定位服务和地图服务可以通过如下两种方式开通,具体开通实现如下:
在AppGallery Connect中选择"我的项目",在项目列表中选择创建的应用,在"项目设置"页面中选择"API管理"。
打开Location Kit、Map Kit服务。
在华为开发者联盟控制台中,在页面侧边栏,选择"HMS API服务"->"API库",并选择对应项目。
在"API库"中找到定位服务、地图服务。
进入卡片中,点击"启用"按钮,即可开通服务。
打开华为开发者联盟AppGallery Connect选择"我的项目",在项目列表中选择创建的应用,在"项目设置"页面中选择"常规",并在项目数据存储位置点击设置按钮。
下载应用中的"agconnect-services.json"文件。
将 "agconnect-services.json"文件移至Android Studio开发工程应用模块的根目录。
打开项目级的build.gradle文件。
项目级build.gradle中代码如下:
buildscript {
ext.kotlin_version = "1.4.10"
repositories {
google()
jcenter()
maven {url 'http://developer.huawei.com/repo/'}
}
dependencies {
classpath "com.android.tools.build:gradle:4.1.1"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.huawei.agconnect:agcp:1.4.0.300'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
jcenter()
maven {url 'http://developer.huawei.com/repo/'}
}
}
打开应用级的build.gradle文件。
在"dependencies"中添加定位服务和地图服务依赖
dependencies {
implementation 'com.huawei.hms:location:{version}'
implementation 'com.huawei.hms:maps:{version}'
}
点击Sync Now 同步工程。
1.打开Android Studio工程的混淆配置文件proguard-rules.pro。
2.加入混淆配置。
-ignorewarnings
-keepattributes *Annotation*
-keepattributes Exceptions
-keepattributes InnerClasses
-keepattributes Signature
-keepattributes SourceFile,LineNumberTable
-keep class com.huawei.hianalytics.**{*;}
-keep class com.huawei.updatesdk.**{*;}
-keep class com.huawei.hms.**{*;}
3.如果开发者使用了AndResGuard,需要在混淆配置文件中加入AndResGuard允许清单。
"R.string.hms*",
"R.string.connect_server_fail_prompt_toast",
"R.string.getting_message_fail_prompt_toast",
"R.string.no_available_network_prompt_toast",
"R.string.third_app_*",
"R.string.upsdk_*",
"R.layout.hms*",
"R.layout.upsdk_*",
"R.drawable.upsdk*",
"R.color.upsdk*",
"R.dimen.upsdk*",
"R.style.upsdk*",
"R.string.agc*"
地图服务(Map Kit)给您提供一套地图开发调用的SDK,地图数据覆盖超过200个国家和地区,支持一百多种语言,方便您轻松地在应用中集成地图相关的功能,全方位提升用户体验。
我们将使用Map Kit,来绘制一个基础地图。
目前华为地图SDK支持的地图容器有两种,SupportMapFragment、MapView:
MapView是Android View类的子类,允许您将地图放置在Android视图中。与SupportMapFragment非常相似,MapView充当地图的容器,通过HuaweiMap对象展示核心地图功能。在常规地图交互模式下调用API时,MapView类的使用者必须在对应的Activity/Fragment的方法中调用以下方法:onCreate(),onStart(),onResume(),onPause(),onStop(),onDestroy(),onSaveInstanceState(Bundle outState)和onLowMemory()。
在本篇Codelab中,我们使用MapView
来创建一个Map示例。
我们先创建一个MapActivity
,在Activity的布局文件中添加一个MapView
,并通过XML文件设置Map的属性。
布局代码
<com.huawei.hms.maps.MapView
android:id="@+id/hw_mapview"
android:layout_width="match_parent"
android:layout_height="match_parent"
map:mapType="normal"
map:uiCompass="true"
map:uiZoomControls="true"/>
MapActivity
中,需要实现OnMapReadyCallback
接口,并声明MapView
对象、HuaweiMap
对象、地图上绘制折线所用到的PolylineOptions
对象,。
Java代码
public class MapActivity extends AppCompatActivity implements OnMapReadyCallback {
private MapView mMapView;
private HuaweiMap mHwMap;
private PolylineOptions mPolylineOptions;
private LocationSource.OnLocationChangedListener mListener;
}
Kotlin代码
class MapActivity : AppCompatActivity(), OnMapReadyCallback {
private lateinit var mMapView: MapView
private var mHwMap: HuaweiMap? = null
private var mPolylineOptions: PolylineOptions? = null
private var mListener: LocationSource.OnLocationChangedListener? = null
}
在MapActivity
的onCreate()
方法中加载MapView
,并使用getMapAsync()
方法来注册回调方法。
Java代码
// init MapView
mMapView = findViewById(R.id.hw_mapview);
Bundle mapViewBundle = null;
if (savedInstanceState != null) {
mapViewBundle = savedInstanceState.getBundle(MAPVIEW_BUNDLE_KEY);
}
mMapView.onCreate(mapViewBundle);
mMapView.getMapAsync(this);
Kotlin代码
// init MapView
mMapView = findViewById(R.id.hw_mapview)
var mapViewBundle: Bundle? = null
if (savedInstanceState != null) {
mapViewBundle = savedInstanceState.getBundle(MAPVIEW_BUNDLE_KEY)
}
mMapView.onCreate(mapViewBundle)
mMapView.getMapAsync(this)
Java代码
@Override
public void onMapReady(HuaweiMap huaweiMap) {
mHwMap = huaweiMap;
}
Kotlin代码
override fun onMapReady(huaweiMap: HuaweiMap?) {
mHwMap = huaweiMap
}
实现onStart(),onResume(),onPause(),onStop(),onDestroy()和onLowMemory()方法中调用MapView对应的方法。。
Java代码
@Override
protected void onStart() {
super.onStart();
mMapView.onStart();
}
@Override
protected void onResume() {
super.onResume();
mMapView.onResume();
}
@Override
protected void onPause() {
super.onPause();
mMapView.onPause();
}
@Override
protected void onStop() {
super.onStop();
mMapView.onStop();
}
@Override
protected void onDestroy() {
super.onDestroy();
mMapView.onDestroy();
}
@Override
public void onLowMemory() {
super.onLowMemory();
mMapView.onLowMemory();
}
Kotlin代码
companion object {
private const val MAPVIEW_BUNDLE_KEY = "MapViewBundleKey"
}
override fun onStart() {
super.onStart()
mMapView.onStart()
}
override fun onResume() {
super.onResume()
mMapView.onResume()
}
override fun onPause() {
super.onPause()
mMapView.onPause()
}
override fun onStop() {
super.onStop()
mMapView.onStop()
}
override fun onDestroy() {
super.onDestroy()
mMapView.onDestroy()
}
override fun onLowMemory() {
super.onLowMemory()
mMapView.onLowMemory()
}
至此基础地图我们已经创建完毕了,现在应用是这个样子的。
定位服务(Location Kit)采用GNSS、Wi-Fi、基站等多途径的混合定位模式进行定位,赋予应用开发者快速、精准地获取用户位置信息的能力,构建全球定位服务能力,助力您发展全球业务。当前定位服务的主要能力包含三个部分:融合定位、活动识别和地理围栏。
本篇Codelab中,我们使用到了Location Kit的融合定位能力。
<!-- Location kit needs Location permission-->
<uses-permission android:name="android.permission.ACCESS_COARES_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<!-- 调试使用-->
<uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION"/>
Java代码
// SDK<=28 所需权限动态申请
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) {
Log.i(TAG, "sdk <= 28 Q");
if (ActivityCompat.checkSelfPermission(this,
Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED
&& ActivityCompat.checkSelfPermission(this,
Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
String[] strings =
{Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION};
ActivityCompat.requestPermissions(this, strings, 1);
}
} else {
// SDK>28 所需权限动态申请,需添加"android.permission.ACCESS_BACKGROUND_LOCATION"权限
if (ActivityCompat.checkSelfPermission(this,
Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED
&& ActivityCompat.checkSelfPermission(this,
Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED
&& ActivityCompat.checkSelfPermission(this,
"android.permission.ACCESS_BACKGROUND_LOCATION") != PackageManager.PERMISSION_GRANTED) {
String[] strings = {android.Manifest.permission.ACCESS_FINE_LOCATION,
android.Manifest.permission.ACCESS_COARSE_LOCATION,
"android.permission.ACCESS_BACKGROUND_LOCATION"};
ActivityCompat.requestPermissions(this, strings, 2);
}
}
Kotlin代码
// SDK ≤ 28 Dynamically Applying for Required Permissions
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) {
Log.i(TAG, "sdk <= 28 Q")
if (ActivityCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_FINE_LOCATION
) != PackageManager.PERMISSION_GRANTED
&& ActivityCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_COARSE_LOCATION
) != PackageManager.PERMISSION_GRANTED
) {
val strings = arrayOf(
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION
)
ActivityCompat.requestPermissions(this, strings, 1)
}
} else {
// //Dynamicly apply for permissions required for SDK > 28. Add the android.permission.ACCESS_BACKGROUND_LOCATION permission.
if (ActivityCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_FINE_LOCATION
) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_COARSE_LOCATION
) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_BACKGROUND_LOCATION
) != PackageManager.PERMISSION_GRANTED
) {
val strings = arrayOf(
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_BACKGROUND_LOCATION
)
ActivityCompat.requestPermissions(this, strings, 2)
}
}
Java代码
protected void onCreate(@Nullable Bundle savedInstanceState) {
fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this);
}
Kotlin代码
override fun onCreate(savedInstanceState: Bundle?) {
fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this)
}
定位服务提供了检查设备定位相关设置的能力。我们先创建一个检测方法checkLocationSettings()
,通过LocationServices
的getSettingsClient(Activity activity)
获取SettingsClient
实例,然后调用checkLocationSettings(LocationSettingsRequest locationSettingsRequest)
接口获取定位设置结果。
Java代码
private void checkLocationSettings() {
LocationSettingsRequest.Builder builder = new LocationSettingsRequest.Builder();
LocationSettingsRequest locationSettingsRequest = builder.build();
SettingsClient settingsClient = LocationServices.getSettingsClient(this);
settingsClient.checkLocationSettings(locationSettingsRequest)
.addOnSuccessListener(new OnSuccessListener<LocationSettingsResponse>() {
@Override
public void onSuccess(LocationSettingsResponse locationSettingsResponse) {
requestLocationUpdate();
}
}).addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(Exception e) {
int statusCode = ((ApiException) e).getStatusCode();
switch (statusCode) {
case LocationSettingsStatusCodes.RESOLUTION_REQUIRED:
try {
ResolvableApiException rae = (ResolvableApiException) e;
rae.startResolutionForResult(MapActivity.this, 0);
} catch (IntentSender.SendIntentException sie) {
}
break;
}
}
});
}
Kotlin代码
private fun checkLocationSettings() {
val builder: LocationSettingsRequest.Builder = LocationSettingsRequest.Builder()
val locationSettingsRequest: LocationSettingsRequest = builder.build()
val settingsClient: SettingsClient = LocationServices.getSettingsClient(this)
settingsClient.checkLocationSettings(locationSettingsRequest)
.addOnSuccessListener { requestLocationUpdate() }.addOnFailureListener { e ->
when ((e as ApiException).statusCode) {
LocationSettingsStatusCodes.RESOLUTION_REQUIRED -> try {
val rae: ResolvableApiException = e as ResolvableApiException
rae.startResolutionForResult(this@MapActivity, 0)
} catch (sie: SendIntentException) {
}
}
}
}
如果您希望应用可以持续获取设备位置,可以使用华为定位服务提供的requestLocationUpdates()接口。该接口通过调用您已经定义的LocationCallback类中onLocationResult()回调方法返回一个包含位置信息的LocationResult对象。
Java
private void requestLocationUpdate() {
mLocationRequest = new LocationRequest();
mLocationRequest.setInterval(5000);
mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
mLocationCallback = new LocationCallback() {
@Override
public void onLocationResult(LocationResult locationResult) {
super.onLocationResult(locationResult);
writeGpsData2Sdcard(locationResult.getLastLocation());
if (mIsRunning) {
updateLocation(locationResult.getLastLocation());
}
}
@Override
public void onLocationAvailability(LocationAvailability locationAvailability) {
super.onLocationAvailability(locationAvailability);
}
};
fusedLocationProviderClient
.requestLocationUpdates(mLocationRequest, mLocationCallback, Looper.getMainLooper())
.addOnSuccessListener(new OnSuccessListener<Void>() {
@Override
public void onSuccess(Void aVoid) {
Log.i(TAG, "request location updates success");
}
})
.addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(Exception e) {
Log.e(TAG, "request location updates failed, error: " + e.getMessage());
}
});
}
Kotlin
private fun requestLocationUpdate() {
mLocationRequest = LocationRequest()
mLocationRequest!!.interval = 5000
mLocationRequest!!.priority = LocationRequest.PRIORITY_HIGH_ACCURACY
mLocationCallback = object : LocationCallback() {
override fun onLocationResult(locationResult: LocationResult) {
super.onLocationResult(locationResult)
writeGpsData2Sdcard(locationResult.lastLocation)
if (mIsRunning) {
processLocationChange(locationResult.lastLocation)
}
}
override fun onLocationAvailability(locationAvailability: LocationAvailability?) {
super.onLocationAvailability(locationAvailability)
}
}
fusedLocationProviderClient
?.requestLocationUpdates(mLocationRequest, mLocationCallback, Looper.getMainLooper())
?.addOnSuccessListener { Log.i(TAG, "request location updates success") }
?.addOnFailureListener { e ->
Log.e(
TAG,
"request location updates failed, error: " + e.message
)
}
}
当我们不再需要接收位置更新时,应当停止位置更新,以便于降低功耗。要停止位置更新,可以调用removeLocationUpdates(),传入与requestLocationUpdates()接口相对应的LocationCallback。
Java代码
private void removeLocationUpdatesWithCallback() {
try {
Task<Void> voidTask = fusedLocationProviderClient.removeLocationUpdates(mLocationCallback);
voidTask.addOnSuccessListener(new OnSuccessListener<Void>() {
@Override
public void onSuccess(Void aVoid) {
}
}).addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(Exception e) {
}
});
} catch (Exception e) {
}
}
Kotlin代码
private fun removeLocationUpdatesWithCallback() {
try {
val voidTask: Task<Void> =
fusedLocationProviderClient!!.removeLocationUpdates(mLocationCallback)
voidTask.addOnSuccessListener { }.addOnFailureListener { }
} catch (e: Exception) {
}
}
在前两个步骤中,我们已经完成了地图服务定位服务的基础功能实现。下面我们将要将两种能力结合起来,实现跑步运动时记录运动轨迹,并计算距离速度的场景。
在OnMapReady回调中,添加位置源,
JAVA代码
// Add Location Source
mHwMap.setLocationSource(new LocationSource() {
@Override
public void activate(OnLocationChangedListener onLocationChangedListener) {
mListener = onLocationChangedListener;
}
@Override
public void deactivate() {
}
});
Kotlin代码
//Add Location Source
mHwMap?.setLocationSource(object : LocationSource {
override fun activate(onLocationChangedListener: LocationSource.OnLocationChangedListener?) {
mListener = onLocationChangedListener
}
override fun deactivate() {}
})
在OnMapReady回调中,添加获取位置信息的代码,并更新Map的镜头位置。
JAVA代码
//Obtains the current position and updates the map camera.
try {
Task<Location> lastLocation = fusedLocationProviderClient.getLastLocation();
lastLocation.addOnSuccessListener(new OnSuccessListener<Location>() {
@Override
public void onSuccess(Location location) {
mListener.onLocationChanged(location);
if (mListener != null) {
mListener.onLocationChanged(location);
CameraUpdate cameraUpdate = CameraUpdateFactory.newLatLngZoom(
new LatLng(location.getLatitude(), location.getLongitude()), 15f);
mHwMap.animateCamera(cameraUpdate);
}
}
}).addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(Exception e) {
}
});
} catch (Exception e) {
}
Kotlin代码
//Obtains the current position and updates the map camera.
try {
val lastLocation: Task<Location> = fusedLocationProviderClient!!.lastLocation
lastLocation.addOnSuccessListener { location ->
mListener!!.onLocationChanged(location)
if (mListener != null) {
mListener!!.onLocationChanged(location)
val cameraUpdate: CameraUpdate = CameraUpdateFactory.newLatLngZoom(
LatLng(location.latitude, location.longitude), 15f
)
mHwMap!!.animateCamera(cameraUpdate)
}
}.addOnFailureListener {
Log.d(TAG, "onMapReady: Obtains the current position failure")
}
} catch (e: Exception) {
}
设置完位置源后,我们就可以在地图上,看见我们的位置了。
我们先添加展示运动数据的布局
<LinearLayout
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_marginTop="60dp"
android:layout_marginStart="15dp"
android:layout_marginEnd="15dp"
android:background="@color/bgWhite"
android:orientation="horizontal">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginStart="20dp"
android:layout_marginTop="20dp"
android:orientation="vertical">
<TextView
android:id="@+id/tv_distance"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:gravity="center_horizontal"
android:maxLength="8"
android:text="0.00"
android:textColor="#000000"
android:textSize="25sp"
android:textStyle="bold" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:layout_marginStart="6dp"
android:layout_marginBottom="2.5dp"
android:text="km"
android:textColor="#88000000"
android:textSize="18sp" />
</LinearLayout>
再在MapActivity中,添加展示运动数据界面
JAVA代码
// Add the exercise data display UI.
mTvSpeed = findViewById(R.id.tv_speed);
mTvDistance = findViewById(R.id.tv_distance);
mTime = findViewById(R.id.cm_time);
mTvStart = findViewById(R.id.tv_start);
mTvStart.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
processStartClick();
}
});
Kotlin代码
// Add the exercise data display UI.
mTvSpeed = findViewById(R.id.tv_speed)
mTvDistance = findViewById(R.id.tv_distance)
mTime = findViewById(R.id.cm_time)
mTvStart = findViewById(R.id.tv_start)
mTvStart!!.setOnClickListener(View.OnClickListener { processStartClick() })
开始跑步时,在processStartClick()方法中添加以下代码
JAVA代码
mIsRunning = true;
mPath.reset();
mPath.setStartTime(System.currentTimeMillis());
mHandler.post(mTimeRunnable);
mTvStart.setText("Stop");
Kotlin代码
mIsRunning = true
mPath.reset()
mPath.startTime = System.currentTimeMillis()
mHandler.post(mTimeRunnable)
mTvStart!!.text = "Stop"
结束跑步时,在processStartClick()方法中添加以下代码
JAVA代码
mIsRunning = false;
mPath.setEndTime(System.currentTimeMillis());
mTvStart.setText("Start");
mHandler.removeCallbacks(mTimeRunnable);
if (mPath.getPathLine().size() > 0) {
mPath.setEndPoint(mPath.getPathLine().get(mPath.getPathLine().size() - 1));
if (null != mMarkerStart && null != mMarkerEnd) {
mMarkerStart.remove();
mMarkerEnd.remove();
}
MarkerOptions StartPointOptions = new MarkerOptions()
.icon(BitmapDescriptorFactory.fromResource(R.mipmap.map_marker_bubble_azure_small))
.position(mPath.getStartPoint());
StartPointOptions.title("Start Point");
StartPointOptions.snippet("Start Point");
mMarkerStart = mHwMap.addMarker(StartPointOptions);
MarkerOptions EndPointOptions = new MarkerOptions()
.icon(BitmapDescriptorFactory.fromResource(R.mipmap.map_marker_bubble_azure_small))
.position(mPath.getEndPoint());
EndPointOptions.title("End Point");
EndPointOptions.snippet("End Point");
mMarkerEnd = mHwMap.addMarker(EndPointOptions);
}
Kotlin代码
mIsRunning = false
mPath.endTime = (System.currentTimeMillis())
mTvStart!!.text = "Start"
mHandler.removeCallbacks(mTimeRunnable)
if (mPath.mPathLinePoints!!.size > 0) {
mPath.endPoint = (mPath.mPathLinePoints!!.get(mPath.mPathLinePoints!!.size - 1))
if (null != mMarkerStart && null != mMarkerEnd) {
mMarkerStart!!.remove()
mMarkerEnd!!.remove()
}
val startPointOptions: MarkerOptions = MarkerOptions()
.icon(BitmapDescriptorFactory.fromResource(R.mipmap.map_marker_bubble_azure_small))
.position(mPath.mStartPoint)
startPointOptions.title("Start Point")
startPointOptions.snippet("Start Point")
mMarkerStart = mHwMap!!.addMarker(startPointOptions)
val endPointOptions: MarkerOptions = MarkerOptions()
.icon(BitmapDescriptorFactory.fromResource(R.mipmap.map_marker_bubble_azure_small))
.position(mPath.mEndPoint)
endPointOptions.title("End Point")
endPointOptions.snippet("End Point")
mMarkerEnd = mHwMap!!.addMarker(endPointOptions)
}
将processStartClick()方法绑定到"Start"按钮上。
我们创建一个处理方法processLocationChange(Location location)。在这个方法中我们根据位置信息,在地图上绘制折线。
JAVA代码
private void processLocationChange(Location location) {
LatLng latLng = new LatLng(location.getLatitude(), location.getLongitude());
// Draw a motion track on a map
mPolylineOptions.add(latLng);
mHwMap.addPolyline(mPolylineOptions);
// Updating Map Kit Camera
if (mListener != null) {
mListener.onLocationChanged(location);
CameraUpdate cameraUpdate = CameraUpdateFactory.newLatLngZoom(
new LatLng(location.getLatitude(), location.getLongitude()), 15f);
mHwMap.animateCamera(cameraUpdate);
}
}
Kotlin代码
private fun processLocationChange(location: Location) {
val latLng = LatLng(location.latitude, location.longitude)
if (mPath.mStartPoint == null) {
mPath.mStartPoint = latLng
}
mPath.addPoint(latLng)
val distance: Float = mPath.updateDistance()
val sportMile = distance / 1000.0
if (mSeconds > 0) {
val distribution = mSeconds.toDouble() / 60.0 / sportMile
mPath.setDistribution(distribution)
mTvSpeed!!.text = mDecimalFormat.format(distribution)
mTvDistance!!.text = mDecimalFormat.format(sportMile)
} else {
mPath.setDistribution(0.0)
mTvSpeed!!.text = "0.00"
mTvDistance!!.text = "0.00"
}
// Draw a motion track on a map
mPolylineOptions!!.add(latLng)
mHwMap!!.addPolyline(mPolylineOptions)
// Updating Map Kit Camera
if (mListener != null) {
mListener!!.onLocationChanged(location)
val cameraUpdate: CameraUpdate = CameraUpdateFactory.newLatLngZoom(
LatLng(location.latitude, location.longitude), 15f
)
mHwMap!!.animateCamera(cameraUpdate)
}
}
在位置更新的回调方法requestLocationUpdate()中,我们调用processLocationChange()方法。
JAVA代码
private void requestLocationUpdate() {
mLocationCallback = new LocationCallback() {
@Override
public void onLocationResult(LocationResult locationResult) {
super.onLocationResult(locationResult);
if (mIsRunning) {
processLocationChange(locationResult.getLastLocation());
}
}
};
Kotlin代码
mLocationCallback = object : LocationCallback() {
override fun onLocationResult(locationResult: LocationResult) {
super.onLocationResult(locationResult)
writeGpsData2Sdcard(locationResult.lastLocation)
if (mIsRunning) {
processLocationChange(locationResult.lastLocation)
}
}
override fun onLocationAvailability(locationAvailability: LocationAvailability?) {
super.onLocationAvailability(locationAvailability)
}
}
当我们持续获得实时位置更新的时候,就可以绘制运动轨迹了。
华为定位服务提供了模拟定位能力,依托这项能力,我们可以用于应用调试。
我们需要在测试设备中的开发人员选项中,选择模拟位置信息的应用。
我们获取一组模拟GPS数据,本篇Codelab使用的Java GPS数据存放在MockGpsData.java中,Kotlin GPS数据保存在MockGpsData.kt中。
在我们创建地图及获取定位之前,加载模拟GPS数据,代码如下:
JAVA代码
Task<Void> voidTask = fusedLocationProviderClient.setMockMode(true);
voidTask.addOnSuccessListener(new OnSuccessListener<Void>() {
@Override
public void onSuccess(Void aVoid) {
Toast.makeText(MainActivity.this, "setMockMode onSuccess", Toast.LENGTH_SHORT).show();
mHandler.post(new Runnable() {
int point = 0;
@Override
public void run() {
if (point + 1 >= MockGpsData.POINTS.length) {
return;
}
double latitude = MockGpsData.POINTS[point++];
double longitude = MockGpsData.POINTS[point++];
final Location mockLocation = new Location(LocationManager.GPS_PROVIDER);
mockLocation.setLongitude(longitude);
mockLocation.setLatitude(latitude);
Task<Void> voidTask = fusedLocationProviderClient.setMockLocation(mockLocation);
voidTask.addOnSuccessListener(new OnSuccessListener<Void>() {
@Override
public void onSuccess(Void aVoid) {
}
}).addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(Exception e) {
}
});
mHandler.postDelayed(this, 100);
}
});
}
}).addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(Exception e) {
Toast.makeText(MainActivity.this, "setMockMode onFailure:" + e.getMessage(), Toast.LENGTH_SHORT).show();
}
});
Kotlin代码
val voidTask: Task<Void> = fusedLocationProviderClient!!.setMockMode(true)
voidTask.addOnSuccessListener(object : OnSuccessListener<Void?> {
override fun onSuccess(aVoid: Void?) {
Toast.makeText(this@MainActivity, "setMockMode onSuccess", Toast.LENGTH_SHORT)
.show()
mHandler.post(object : Runnable {
var point = 0
override fun run() {
if (point + 1 >= MockGpsData.POINTS.size) {
return
}
val latitude: Double = MockGpsData.POINTS.get(point++)
val longitude: Double = MockGpsData.POINTS.get(point++)
val mockLocation = Location(LocationManager.GPS_PROVIDER)
mockLocation.longitude = longitude
mockLocation.latitude = latitude
val voidTask: Task<Void> =
fusedLocationProviderClient!!.setMockLocation(mockLocation)
voidTask.addOnSuccessListener(object : OnSuccessListener<Void?> {
override fun onSuccess(aVoid: Void?) {}
}).addOnFailureListener(object : OnFailureListener {
override fun onFailure(e: Exception?) {}
})
mHandler.postDelayed(this, 100)
}
})
}
}).addOnFailureListener { e ->
if (e != null) {
Toast.makeText(
this@MainActivity,
"setMockMode onFailure:" + e.message,
Toast.LENGTH_SHORT
).show()
}
}
加载完成后,我们便可以使用模拟GPS数据进行调试。
干得好,您已经成功完成了Codelab并学到了:
您可以阅读下面链接,了解更多相关的信息。
您可以点击下方按钮下载源码。