使用高德SDK实现定位打卡

模仿钉钉打卡

Posted by JovenHe on April 30, 2019

近期项目需要类似钉钉打卡的功能,所以在高德地图的基础上进行开发实现,因为钉钉打卡的地图就是高德地图,本文需要结合高德开放明台的文档来学习使用。

一开始仅仅是三月中旬调研过程中所做的案例项目,在实际项目中模仿钉钉打卡是一个稍微复杂的流程,下面是在4月底项目基本完结后所做的总结

申请 Key

在高德开放平台申请个人账号

在控制台-我的应用-创建应用-添加key

高德控制台.png 高德应用添加key.png

按照提示填写即可,在获取中可以看到SHA1与应用签名相关,PackageName与应用包名相关,

提交后可以得到key,并在后续中可以使用

Android Studio 工程配置

libs中导入jar文件与so库,因为我只需要地图显示与定位的功能,所以只导入Android_Map3D_SDK和AMap_Location,在我的使用中发现Map3D比Map2D的显示更加流畅,在滑动时Map2D卡顿明显

jar与so.png

在AndroidManifest中配置权限,application标签中配置Key:

AndroidManifest中配置.png

在app的build.gradle配置,确保debug与release版本都有签名,并设置jniLibs.srcDirs = [‘libs’],加载SO文件

build.gradle.png

实际项目

首先在页面上有两个页面,一个是定位打卡,一个是地图打卡,下面

钉钉定位打卡页面 钉钉地图打卡页面
定位打卡.png 地图打卡.png

定位打卡

可以看到定位打卡,只会用到高德的定位模块,所以在这个fragment中进行定位相关操作

public void onViewCreated() {
mPresenter.getPosition(SharePreUtils.getString(mContext, SharePreUtils.usrToken));
        //初始化定位
        mLocationClient = new AMapLocationClient(mContext.getApplicationContext());
        //设置定位回调监听
        mLocationClient.setLocationListener(location -> {
            //得到定位数据location,可进行位置判断操作
        });

        AMapLocationClientOption option = new AMapLocationClientOption();
        /**
         * 设置定位场景,目前支持三种场景(签到、出行、运动,默认无场景)
         */
        option.setLocationPurpose(AMapLocationClientOption.AMapLocationPurpose.SignIn);
        if (null != mLocationClient) {
            mLocationClient.setLocationOption(option);
            //设置场景模式后最好调用一次stop,再调用start以保证场景模式生效
            mLocationClient.stopLocation();
            mLocationClient.startLocation();
        }
    }

    @Override
    public void onResume() {
        super.onResume();
        if (!LocationUtils.isLocationEnabled(mContext)) {
            Toast.makeText(mContext, "请打开GPS位置信息", Toast.LENGTH_SHORT).show();
            LocationUtils.openGpsSettings(mContext);
        }
        final RxPermissions rxPermissions = new RxPermissions(this);
        rxPermissions
                .request(needPermissions)
                .subscribe(granted -> {
                    if (granted) {  
                        if (!mLocationClient.isStarted()) {
                            mLocationClient.startLocation();
                        }
                    } else {
Toast.makeText(mContext,"请赋予位置权限,否则无法定位",Toast.LENGTH_SHORT).show();
                        // Oups permission denied
                    }
                });
    }

    @Override
    public void onStop() {
        super.onStop();
        mLocationClient.stopLocation();
    }
    @Override
    public void onDestroy() {
        super.onDestroy();
        mLocationClient.onDestroy();
    }

可以看到在View创建后构建定位服务类对象,设置相关参数后开启定位,并在回调中得到定位数据,得到位置数据后我们就需要根据服务器设置的考勤信息来判断是否外勤,是否是迟到早退,并以此来更新打卡按钮的显示了。

其中判断是否外勤的方法高德已经提供:

boolean isInRange = false;
for (int i = 0; i < mPointPositions.size(); i++) {
    float dis = CoordinateConverter.calculateLineDistance(mPointMine,
            new DPoint(mPointPositions.get(i).getLatitude(), mPointPositions.get(i).getLongitude()));
    Log.e(TAG, String.format("距离考勤地点%f米", dis));
    if (dis < mPointPositions.get(i).getDistance()) {
        mtvLocationInfo.setText("您已进入考勤范围:" + mPointPositions.get(i).getPlaceName());
        isInRange = true;
        break;
    }
}

mPointPositions是后台返回的打卡地点数据集合,通过CoordinateConverter.calculateLineDistance测量两个位置点得到距离,以此判断是否在范围内。

当然这个页面的开发难度更多是逻辑判断的,判断是否是迟到、早退、外勤还是正常打卡,根据后台数据判断是上班、下班还是更新等多种情况。最好是把所有的判断捋清楚后放在计时里。

地图打卡

可以看到地图打卡是会用到地图和定位的,当然大量的逻辑判断其实都和定位打卡是一致的,相关参数直接通过intent传递过来,判断直接照搬就行,重点是地图上的显示

布局文件中

<com.amap.api.maps.MapView
    android:id="@+id/map"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>

activity中

