Quay bản đồ google map theo hướng thiết bị khi di chuyển.

I. Giới thiệu về google map api Nhắc đến google map chắc hẳn những người dùng smartphone hiện nay đều đã từng sử dụng hoặc ít nhất đã từng nghe nói đến nó, một ứng dụng vô cùng hữu ích cho phép người dùng có thể tìm kiếm vị trí, địa điểm, đường đi dựa trên bản đồ, ... . Google map api là một thư viện mà google phát triển để cho phép các lập trình viên có thể tích hợp google map vào ứng dụng của mình để có thể tạo ra được những ứng dụng hữu ích và nhiều tính năng hay, đã có không ít những ứng dụng nổi tiêng có tích hợp google map chúng ta có thể kể đến như Uber, Grab... tất cả những ứng dụng đó đều rất hữu ích và tiện dụng. Google map cho phép chúng ta có thể sử dụng và custom map theo ý muốn của chúng ta.

Hôm nay trong bài viết này mình sẽ giới thiệu về cách tích hợp google map và quay bản đồ theo hướng của thiết bị dựa trên cảm biến, nó tương ứng với chế độ compass khi chúng ta click vào button location tới khi hiển thị icon la bàn trên ứng dụng google map. Nó là một chế độ rất hữu ích, khi bản đồ ở chế độ này camera của bản đồ sẽ luôn quay theo hướng quay của thiết bị, nó giúp chúng ta khi di chuyển có thể dễ dàng theo dõi đường đi trên bản đồ hơn và tạo cảm giác di chuyển tốt hơn.

Để thực hiện yêu cầu được đặt ra mình có đưa ra một cách giải quyết đó là sử dụng sensor trong android để lắng nghe sự kiện quay device và quay bản đồ theo góc quay của thiết bị. Sau đây chúng ta sẽ thử thực hiện nó.

II. Tạo ứng dụng demo Chúng ta sẽ tạo một project android để thực hiện giải pháp trên .

Để sử dụng được google map trong ứng dụng cần phải thêm thư viện hỗ trợ vào gradle và cùng với đó là đăng kí một api key, các bạn có thể tham khảo hướng dẫn sau: https://developers.google.com/maps/documentation/android-api/signup

compile 'com.google.android.gms:play-services-maps:9.6.1'
compile 'com.google.android.gms:play-services-location:9.6.1'

Tiếp theo chúng ta cần tạo một class để hanle cảm biến gia tốc của máy và tạo một callback để thực thi việc quay bản đồ. Ở bài viết trước mình cũng đã giới thiệu và cùng các bạn tìm hiểu qua về sensor, nếu bạn nào quan tâm có thể tìm đọc tại đây: https://viblo.asia/nghicv.57/posts/6J3ZgkxgZmB

public class DeviceRotationSensor implements SensorEventListener {

    private SensorManager mSensorManager;
    private float[] mLastAccelerometers;
    private float[] mLastMagnetometers;
    private boolean mLastAccelerometerSet;
    private boolean mLastMagnetometerSet;
    private float[] mOrientation = new float[3];
    private int mDirection;
    private Sensor mAccelerometer;
    private Sensor mMagnetometer;
    private IOnDeviceRotationListener mOnDeviceRotationListener;

    public DeviceRotationSensor(SensorManager sensorManager) {
        mSensorManager = sensorManager;
    }

    @Override
    public void onSensorChanged(android.hardware.SensorEvent sensorEvent) {
        switch (sensorEvent.sensor.getType()) {
            case Sensor.TYPE_ACCELEROMETER:
                mLastAccelerometers = sensorEvent.values.clone();
                mLastAccelerometerSet = true;
                break;
            case Sensor.TYPE_MAGNETIC_FIELD:
                mLastMagnetometers = sensorEvent.values.clone();
                mLastMagnetometerSet = true;
                break;
            default:
                break;
        }
        float[] rotationMatrix = new float[9];
        if (mLastMagnetometerSet && mLastAccelerometerSet) {
            boolean success =
                    SensorManager.getRotationMatrix(rotationMatrix, null, mLastAccelerometers,
                            mLastMagnetometers);
            if (success) {
                SensorManager.getOrientation(rotationMatrix, mOrientation);
                int bearing = (int) Math.toDegrees(mOrientation[0]);
                if (mOnDeviceRotationListener != null) {
                    mOnDeviceRotationListener.onRotate(bearing);
                    Log.d("nghicv", bearing + "");
                }
            }
        }
    }

