Карты, темы и дизайн

29 марта 2023
Карты, темы и дизайн

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

Приручение полилиний Google

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

Сами смещения вовсе не обязательно хранить и передавать в числовом виде. Если вы смотрели фильм «Марсианин», то там был эпизод передачи ASCII-символов числами. Обратный вариант также возможен. Смещения можно закодировать в виде ASCII-символов и существенно сэкономить место. Такое кодирование имеет потери, но обеспечивает разумную точность. Детали этого алгоритма доступны на портале для разработчиков Google.

На практике перед Flutter-разработчиками часто возникает задача декодирования строки полилинии и преобразования её в список координат. Из них потом можно отобразить маршрут на карте. Писать свою собственную реализацию никто не запрещает, но проще воспользоваться уже готовым плагином flutter_polyline_points_wrapper:

import 'package:flutter_polyline_points/flutter_polyline_points_wrapper.dart';

Есть два способа использования. Первый вернёт экземпляр PolylineResult, содержащий статус API, потенциальное сообщение об ошибке и список декодированных точек:

PolylinePoints polylinePoints = PolylinePoints();
PolylineResult result = await polylinePoints.getRouteBetweenCoordinates(googleAPiKey,
        _originLatitude, _originLongitude, _destLatitude, _destLongitude);
print(result.points);

Второй способ более простой и возвращает только результат расшифровки закодированной строки:

List<PointLatLng> result = polylinePoints.decodePolyline("_p~iF~ps|U_ulLnnqC_mqNvxq`@");
print(result);

Для корректной работы плагина нужен валидный ключ Google API.

Альтернатива Google Maps

Продолжая тему картографии отметим, что многие разработчики недолюбливают работать с картами Google во Flutter. На StackOverflow и Reddit эпизодически возникают соответствующие обсуждения. Полезной альтернативой, в этом случае, становятся карты проекта OpenStreetMap (OSM). Для их подключения можно использовать flutter_osm_plugin.

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

Подключение выполняется стандартно, через pubspec.yaml:

dependencies:
  flutter_osm_plugin: ^0.53.1

Вначале нужно создать базовую структуру: 

OSMFlutter( 
        controller:mapController,
        trackMyPosition: false,
        initZoom: 12,
        minZoomLevel: 8,
        maxZoomLevel: 14,
        stepZoom: 1.0,
        userLocationMarker: UserLocationMaker(
            personMarker: MarkerIcon(
                icon: Icon(
                    Icons.location_history_rounded,
                    color: Colors.red,
                    size: 48,
                ),
            ),
            directionArrowMarker: MarkerIcon(
                icon: Icon(
                    Icons.double_arrow,
                    size: 48,
                ),
            ),
        ),
         roadConfiguration: RoadOption(
                roadColor: Colors.yellowAccent,
        ),
        markerOption: MarkerOption(
            defaultMarker: MarkerIcon(
                icon: Icon(
                  Icons.person_pin_circle,
                  color: Colors.blue,
                  size: 56,
                  ),
                )
        ),
    );

Контроль за картой осуществляется через MapController:

MapController controller = MapController(
                            initMapWithUserPosition: false,
                            initPosition: GeoPoint(latitude: 47.4358055, longitude: 8.4737324),
                            areaLimit: BoundingBox( 
                                east: 10.4922941, 
                                north: 47.8084648, 
                                south: 45.817995, 
                                west:  5.9559113,
                            ),
            );
// or 

 MapController controller = MapController.withPosition(
                            initPosition: GeoPoint(
                              latitude: 47.4358055,
                               longitude: 8.4737324
                            ,),
                            areaLimit: BoundingBox( 
                                east: 10.4922941, 
                                north: 47.8084648, 
                                south: 45.817995, 
                                west:  5.9559113,
                            ),
            );

Репозиторий проекта содержит много примеров для выполнения действий над картой, например, зуммирование, установку текущей локации пользователя и расстановку меток. Единственное, о чём нужно позаботиться — копирайт. OpenStreetMaps бесплатен для использования, если выполняются требования Руководства по лицензированию. Так что авторы добавили виджет CopyrightOSMWidget, пример использования которого приведён в Issue #101.

Пара слов о темах

