跑步APP
- 选了一门专业选修课——无线传输与定位,其实主要讲解的是GPS整个架构。
- 最后,老师在课程设计部分留了一个基于GPS的手机APP设计。思来想去,决定做这个跑步APP了。结果,在前几天告诉我们,卓越工程师班的同学没有这个设计了。哎。。。。。反正都做完了,就拿来写一哈blog吧。
- APP截屏如下。
功能简介
- 记录跑步时间、跑步距离等信息。
- 在地图上显示跑步轨迹。
- 具有暂停功能,可以点击按钮进入暂停模式,再按一次进入跑步计时模式,长按则是停止本次跑步。
开发必备
- 以上这些功能中,定位部分是基于手机GPS,而地图显示部分则是基于高德地图的API。
高德地图SDK接入
- 这部分还是很麻烦的,主要是包签名部分,网上好多教程,这里贴个靠谱的链接。
- https://blog.csdn.net/m0_37602117/article/details/75949320
关键类的实现
GPS部分
- Android6.0以后引入动态权限,所以要动态申请开启GPS。
- 初始化LocationManager,负责管理GPS。其中,需要设置requestLocationUpdates,里面添加LocationListener监听回调函数。
- 该类中,还添加了RecvDataListener接口,暴露给外部,负责GPS数据接收后的逻辑处理。
- 还有一点,GPS坐标需要转换为高德地图的坐标,这一点在GPS2GAODE方法中有所体现。
public class GPSManager {
private Context context;
private LocationManager lm;
private RecvDataListener recvDataListener;
public GPSManager(Context context) {
this.context = context;
}
public void InitGPS() {
if(ContextCompat.checkSelfPermission(context,android.Manifest.permission.ACCESS_FINE_LOCATION)== PackageManager.PERMISSION_GRANTED) {
lm = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
if (!isGPSAble()) {
Toast.makeText(context, "请打开GPS~", Toast.LENGTH_SHORT).show();
OpenGPS();
}
//从GPS获取最近的定位信息
Location lc = lm.getLastKnownLocation(LocationManager.GPS_PROVIDER);
updateShow(lc);
//设置间隔两秒获得一次GPS定位信息
lm.requestLocationUpdates(LocationManager.GPS_PROVIDER, 2000, 8, new LocationListener() {
@Override
public void onLocationChanged(Location location) {
// 当GPS定位信息发生改变时,更新定位
updateShow(location);
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
}
@Override
public void onProviderEnabled(String provider) {
// 当GPS LocationProvider可用时,更新定位
}
@Override
public void onProviderDisabled(String provider) {
}
});
}
}
public LatLng GPS2GAODE(double lat, double lon) {
LatLng mark=null;
CoordinateConverter converter=new CoordinateConverter();
converter.from(CoordinateConverter.CoordType.GPS);
try {
converter.coord(new LatLng(lat,lon));
mark=converter.convert();
}catch (Exception e){
}
return mark;
}
//定义一个更新显示的方法
private void updateShow(Location location) {
if (recvDataListener != null) {
recvDataListener.onReceived(location);
}
}
private void OpenGPS() {
Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
((Activity)context).startActivityForResult(intent, 0);
}
private boolean isGPSAble() {
return lm.isProviderEnabled(android.location.LocationManager.GPS_PROVIDER) ? true : false;
}
public interface RecvDataListener {
void onReceived(Location location);
}
public void setOnRecvDataListener(RecvDataListener recvDataListener) {
this.recvDataListener = recvDataListener;
}
}
高德地图控制部分
- 由于在地图显示上,需要有气泡坐标点的设置与清除。所以写了一个简单的类来管理。
public class MapManager {
private MapView mMapView = null;
private AMap aMap = null;
public MapManager(MapView mMapView, AMap aMap) {
this.mMapView = mMapView;
this.aMap = aMap;
}
public void AddMarker(LatLng pos, String title, boolean isCenter) {
if (isCenter) {
aMap.moveCamera(CameraUpdateFactory.newLatLngZoom(pos, 19));
}
aMap.addMarker(new MarkerOptions().position(pos).title(title).snippet("DefaultMarker"));
}
public void ClearMarker() {
aMap.clear();
}
}
计时部分
- 界面右上方的计时部分,使用的是Timer、Task来实现的。
- 主要两个类。RunTask为业务处理子类,负责计时以及通过Handler更新UI。RunTaskManager负责控制RunTask的状态。
public class RunTask extends TimerTask {
private int t;
private MainViewHandler mHandler;
public RunTask(int t, MainViewHandler mHandler) {
this.t = t;
this.mHandler = mHandler;
}
@Override
public void run() {
t++;
Bundle bundle = new Bundle();
bundle.putInt(MainViewHandler.VAL_TIME, t);
Message msg = new Message();
msg.what = MainViewHandler.TIME;
msg.setData(bundle);
mHandler.sendMessage(msg);
}
public int getT() {
return t;
}
}
public class RunTaskManager {
private Timer timer;
private int t;
private RunTask task;
public RunTaskManager() {
}
public void Start(RunTask task) {
this.task = task;
timer = new Timer();
timer.schedule(task, 1000, 1000);
}
public int Pause() {
t = task.getT();
timer.cancel();
timer = null;
task.cancel();
task = null;
return t;
}
public void Stop() {
if (timer != null) {
timer.cancel();
timer = null;
}
}
}
跑步业务逻辑部分
- 获取GPS数据后,进行业务逻辑的处理。
- 获取GPS坐标后,将其转化为高德地图坐标。控制地图,将当前位置移动到屏幕中心。
- 若旧的坐标(oldLatLng)不为空说明正在跑步,计算新坐标(newLatLng)与旧坐标(oldLatLng)的距离,更新至UI。
- 然后调用高德地图的API,将这一段跑动距离作为线段添加至地图上。
- 如此完成一次业务处理。
gpsManager.setOnRecvDataListener(new GPSManager.RecvDataListener() {
@Override
public void onReceived(Location location) {
LatLng pos = gpsManager.GPS2GAODE(location.getLatitude(), location.getLongitude());
newLatLng = pos;
aMap.moveCamera(CameraUpdateFactory.newLatLngZoom(pos, 16));
AMapLocation aMapLocation = new AMapLocation(location);
aMapLocation.setLongitude(pos.longitude);
aMapLocation.setLatitude(pos.latitude);
onLocationChangedListener.onLocationChanged(aMapLocation);
if (oldLatLng != null) {
float[] results=new float[1];
try{
Location.distanceBetween(oldLatLng.latitude, oldLatLng.longitude, newLatLng.latitude, newLatLng.longitude, results);
}catch(Exception e){
e.printStackTrace();
}
distance += results[0] / 1000.0;
tv_miles.setText(String.format("%.1f", distance));
PolylineOptions polylineOptions = new PolylineOptions();
polylineOptions.width(15);
//设置渐变颜色
polylineOptions.color(Color.BLUE);
polylineOptions.add(oldLatLng, newLatLng);
oldLatLng = newLatLng;
aMap.addPolyline(polylineOptions);
}
}
});
讨论
-
总的来说,这个APP还是挺简单的。花了一天的时间,写完了整体;第二天优化了一些小细节。
-
不过,在高德地图的API部分花了一点时间。主要是定位蓝点的显示有些问题,百度了好久才找到根本原因。它自身的AMap类需要设置setLocationSource来触发定位。然后,在activate回调函数中取得onLocationChangedListener。最后,通过onLocationChangedListener的onLocationChanged(aMapLocation)方法更新蓝点的显示。