跑步APP设计

Posted by 周宝航 on June 21, 2018

跑步APP

  • 选了一门专业选修课——无线传输与定位,其实主要讲解的是GPS整个架构。
  • 最后,老师在课程设计部分留了一个基于GPS的手机APP设计。思来想去,决定做这个跑步APP了。结果,在前几天告诉我们,卓越工程师班的同学没有这个设计了。哎。。。。。反正都做完了,就拿来写一哈blog吧。
  • APP截屏如下。

Alt text

功能简介

  • 记录跑步时间、跑步距离等信息。
  • 在地图上显示跑步轨迹。
  • 具有暂停功能,可以点击按钮进入暂停模式,再按一次进入跑步计时模式,长按则是停止本次跑步。

开发必备

  • 以上这些功能中,定位部分是基于手机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);
                }
            }
        });

讨论

  1. 总的来说,这个APP还是挺简单的。花了一天的时间,写完了整体;第二天优化了一些小细节。

  2. 不过,在高德地图的API部分花了一点时间。主要是定位蓝点的显示有些问题,百度了好久才找到根本原因。它自身的AMap类需要设置setLocationSource来触发定位。然后,在activate回调函数中取得onLocationChangedListener。最后,通过onLocationChangedListener的onLocationChanged(aMapLocation)方法更新蓝点的显示。