Создание персональной темы для приложения может быть сложным делом. Её стоит рассматривать как набор цветов и свойств для элементов пользовательского интерфейса. Пользователи привыкли, что выбранная ими системная тема влияет на все приложения, поэтому не игнорируйте сложившиеся принципы. 

Вот простой пример, как заставить виджет MaterialApp адаптироваться к настройкам устройства:

Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: ‘Flutter Theme demo’,
home: Screen1(),
darkTheme: ThemeData(
brightness: Brightness.dark,
),
theme: ThemeData(
brightness: Brightness.light,
),
themeMode: ThemeMode.system,
);
}

По умолчанию для всех приложений Flutter используется светлая тема. Но в то же время, в нём есть и значения для тёмной темы. Так что если нужно создать пользовательскую тему, потребуется переопределить некоторые значения. Встроенный механизм позволяет задать тему через свойство theme. Сами же стили и цвета указываются в объекте типа ThemeData.

Если возможностей штатного механизма недостаточно, можно создать расширение к теме, так называемый ThemeExtension. Вместе с абстрактными методами copyWith() и lerp() это даст больший контроль и структурированность кодовой базы. Примеры таких расширений есть в статье Flutter: Настройка тем приложения.

Неоднородные границы

Flutter предоставляет различные способы оформления границ элементов, такие как BoxDecoration, PhysicalModel и Material. Но все они воспринимают границу, как единый элемент, имеющий общую ширину. Если же нужно создать границу с разной шириной сторон, то можно использовать сторонний пакет Non-Uniform Border

Non-Uniform Border

Этот пакет предоставляет кастомный класс границы и может быть полезен, если требуется неоднородный стиль границ. Чтобы задействовать класс, нужно добавить пакет, как зависимость в pubspec.yaml:

dependencies:
  non_uniform_border: ^1.0.0

Теперь нужно создать экземпляр с требуемой шириной каждой стороны и цветом. Пример, приведённый в репозитории проекта, отлично демонстрирует, как это работает:

import 'package:non_uniform_border/non_uniform_border.dart';

// Create a non-uniform border with different widths and radius.
final shapeBorder = NonUniformBorder(
  leftWidth: 4,
  rightWidth: 8,
  topWidth: 12,
  bottomWidth: 16,
  color: Color(0xfffbbf24),
  side: BorderSide.strokeAlignCenter,
  borderRadius: BorderRadius.horizontal(
    left: Radius.circular(50),
    right: Radius.circular(100),
  ),
);

// Inside a Container.
Container(
  width: 400,
  height: 400,
  decoration: const ShapeDecoration(
    color: Color(0xffa3e635),
    shape: shapeBorder,
  ),
);

// Create a symmetrical border with different widths for each side.
NonUniformBorder.symmetrical(
  verticalWidth: 8,
  horizontalWidth: 4,
  color: Color(0xffec4899),
);

// Create an uniform border.
NonUniformBorder.all(
  width: 2,
  color: Color(0xff00ff00),
);

Non-Uniform Border является полезным дополнением к стандартным возможностям Flutter для создания границ и может помочь вам реализовать необычные дизайнерские решения для ваших приложений.

Митапы

Онлайн

Flutter Meetup

17 мая 2023

Весной у нас запланирован Flutter Meetup. Программа мероприятия формируется, но регистрация уже открыта. Кстати, вы уже можете подать доклад прямо в режиме онлайн.

Интересуетесь нашими мероприятиями? В Telegram-канале Evrone meetups мы выкладываем анонсы с подробными описаниями докладов, а также студийные записи прошедших митапов. Тем для кого выступать в новинку, мы оказываем всяческую поддержку и помогаем оформить экспертизу в яркое выступление. Подписывайтесь и пишите @andrew_aquariuss, чтобы узнать подробности.

Регистрация

Вакансии

Удаленка / Офис

Evrone 

Мы открыты для новых Flutter-разработчиков. В Evrone можно работать удалённо с первого дня, мы поддерживаем и оплачиваем участие в Open-source проектах, а расти в грейдах можно с помощью честной системы проверки навыков и менторства.

Подробнее

Подписаться
на Digest →
Важные новости и мероприятия без спама
Технологии которыми вы владеете и которые вам интересны
Ваш адрес электронной почты в безопасности - вот наша политика конфиденциальности.