public void initData() {
    //初始化地图控制器对象
    if (aMap == null) {
        aMap = mMapView.getMap();
    }
    //和自定义infoWindow相绑定
    aMap.setInfoWindowAdapter(this);

    //初始化定位
    mLocationClient = new AMapLocationClient(getApplicationContext());
    //设置定位回调监听
    mLocationClient.setLocationListener(aMapLocation -> {
        Log.e(TAG, "getLocationDetail: " + aMapLocation.getLatitude() + "=" + aMapLocation.getLongitude());
        dismissLoadingDialog();
        aMap.clear();
        circles.clear();

        MarkerOptions markerOption = new MarkerOptions();
        LatLng mylatLng = new LatLng(aMapLocation.getLatitude(), aMapLocation.getLongitude());
        markerOption.position(mylatLng);

        for (PositionBean.DataBean.PlaceBean pointPosition : mPointPositions) {
            circles.add(aMap.addCircle(
                    new CircleOptions()
                            .center(new LatLng(pointPosition.getLatitude(), pointPosition.getLongitude()))
                            .radius(pointPosition.getDistance())
                            .fillColor(Color.parseColor("#4D80DAFF"))
                            .strokeColor(Color.parseColor("#119AFF"))
                            .strokeWidth(Tools.getPxFromDp(getResources(), 2))));
        }
        for (Circle circle : circles) {
            isInRange = circle.contains(mylatLng);
            if (isInRange) {
                break;
            }

        }
        markerOption.title(isInRange ? "已进入考勤范围" : "未进入考勤范围");
        if (isLocalFirst) {
            aMap.moveCamera(CameraUpdateFactory.newLatLngZoom(mylatLng, 17.5f));
            isLocalFirst = false;
        } else {
            aMap.moveCamera(CameraUpdateFactory.newLatLng(mylatLng));
        }

        markerOption.draggable(false);
        markerOption.icon(BitmapDescriptorFactory.fromBitmap(
                Tools.loadBitmapFromView(getMyLocationView())));
        Marker marker = aMap.addMarker(markerOption);
        marker.showInfoWindow();
        mtvLocationStatus.setText(isInRange ? "(考勤范围内)" : "(外勤)");
        mtvLocationInfo.setText(aMapLocation.getAddress());

    });

    AMapLocationClientOption option = new AMapLocationClientOption();
    /**
     * 设置定位场景,目前支持三种场景(签到、出行、运动,默认无场景)
     */
    option.setLocationPurpose(AMapLocationClientOption.AMapLocationPurpose.SignIn);
    if (null != mLocationClient) {
        mLocationClient.setLocationOption(option);
        //设置场景模式后最好调用一次stop,再调用start以保证场景模式生效
        mLocationClient.stopLocation();
        mLocationClient.startLocation();
    }


}

@Override
protected void onDestroy() {
    super.onDestroy();
    //在activity执行onDestroy时执行mMapView.onDestroy(),销毁地图
    mMapView.onDestroy();
    mLocationClient.onDestroy();
    handler.removeMessages(1);
}

private View getMyLocationView() {
    View view = LayoutInflater.from(mContext).inflate(R.layout.layout_location_marker, null, false);

    Tools.layoutView(view, (int) Tools.getPxFromDp(getResources(), 47),
            (int) Tools.getPxFromDp(getResources(), 55));
    TextView tvName = view.findViewById(R.id.tv_name);
    String name = SharePreUtils.getString(mContext, SharePreUtils.name);

    tvName.setText(name.length() >= 2 ? (name.substring(name.length() - 2, name.length())) : name);
    return view;
}




@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    //在activity执行onSaveInstanceState时执行mMapView.onSaveInstanceState (outState),保存地图当前的状态
    mMapView.onSaveInstanceState(outState);
}

@Override
protected void onPause() {
    super.onPause();
    //在activity执行onPause时执行mMapView.onPause (),暂停地图的绘制
    mMapView.onPause();
    mLocationClient.stopLocation();
}

@Override
protected void onResume() {
    super.onResume();
    //在activity执行onResume时执行mMapView.onResume (),重新绘制加载地图
    mMapView.onResume();
    if (!LocationUtils.isGpsEnabled(mContext)) {
        Toast.makeText(mContext, "请打开GPS位置信息", Toast.LENGTH_SHORT).show();
        LocationUtils.openGpsSettings(mContext);
    }
    final RxPermissions rxPermissions = new RxPermissions(this);
    rxPermissions
            .request(needPermissions)
            .subscribe(granted -> {
                Log.e(TAG, "granted: " + granted);
                if (granted) {
                    mLocationClient.startLocation();
                } else {
                    dismissLoadingDialog();
                    Toast.makeText(mContext, "请赋予位置权限,否则无法定位", Toast.LENGTH_SHORT).show();
                }
            });
}

@Override
public View getInfoWindow(Marker marker) {
    View infoWindow = getLayoutInflater().inflate(R.layout.layout_infowindow, null);//display为自定义layout文件
    TextView name = (TextView) infoWindow.findViewById(R.id.tv_info);
    name.setText(marker.getTitle());

    return infoWindow;
}

@Override
public View getInfoContents(Marker marker) {
    return null;
}

地图组件设置

mUiSettings = aMap.getUiSettings();
        //设置缩放按钮的显示
        mUiSettings.setZoomControlsEnabled(false);
        //设置默认定位按钮是否显示,非必需设置。
        mUiSettings.setMyLocationButtonEnabled(true);
        //设置比例尺控件是否显示
        mUiSettings.setScaleControlsEnabled(true);
        //设置地图logo显示位置
        mUiSettings.setLogoPosition(AMapOptions.LOGO_POSITION_BOTTOM_RIGHT);

这一步是获取UI设置后,控制缩放按钮、定位按钮、比例尺控件、地图logo等控件的显示与位置设置

其实代码上很简单,只是得到定位后就绘制考勤范围,然后绘制用户定位点的Marker覆盖物的位置、图标、显示信息等,显示效果如下:

地图显示.png

总结

其实对接第三方是很简单的,除非没有任何文档,关键在业务逻辑上,高德地图使用还是很简单的,有不明白的也可以直接查看参考手册来查找相关APi