Статьи

Начало работы с Google Maps для Android: средний уровень

Одна из самых полезных функций для пользователей — интеграция карт. В предыдущей части этой серии мы обсуждали, как настроить Google Maps для Android с помощью консоли разработчика Google и как создать основной фрагмент Google Maps. Затем мы добавили различные виды маркеров и способы рисования на карте.

В этом учебном пособии вы расширите то, что узнали в предыдущей статье, чтобы расположить представления поверх карты, переопределить элементы управления селектором внутреннего уровня и добавить компонент «Просмотр улиц» в свои приложения. Исходный код этой статьи можно найти на GitHub.

Для начала выполните шаги, перечисленные в предыдущей статье этой серии , чтобы создать базовый проект с использованием MapFragment , присоединить его к Activity и активировать API Карт Google через Консоль разработчика Google . Для этого урока вам не нужно использовать местоположения классов Play Services, но вам нужно импортировать карты библиотеки Play Services в ваш узел dependencies build.gradle .

1
2
3
4
5
dependencies {
    compile fileTree(dir: ‘libs’, include: [‘*.jar’])
    compile ‘com.android.support:appcompat-v7:23.0.0’
    compile ‘com.google.android.gms:play-services-maps:7.8.0’
}

Как только это будет сделано, вы получите экран, который выглядит следующим образом:

Начальный вид карты

Далее вам нужно настроить камеру. В этом уроке мы сосредоточимся на Мэдисон Сквер Гарден в Нью-Йорке, потому что это отличный пример здания, использующего карты уровней внутри помещений.

В onViewCreated вы можете добавить вызов следующего вспомогательного метода initCamera . Возможно, вы помните, что нам нужно подождать, пока onViewCreated будет работать с Google Maps, потому что именно тогда мы знаем, что объект карты готов к использованию.

01
02
03
04
05
06
07
08
09
10
11
12
private void initCamera() {
    CameraPosition position = CameraPosition.builder()
            .target( new LatLng( 40.7506, -73.9936 ) )
            .zoom( 18f )
            .bearing( 0.0f )
            .tilt( 0.0f )
            .build();
 
    getMap().animateCamera(
        CameraUpdateFactory.newCameraPosition( position ), null );
    getMap().setMapType( GoogleMap.MAP_TYPE_HYBRID );
}

Вышеупомянутый метод перемещает камеру к нашей цели и увеличивает ее достаточно близко, чтобы селектор в помещении стал видимым. Вы заметите, что на правой стороне экрана есть полоса чисел и наложение на карте для каждого этажа. Когда вы выбираете другой уровень справа, текущий план этажа анимируется на новый. Это функция, с которой вы будете работать позже, чтобы иметь собственный выбор уровня управления просмотром.

Пример стандартного селектора уровня в помещении

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

  • GoogleMap.OnIndoorStateChangeListener используется для определения того, когда селектор уровня в помещении изменил видимость.
  • SeekBar.OnSeekBarChangeListener используется с одним из наших оверлеев вида для управления выбором уровня, а не с использованием набора кнопок по умолчанию справа.
  • GoogleMap.OnMapLongClickListener используется в этом примере для изменения отображаемого местоположения вашего компонента Просмотр улиц.
1
2
3
4
public class MapFragment extends SupportMapFragment implements
       GoogleMap.OnIndoorStateChangeListener,
       GoogleMap.OnMapLongClickListener,
       SeekBar.OnSeekBarChangeListener {

После того, как вы добавили необходимые методы для этих трех интерфейсов, вы можете начать добавлять виды в верхней части карты.

Несмотря на то, что базовые функции Карт Google соответствуют большинству потребностей, в некоторые моменты вы захотите добавить дополнительные виды на карту для выполнения действий. Для этого урока мы добавим SeekBar и некоторые объекты TextView , чтобы настроить элементы управления для селектора уровня внутри помещения.

Начните с создания нового XML-файла макета view_map_overlay.xml . Добавьте следующий код, чтобы создать базовый макет, который будет использоваться на экране.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<?xml version=»1.0″ encoding=»utf-8″?>
<RelativeLayout xmlns:android=»http://schemas.android.com/apk/res/android»
    android:layout_width=»match_parent»
    android:layout_height=»match_parent»>
 
    <LinearLayout
        android:layout_width=»match_parent»
        android:layout_height=»wrap_content»>
         
        <TextView
            android:id=»@+id/indoor_min_level»
            android:text=»0″
            android:layout_width=»0dp»
            android:layout_height=»wrap_content»
            android:layout_weight=»1″
            android:padding=»4dp»
            android:textSize=»20sp»
            android:gravity=»center»
            android:textColor=»@android:color/white» />
 
        <SeekBar
            android:id=»@+id/indoor_level_selector»
            android:layout_width=»0dp»
            android:layout_height=»wrap_content»
            android:layout_weight=»8″ />
        
       <TextView
            android:id=»@+id/indoor_max_level»
            android:text=»10″
            android:layout_width=»0dp»
            android:layout_height=»wrap_content»
            android:layout_weight=»1″
            android:padding=»4dp»
            android:textSize=»20sp»
            android:textColor=»@android:color/white»
            android:gravity=»center» />
             
    </LinearLayout>
     
</RelativeLayout>

Как только ваш файл макета завершен, вы можете добавить его в качестве наложения на фрагмент карты. В onCreateView вам необходимо получить доступ к родительскому ViewGroup , накачать новое наложение макета и прикрепить его к родительскому ViewGroup . Здесь также сохраняются ссылки на каждое из представлений в оверлее, чтобы они могли быть изменены позже в вашем приложении.

01
02
03
04
05
06
07
08
09
10
11
12
13
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    ViewGroup parent = (ViewGroup) super.onCreateView( inflater, container, savedInstanceState );
    View overlay = inflater.inflate( R.layout.view_map_overlay, parent, false );
 
    mIndoorSelector = (SeekBar) overlay.findViewById( R.id.indoor_level_selector );
    mIndoorMinLevel = (TextView) overlay.findViewById( R.id.indoor_min_level );
    mIndoorMaxLevel = (TextView) overlay.findViewById( R.id.indoor_max_level );
 
    parent.addView( overlay );
 
    return parent;
}

