Поиск по этому блогу

четверг, 28 декабря 2017 г.

Рисование полигона на GoogleMap в виде круга для выделения области карты

Наверняка многие сталкивались с такой штукой как рисование на карте, может не в разработке, но в использовании каких либо приложений, их просто куча. Так вот заказчики бывает насмотрятся на такие приложения и потом хотят себе такое же, а ты сидишь такой, не знаешь за что хвататься, а тут еще и эта фича которую ты вообще без понятия как реализовывать. По этому это маленькая заметка будет о том как рисовать радиус на карте, сохранять список выделенных координат и дальше что-то с ними делать, отправлять на сервер, или же расчитывать какие-то параметры, это уже как говорится на выбор разработчика.

Все как бы на много проще чем кажется, по сути вся эта статья будет состоять из одного класса, очень не привычно для моего блога, так как я обычно у меня проект если состоит из какого-то кода, то это куча классов и методов которые что-то делают, но сегодня будет не так.

image

В общем в нашем MainActivity нам нужно будет подключить карту, и после этого отследить onTouch событие когда пользователь будет рисовать на карте, а потом что бы после того как будет нарисован какой-то круг, вся область за этим кругом была затемнена, а сам круг был обычного цвета карты, что бы был акцент именно на этой области. В общем как на скриншоте. По этому давайте подключим все библиотеки, у нас это ButterKnife и maps api.

app/build.gradle
dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:26.1.0'

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

    compile 'com.jakewharton:butterknife:8.8.1'
    annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'
}

ButterKnife используем для удобства подключения вьюх и остальных ресурсов в проект. Карты очевидно используем для отображения карт :)

Дальше нам нужно нарисовать нашу разметку, в ней у нас будет карта и фрейм леяут поверх нее.

activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent" >

    <fragment
        android:id="@+id/map"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        class="com.google.android.gms.maps.SupportMapFragment" />

    <FrameLayout
        android:id="@+id/fram_map"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" >

        <Button
            android:id="@+id/drawBtn"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="onDrawClick"
            android:text="Free Draw" />
    </FrameLayout>

</FrameLayout>

Как я и говорил, у нас будет карта, она будет у нас основным объектом на экране, поверх у нас FrameLayout в котором кнопка по нажатию на которую мы будем чистить карту и потом рисован на ней.

MainActivity.java
import android.graphics.Point;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;
import android.widget.FrameLayout;

import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.OnMapReadyCallback;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.Polygon;
import com.google.android.gms.maps.model.PolygonOptions;
import com.google.android.gms.maps.model.PolylineOptions;

import java.util.ArrayList;
import java.util.List;

import butterknife.BindColor;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;

public class MainActivity extends AppCompatActivity implements OnMapReadyCallback, View.OnTouchListener {

    private GoogleMap googleMap;

    private int source = 0;
    private int destination = 1;

    private boolean isMapMoveable = false;
    private boolean screenLeave = false;

    private ArrayList<LatLng> latLngArrayList = new ArrayList<>();

