Карты, темы и дизайн
В этом выпуске мы декодируем строку полилинии и узнаем, что можно использовать в качестве замены 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.
Этот пакет предоставляет кастомный класс границы и может быть полезен, если требуется неоднородный стиль границ. Чтобы задействовать класс, нужно добавить пакет, как зависимость в 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 проектах, а расти в грейдах можно с помощью честной системы проверки навыков и менторства.