Когда вы запустите приложение, вы должны увидеть свои виды в верхней части карты. Однако вы также по-прежнему увидите селектор уровня по умолчанию, который загромождает вид.

Наложение перед удалением стандартных переключателей уровня в помещении

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

1
2
3
4
5
6
private void initMapIndoorSelector() {
    mIndoorSelector.setOnSeekBarChangeListener( this );
 
    getMap().getUiSettings().setIndoorLevelPickerEnabled( false );
    getMap().setOnIndoorStateChangeListener( this );
}

Теперь, когда ваш вид наложен на карту, вы должны скрывать его, пока он не понадобится. В onViewCreated вызовите новый вспомогательный метод с именем hideFloorLevelSelector который скрывает все ваши наложенные представления.

1
2
3
4
5
private void hideFloorLevelSelector() {
    mIndoorSelector.setVisibility( View.GONE );
    mIndoorMaxLevel.setVisibility( View.GONE );
    mIndoorMinLevel.setVisibility( View.GONE );
}

Создав и скрыв свои виды, вы можете начать добавлять их в логику, чтобы они при необходимости отображались и взаимодействовали с картой. Ранее вы создали метод onIndoorBuildingFocused как часть GoogleMap.OnIndoorStateChangeListener . В этом методе вам нужно сохранить ссылку на то, какое здание находится в фокусе, а затем при необходимости скрыть или показать SeekBar управления SeekBar .

01
02
03
04
05
06
07
08
09
10
11
@Override
public void onIndoorBuildingFocused() {
    mIndoorBuilding = getMap().getFocusedBuilding();
 
    if( mIndoorBuilding == null || mIndoorBuilding.getLevels() == null || mIndoorBuilding.getLevels().size() <= 1 ) {
        hideFloorLevelSelector();
    } else {
        showFloorLevelSelector();
    }
 
}

Внутреннее здание получит фокус, когда здание будет видно камерой камеры, и карта будет достаточно увеличена. Если эти условия больше не выполняются, этот метод будет вызван снова и getMap().getFocusedBuilding вернет null значение.

showFloorLevelSelector делает showFloorLevelSelector все наложенные виды, перемещает SeekBar к правильному выбранному значению и устанавливает для текстовых меток значения, представляющие краткое имя верхнего и нижнего этажей для этого здания. Когда вы извлекаете уровни из объекта IndoorBuilding , нижний этаж является последним элементом в списке, а верхний этаж находится в позиции 0 .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
private void showFloorLevelSelector() {
    if( mIndoorBuilding == null )
        return;
 
    int numOfLevels = mIndoorBuilding.getLevels().size();
 
    mIndoorSelector.setMax( numOfLevels — 1 );
 
    //Bottom floor is the last item in the list, top floor is the first
    mIndoorMaxLevel.setText( mIndoorBuilding.getLevels().get( 0 ).getShortName() );
    mIndoorMinLevel.setText( mIndoorBuilding.getLevels().get( numOfLevels — 1 ).getShortName() );
 
    mIndoorSelector.setProgress( mIndoorBuilding.getActiveLevelIndex() );
 
    mIndoorSelector.setVisibility( View.VISIBLE );
    mIndoorMaxLevel.setVisibility( View.VISIBLE );
    mIndoorMinLevel.setVisibility( View.VISIBLE );
 
}

Последний метод, который необходимо реализовать для селектора уровня внутри помещения, — это onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) . Когда позиция SeekBar изменяется, вам нужно активировать новый уровень в текущем здании. Поскольку уровни упорядочены сверху вниз, вам нужно активировать уровень в позиции numOfLevels - 1 - progress , чтобы соотнести его с позицией SeekBar .

1
2
3
4
5
6
7
8
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean b) {
    if( mIndoorBuilding == null )
        return;
 
    int numOfLevels = mIndoorBuilding.getLevels().size();
    mIndoorBuilding.getLevels().get( numOfLevels — 1 — progress ).activate();
}