    @BindView(R.id.fram_map)
    FrameLayout framMap;
    @BindView(R.id.drawBtn)
    Button drawBtn;
    @BindColor(R.color.colorPrimary)
    int colorPrimary;
    @BindColor(R.color.transparentGray)
    int transparentGray;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);

        SupportMapFragment customMapFragment = ((SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map));
        customMapFragment.getMapAsync(this);
    }

    @OnClick(R.id.drawBtn)
    public void onDrawClick() {
        isMapMoveable = true;
        drawBtn.setVisibility(View.GONE);
        latLngArrayList.removeAll(latLngArrayList);
        googleMap.clear();
    }

    public void drawMap() {
        if (latLngArrayList.size() > 1) {
            googleMap.addPolyline(new PolylineOptions().add(
                    latLngArrayList.get(source),
                    latLngArrayList.get(destination))
                    .width(20)
                    .color(colorPrimary)
            );
            source++;
            destination++;
        }
    }

    private List<LatLng> createOuterBounds() {
        final float delta = 0.01f;

        return new ArrayList<LatLng>() {{
            add(new LatLng(90 - delta, -180 + delta));
            add(new LatLng(0, -180 + delta));
            add(new LatLng(-90 + delta, -180 + delta));
            add(new LatLng(-90 + delta, 0));
            add(new LatLng(-90 + delta, 180 - delta));
            add(new LatLng(0, 180 - delta));
            add(new LatLng(90 - delta, 180 - delta));
            add(new LatLng(90 - delta, 0));
            add(new LatLng(90 - delta, -180 + delta));
        }};
    }


    private void drawFinalPolygon() {
        latLngArrayList.add(latLngArrayList.get(0));

        PolygonOptions polygonOptions = new PolygonOptions();
        polygonOptions.fillColor(transparentGray);
        polygonOptions.addAll(createOuterBounds());
        polygonOptions.strokeColor(colorPrimary);
        polygonOptions.strokeWidth(20);
        polygonOptions.addHole(latLngArrayList);

        Polygon polygon = googleMap.addPolygon(polygonOptions);

        for(LatLng latLng : polygon.getPoints()) {
            Log.e("latitude", "" + latLng.latitude);
            Log.e("longitude", "" + latLng.longitude);
        }
    }

    @Override
    public void onMapReady(final GoogleMap googleMap) {
        this.googleMap = googleMap;
        framMap.setOnTouchListener(this);
    }

    @Override
    public boolean onTouch(View view, MotionEvent event) {
        if (isMapMoveable) {
            Point point = new Point(Math.round(event.getX()), Math.round(event.getY()));
            LatLng latLng = googleMap.getProjection().fromScreenLocation(point);
            double latitude = latLng.latitude;
            double longitude = latLng.longitude;

            int eventaction = event.getAction();
            switch (eventaction) {
                case MotionEvent.ACTION_DOWN:
                    screenLeave = false;
                case MotionEvent.ACTION_MOVE:
                    latLngArrayList.add(new LatLng(latitude, longitude));
                    screenLeave = false;
                    drawMap();
                case MotionEvent.ACTION_UP:
                    if (!screenLeave) {
                        screenLeave = true;
                    } else {
                        isMapMoveable = false;
                        source = 0;
                        destination = 1;
                        drawBtn.setVisibility(View.VISIBLE);
                        drawFinalPolygon();
                    }
                    break;
                default:
                    break;
            }

            if (isMapMoveable) {
                return true;
            } else {
                return false;
            }
        } else {
            return false;
        }
    }
}

Рассмотрим все по порядку. В самом верху класса мы определили все переменные и ресурсы типа карты, GoogleMap и ArrayList в который мы будем сохранять наши координаты. 
Дальше в методе onCreate() мы подключили SupportMapFragment и ButterKnife. 
Метод onDrawClick() обрабатывает клик по кнопке, чистит карту делает карту рисовабельной и удаляет все из списка.
drawMap() — метод который добавляет полигоны на карту по ходу дела рисования пальцем на карте.
createOuterBounds() — метод который зарисовывает всю область вокруг нарисованного круга.
drawFinalPolygon() — очевидно рисует финальную версию полигона на карте по координатам которые мы сохранили в ArrayList и заканчивает круг в начальную точку с которой он начинался. Ну и дальше по желанию распечатывает в лог координаты которые были использованы на карте.
onMapReady() — метод класса GoogleMap, он вызывается когда карта готова к использованию.
onTouch() — метод в котором происходит вся магия, в начале мы проверяем если карта готова к началу рисования, то есть проверяется isMapMoveable, если да, тогда мы начинаем отслеживать нажатие на экран и отслеживать координаты на карте. По окончанию когда мы отпускаем карту по событию MotionEvent.ACTION_UP мы делаем isMapMoveable = false для того что бы мы снова могли двигать картой. И делаем кнопку снова видимой для очистки экрана и новой возможности для рисования.

Не забываем что нам нужно в AndroidManifest прописать доступ в интернет и мета-данные для работы с картой.

AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="project.dajver.com.drawgesturesmap">

    <uses-permission android:name="android.permission.INTERNET" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <meta-data
            android:name="com.google.android.geo.API_KEY"
            android:value="@string/google.maps.api.key"/>

    </application>

</manifest>

Исходники:
GitHub