    @Override
    public void onAccuracyChanged(Sensor sensor, int i) {

    }

    public float getDirection() {
        return mDirection;
    }

    public void register() {
        mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
        mMagnetometer = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
        mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_UI);
        mSensorManager.registerListener(this, mMagnetometer, SensorManager.SENSOR_DELAY_UI);
    }

    public void unregister() {
        mSensorManager.unregisterListener(this, mAccelerometer);
        mSensorManager.unregisterListener(this, mMagnetometer);
    }

    public void setOnDeviceRotationListener(IOnDeviceRotationListener onDeviceRotationListener) {
        mOnDeviceRotationListener = onDeviceRotationListener;
    }
}

Interface IOnDeviceRotationListener

public interface IOnDeviceRotationListener {

    void onRotate(int bearing);
}

Class trên thực hiện việc goi hàm onRotate() và truyền vào một đối số là góc quay mỗi khi người dùng có tác động quay, lắc, di chuyển device.

Thực hiện đăng kí và hủy đăng kí sensor

 @Override
    protected void onResume() {
        super.onResume();
        mRotationSensor.register();
    }

    @Override
    protected void onPause() {
        super.onPause();
        mRotationSensor.unregister();
    }

Thực hiện quay camera khi cảm biến thay đổi

mRotationSensor.setOnDeviceRotationListener(new IOnDeviceRotationListener() {
            @Override
            public void onRotate(int bearing) {
                if (mMapStatus != MapStatus.COMPASS || mDeclination != 0.0f) {
                    return;
                }

                long currentTime = System.currentTimeMillis();
                if (currentTime - mOldTime > 150 && Math.abs(mBearing - bearing) > 1.2f) {
                    moveCameraBearing(18, bearing, 45);
                    if (mPositionMarker != null) {
                        mPositionMarker.setRotation(bearing);
                    }
                    mOldTime = currentTime;
                }

                mBearing = bearing;
            }
        });

Vì việc thay đổi sensor diễn ra liên tục dù người dùng có xoay thiết bị không đáng kể điều này dẫn đến hàm xoay camera sẽ được gọi rất nhiều lần làm cho map bị giật và hiệu ứng quay không được mượt nên mình đã kiểm tra nếu mức độ thay đổi của góc quay lớn hơn 1.2f mới thuc hiện di chuyển camera của bản đồ.

Hàm quay camera

 private void moveCameraBearing(float zoom, float bearing, float tilt) {
        if (mGoogleMap == null) {
            return;
        }
        mGoogleMap.getUiSettings().setCompassEnabled(true);
        CameraPosition cameraPosition = new CameraPosition.Builder().target(
                new LatLng(mCurrentLocation.getLatitude(), mCurrentLocation.getLongitude()))
                .zoom(zoom)
                .bearing(bearing)
                .tilt(tilt)
                .build();
        mGoogleMap.animateCamera(CameraUpdateFactory.newCameraPosition(cameraPosition), 150, null);

Ở trên mình có nêu ra một cách để thực hiện việc quay bản đồ giống chế độ compass của google tuy nhiên cách trên còn nhiều hạn chế về độ mượt mà của hiệu ứng quay chưa được tốt nên cũng rất mong mọi người có cách hay hơn hoặc cải thiện đuọc cách làm này tốt hơn. Xin cảm ơn.

Link source code: https://github.com/Nghicv/google-map-example