Теперь, когда вы знаете, как накладывать виды на карту и как работать с селектором уровня в помещении, давайте рассмотрим, как работать с Street View в ваших приложениях. Просмотр улиц, как и Google Maps, позволяет использовать фрагмент или вид. В этом примере вы будете использовать StreetViewPanoramaView и наложить его на свой MapFragment .

Это представление будет инициализировано, чтобы показать улицу рядом с Мэдисон-Сквер-Гарден, и при длительном нажатии на другую область карты в режиме просмотра улиц будут отображаться изображения, связанные с выбранной позицией. Если вы выберете отображение области, которая не связана напрямую с изображением Street View, Google выберет ближайший для отображения, если он находится в пределах установленного расстояния. Если поблизости нет изображений Street View (скажем, вы выбрали место в середине океана), то Street View покажет черный экран.

Что еще нужно знать, это то, что вы можете иметь только один StreetViewPanoramaView или фрагмент, видимый пользователю одновременно.

Просмотр улиц на Мэдисон Сквер Гарден

Для начала обновите view_map_overlay.xml , чтобы добавить StreetViewPanoramaView .

1
2
3
4
5
<com.google.android.gms.maps.StreetViewPanoramaView
   android:id=»@+id/steet_view_panorama»
   android:layout_width=»match_parent»
   android:layout_height=»240dp»
   android:layout_alignParentBottom=»true»/>

Когда ваш файл макета будет готов, перейдите в onCreateView в вашем MapFragment , сохраните ссылку на ваш новый вид и вызовите метод onCreate для этого представления. Важно, чтобы вы вызывали onCreate , потому что текущий фрагмент onCreate уже был вызван до того, как этот вид был прикреплен, а компонент Street View выполняет в onCreate действия, необходимые для инициализации.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    ViewGroup parent = (ViewGroup) super.onCreateView( inflater, container, savedInstanceState );
    View overlay = inflater.inflate( R.layout.view_map_overlay, parent, false );
 
    mIndoorSelector = (SeekBar) overlay.findViewById( R.id.indoor_level_selector );
    mIndoorMinLevel = (TextView) overlay.findViewById( R.id.indoor_min_level );
    mIndoorMaxLevel = (TextView) overlay.findViewById( R.id.indoor_max_level );
 
    mStreetViewPanoramaView = (StreetViewPanoramaView) overlay.findViewById(R.id.steet_view_panorama);
    mStreetViewPanoramaView.onCreate(savedInstanceState);
 
    parent.addView(overlay);
 
    return parent;
}

Затем в onViewCreated добавьте новый метод initStreetView . Этот новый метод будет асинхронно получать объект StreetViewPanorama когда он будет готов, и обрабатывать, показывая начальную позицию Street View. Важно отметить, что getStreetViewPanoramaAsync( OnStreetViewPanoramaReadyCallback callback ) может вызываться только из основного потока.

01
02
03
04
05
06
07
08
09
10
11
private void initStreetView() {
    getMap().setOnMapLongClickListener( this );
 
    mStreetViewPanoramaView.getStreetViewPanoramaAsync(new OnStreetViewPanoramaReadyCallback() {
        @Override
        public void onStreetViewPanoramaReady(StreetViewPanorama panorama) {
            mPanorama = panorama;
            showStreetView( new LatLng( 40.7506, -73.9936 ) );
        }
    });
}

Наконец, вам нужно определить вспомогательный метод showStreetView( LatLng latlng ) показанный выше. Этот метод создает объект StreetViewPanoramaCamera который позволяет изменять наклон, масштаб и ориентацию камеры Street View. В этом примере для камеры установлены значения по умолчанию.

Далее вам нужно установить положение камеры. В этом примере мы также включаем необязательный параметр для отображения названий улиц.

01
02
03
04
05
06
07
08
09
10
11
12
13
private void showStreetView( LatLng latLng ) {
    if( mPanorama == null )
        return;
 
    StreetViewPanoramaCamera.Builder builder = new StreetViewPanoramaCamera.Builder( mPanorama.getPanoramaCamera() );
    builder.tilt( 0.0f );
    builder.zoom( 0.0f );
    builder.bearing( 0.0f );
    mPanorama.animateTo( builder.build(), 0 );
     
    mPanorama.setPosition( latLng, 300 );
    mPanorama.setStreetNamesEnabled( true );
}

Как только ваш showStreetView( LatLng latlng ) завершен, его также можно вызывать из onMapLongClick(LatLng latLng) чтобы вы могли легко изменить отображаемую область.

1
2
3
4
@Override
public void onMapLongClick(LatLng latLng) {
    showStreetView( latLng );
}
Местоположение просмотра улиц изменено на onMapLongClickLatLng latLng

В этом руководстве вы узнали о некоторых продвинутых способах взаимодействия с MapFragment Google, добавив дополнительные виды в MapFragment и узнали, как управлять селектором уровня внутреннего здания. Мы также рассмотрели основы добавления функции Street View в ваше приложение, чтобы отобразить другую точку зрения для ваших пользователей.

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