Эта статья участвовала в "Проект «Звезда раскопок»”, чтобы выиграть творческий подарочный пакет и бросить вызов творческим поощрительным деньгам.
Предисловие: хотите ли вы создать собственный компонент карты, такой как Meituan? Хотите, чтобы программное обеспечение было таким же плавным, как Meituan? Хотите, чтобы зарплата была такой же высокой, как в Meituan? Уже день, хочешь помечтать? Всем привет, меня зовут Т. Спустя еще неделю, на этот раз я в основном использовал AutoNavi Map SDK для реализации пользовательской карты.Зачем мне это делать, потому что в Интернете слишком мало статей по обработке карт про Flutter, не говоря уже о пользовательской карте. слишком кратко, больно, сказать слишком много слез, поэтому я потратил на 5 дней меньше времени со своей девушкой, чтобы написать эту статью! !
Сначала о рендерах:
Есть много других мелких функций, которые не будут показаны, можете сами запустить и посмотреть~
Примечания к прочтению этой статьи:
1. Тестовый код должен использовать реальную машину, а симулятор не может загрузить карту (позиционировать можно, но карту загрузить нельзя, что может быть связано с версией)
2. Плагины, используемые в этой статье:
permission_handler: ^8.1.4 #权限管理
amap_flutter_map: 2.0.2 #高德地图
текст:
1. Подать заявку на получение ключа на платформе разработчика AutoNavi:
Шаг 1. Зарегистрируйте учетную запись разработчика
Адрес разработчика AutoNavi:console.amap.com/
Шаг 2. Создайте новое приложение и подайте заявку на получение ключа
третий шаг:
О том, как получить SHA1 и как бороться с ошибками при использовании позиционирования AutoNavi в этой статье (следующая статья, еще усердно работаю над кодированием слов 😭, ставьте лайк)
Четвертый шаг – получение ключа:
2. Обработать разрешение на использование карты Gaode
Шаг 1. Добавьте разрешения, необходимые для позиционирования
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<!--访问网络-->
<uses-permission android:name="android.permission.INTERNET" />
<!--粗略定位-->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<!--精确定位-->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<!--申请调用A-GPS模块-->
<uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS" />
<!--用于获取运营商信息,用于支持提供运营商信息相关的接口-->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<!--用于访问wifi网络信息,wifi信息会用于进行网络定位-->
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<!--用于获取wifi的获取权限,wifi信息会用来进行网络定位-->
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<!--用于读取手机当前的状态-->
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<!--用于写入缓存数据到扩展存储卡-->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
**Шаг 2:**Добавьте ключ, на который вы только что подали заявку, и добавьте подробное позиционирование разработки.
<meta-data
android:name="com.amap.api.v2.apikey"
android:value="自己的key" />
<service android:name="com.amap.api.location.APSService"/>
Шаг 3: Настройте некоторые файлы key.jks
Как сгенерировать key.jks в этой статье
Создайте key.properties после генерации
storePassword=你自己的密码
keyPassword=你自己的密码
keyAlias=key
storeFile=存放的位置(D:\\flutter_gaode_keystore\\key.jks)
Затем в build.gradle в папке приложения используйте:
Сначала найдите key.properties
def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('key.properties')
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
Затем укажите его в signingConfigs:
signingConfigs {
release {
keyAlias keystoreProperties['keyAlias']
keyPassword keystoreProperties['keyPassword']
storeFile file(keystoreProperties['storeFile'])
storePassword keystoreProperties['storePassword']
}
}
Шаг 4: Импортируйте карту
Все еще в build.gradle в папке приложения
dependencies {
implementation('com.amap.api:location:5.2.0')
implementation 'com.amap.api:3dmap:7.6.0'
implementation 'com.amap.api:search:5.0.0'
}
sourceSets {
//添加地图SDK引入路径
main {
jniLibs.srcDirs = ['libs']
}
}
В buildTypes и добавляем примерно две строчки:
buildTypes {
release {
signingConfig signingConfigs.release
minifyEnabled false //删除无用代码
shrinkResources false //删除无用资源
}
}
Это конфигурация~
3. Загрузите карту Гаоде
Следующая часть — это часть реализации кода, показывающая только основной код🐷
Шаг 1. Добавьте примененный ключ
class ConstConfig {
///配置申请的apikey,在此处配置之后,可以在初始化[AMapWidget]时,通过`apiKey`属性设置
///
///注意:使用[AMapWidget]的`apiKey`属性设置的key的优先级高于通过Native配置key的优先级,
///使用[AMapWidget]的`apiKey`属性配置后Native配置的key将失效,请根据实际情况选择使用
static const AMapApiKey amapApiKeys = AMapApiKey(
androidKey: '你自己申请的key',
iosKey: '没有申请ios的话先填android的key,这样也可以在安卓手机测试');
}
Шаг 2. Определите тип карты
//地图类型
MapType _mapType;
final Map<String, MapType> _radioValueMap = {
'普通地图': MapType.normal,
'卫星地图': MapType.satellite,
'导航地图': MapType.navi,
'公交地图': MapType.bus,
'黑夜模式': MapType.night,
};
Инициализируйте карту:
@override
void initState() {
super.initState();
//默认为普通地图
_mapType = MapType.normal;
}
Создайте карту:
//创建地图
final AMapWidget map = AMapWidget(
apiKey: ConstConfig.amapApiKeys,
//地图类型属性
mapType: _mapType ?? MapType.normal,
);
Шаг третий: Показать карту
AMapRadioGroup(
groupLabel: '地图样式',
groupValue: _mapType,
radioValueMap: _radioValueMap,
onChanged: (value) => {
//改变当前地图样式为选中的样式
setState(() {
_mapType = value;
})
},
)
Шаг 4: Адрес по умолчанию
//默认显示在北京天安门
static final CameraPosition _kInitialPosition = const CameraPosition(
target: LatLng(39.909187, 116.397451),
zoom: 10.0,
);
4. Настройте карту
Шаг 1: Загрузите стиль пользовательской карты
//用于记录是否为自定义地图
bool _mapCreated = false;
//加载自定义地图样式
void _loadCustomData() async {
if (null == _customStyleOptions) {
_customStyleOptions = CustomStyleOptions(false);
}
ByteData styleByteData = await rootBundle.load('assets/style.data');
_customStyleOptions.styleData = styleByteData.buffer.asUint8List();
ByteData styleExtraByteData =
await rootBundle.load('assets/style_extra.data');
_customStyleOptions.styleExtraData =
styleExtraByteData.buffer.asUint8List();
//如果需要加载完成后直接展示自定义地图,可以通过setState修改CustomStyleOptions的enable为true
setState(() {
_customStyleOptions.enabled = true;
});
}
Шаг 2: Определите карту
final AMapWidget map = AMapWidget(
apiKey: ConstConfig.amapApiKeys,
onMapCreated: onMapCreated,
customStyleOptions: _customStyleOptions,
);
void onMapCreated(AMapController controller) {
if (null != controller) {
_mapCreated = true;
}
}
Шаг 3: Нажмите, чтобы переключиться на пользовательскую карту
AMapSwitchButton(
label: Text(
'自定义地图',
style: TextStyle(color: Colors.white),
),
defaultValue: _customStyleOptions.enabled,
onSwitchChanged: (value) => {
if (_mapCreated)
{
setState(() {
_customStyleOptions.enabled = value;
})
}
},
)
5. Пусть курьер летает
Шаг 1. Получите собственное изображение
Есть три способа:
Первый:
///通过BitmapDescriptor.fromAssetImage的方式获取图片
Future<void> _createMarkerImageFromAsset(BuildContext context) async {
if (_markerIcon == null) {
final ImageConfiguration imageConfiguration =
createLocalImageConfiguration(context);
BitmapDescriptor.fromAssetImage(imageConfiguration, 'assets/start.png')
.then(_updateBitmap);
}
}
Второй:
///通过BitmapDescriptor.fromBytes的方式获取图片
Future<void> _createMarkerImageFromBytes(BuildContext context) async {
final Completer<BitmapDescriptor> bitmapIcon =
Completer<BitmapDescriptor>();
final ImageConfiguration config = createLocalImageConfiguration(context);
const AssetImage('assets/end.png')
.resolve(config)
.addListener(ImageStreamListener((ImageInfo image, bool sync) async {
final ByteData bytes =
await image.image.toByteData(format: ImageByteFormat.png);
final BitmapDescriptor bitmap =
BitmapDescriptor.fromBytes(bytes.buffer.asUint8List());
bitmapIcon.complete(bitmap);
}));
bitmapIcon.future.then((value) => _updateBitmap(value));
}
Третий (самый простой):
//最简单的方式
if (null == _markerIcon) {
_markerIcon = BitmapDescriptor.fromIconPath('assets/location_marker.png');
}
Шаг 2: Рассчитайте положение отображаемого изображения по осям x и y.
void _changeAnchor() {
final Marker marker = _markers[selectedMarkerId];
if (marker == null) {
return;
}
final Offset currentAnchor = marker.anchor;
double dx = 0;
double dy = 0;
if (currentAnchor.dx < 1) {
dx = currentAnchor.dx + 0.1;
} else {
dx = 0;
}
if (currentAnchor.dy < 1) {
dy = currentAnchor.dy + 0.1;
} else {
dy = 0;
}
final Offset newAnchor = Offset(dx, dy);
setState(() {
_markers[selectedMarkerId] = marker.copyWith(
anchorParam: newAnchor,
);
});
}
Таким образом, на карте может отображаться брат на вынос, а также некоторые другие мелкие функции.
Изменить положение изображения:
void _changePosition() {
final Marker marker = _markers[selectedMarkerId];
final LatLng current = marker.position;
final Offset offset = Offset(
mapCenter.latitude - current.latitude,
mapCenter.longitude - current.longitude,
);
setState(() {
_markers[selectedMarkerId] = marker.copyWith(
positionParam: LatLng(
mapCenter.latitude + offset.dy,
mapCenter.longitude + offset.dx,
),
);
});
}
Future<void> _changeAlpha() async {
final Marker marker = _markers[selectedMarkerId];
final double current = marker.alpha;
setState(() {
_markers[selectedMarkerId] = marker.copyWith(
alphaParam: current < 0.1 ? 1.0 : current * 0.75,
);
});
}
Измените угол изображения:
Future<void> _changeRotation() async {
final Marker marker = _markers[selectedMarkerId];
final double current = marker.rotation;
setState(() {
_markers[selectedMarkerId] = marker.copyWith(
rotationParam: current == 330.0 ? 0.0 : current + 30.0,
);
});
}
6. Добавьте несколько значков на карту
1. Определите маркер
static final LatLng mapCenter = const LatLng(39.909187, 116.397451);
//需要先设置一个空的map赋值给AMapWidget的markers,否则后续无法添加marker
final Map<String, Marker> _initMarkerMap = <String, Marker>{};
2. Увеличьте маркер
добавить один:
void _addMarker() {
final _markerPosition =
LatLng(_currentLatLng.latitude, _currentLatLng.longitude + 2 / 1000);
final Marker marker = Marker(
position: _markerPosition,
//使用默认hue的方式设置Marker的图标
icon: BitmapDescriptor.defaultMarkerWithHue(BitmapDescriptor.hueOrange),
);
//调用setState触发AMapWidget的更新,从而完成marker的添加
setState(() {
_currentLatLng = _markerPosition;
//将新的marker添加到map里
_markers[marker.id] = marker;
});
}
Добавить несколько:
for(int i=0; i< 10; i++) {
LatLng position = LatLng(
mapCenter.latitude + sin(i * pi / 12.0) / 20.0,
mapCenter.longitude + cos(i * pi / 12.0) / 20.0);
Marker marker = Marker(position: position);
_initMarkerMap[marker.id] = marker;
}
7. Вспомогательный тост
Инкапсулирует тост и добавляет его на экран через OverlayEntry
Покажите его основной код:
if (_overlayEntry == null) {
//OverlayEntry负责构建布局
//通过OverlayEntry将构建的布局插入到整个布局的最上层
_overlayEntry = OverlayEntry(
builder: (BuildContext context) => Positioned(
//top值,可以改变这个值来改变toast在屏幕中的位置
top: buildToastPosition(context),
child: Container(
alignment: Alignment.center,
width: MediaQuery.of(context).size.width,
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 40.0),
child: AnimatedOpacity(
opacity: _showing ? 1.0 : 0.0, //目标透明度
duration: _showing
? Duration(milliseconds: 100)
: Duration(milliseconds: 400),
child: _buildToastWidget(),
),
)),
));
//插入到整个布局的最上层
overlayState.insert(_overlayEntry);
} else {
//重新绘制UI,类似setState
_overlayEntry.markNeedsBuild();
}
Основной код чертежа:
//toast绘制
static _buildToastWidget() {
return Center(
child: Card(
color: _bgColor,
child: Padding(
padding: EdgeInsets.symmetric(
horizontal: _pdHorizontal, vertical: _pdVertical),
child: Text(
_msg,
style: TextStyle(
fontSize: _textSize,
color: _textColor,
),
),
),
),
);
}
Установите местоположение тоста:
// 设置toast位置
static buildToastPosition(context) {
var backResult;
if (_toastPosition == ToastPostion.top) {
backResult = MediaQuery.of(context).size.height * 1 / 4;
} else if (_toastPosition == ToastPostion.center) {
backResult = MediaQuery.of(context).size.height * 2 / 5;
} else {
backResult = MediaQuery.of(context).size.height * 3 / 4;
}
return backResult;
}
Таким образом объясняются основные функции, а в исходном коде написаны и другие функции, такие как отображение состояния дорог, ограничение размера карты и т. д.
На этом статья окончена, пожалуйста, ставьте палец вверх, когда увидите здесь братьев, моя девушка уже разрешила мне поспать у двери 😭
(Слишком долго, чтобы игнорировать то, как она уговаривает...) Расскажите младшему брату в комментариях 😭