amap_map_fluttify

This commit is contained in:
2024-11-17 15:45:43 +08:00
commit ee80f75473
554 changed files with 220726 additions and 0 deletions

View File

@ -0,0 +1,29 @@
import 'package:flutter/material.dart';
import 'package:permission_handler/permission_handler.dart';
import 'location_picker.widget.dart';
class LocationPickerScreen extends StatefulWidget {
@override
_LocationPickerScreenState createState() => _LocationPickerScreenState();
}
class _LocationPickerScreenState extends State<LocationPickerScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(
// body: LocationPicker(
// requestPermission: () {
// return Permission.location.request().then((it) => it.isGranted);
// },
// poiItemBuilder: (poi, selected) {
// return ListTile(
// title: Text(poi.title!),
// subtitle: Text(poi.address!),
// trailing: selected ? Icon(Icons.check) : SizedBox.shrink(),
// );
// },
// ),
);
}
}

View File

@ -0,0 +1,288 @@
import 'dart:async';
import 'package:amap_map_fluttify/amap_map_fluttify.dart';
import 'package:flutter/material.dart';
import 'package:flutter_easyrefresh/easy_refresh.dart';
import 'package:flutter_easyrefresh/material_footer.dart';
import 'package:sliding_up_panel/sliding_up_panel.dart';
import 'models.dart';
const _iconSize = 50.0;
const _package = 'amap_all_fluttify';
const _indicator = 'images/indicator.png';
double _fabHeight = 16.0;
typedef RequestPermission = Future<bool> Function();
typedef PoiItemBuilder = Widget Function(Poi poi, bool selected);
class LocationPicker extends StatefulWidget {
const LocationPicker({
required Key key,
required this.requestPermission,
required this.poiItemBuilder,
this.zoomLevel = 16.0,
this.zoomGesturesEnabled = false,
this.showZoomControl = false,
required this.centerIndicator,
this.enableLoadMore = true,
required this.onItemSelected,
}) : assert(zoomLevel >= 3 && zoomLevel <= 19),
super(key: key);
/// 请求权限回调
final RequestPermission requestPermission;
/// Poi列表项Builder
final PoiItemBuilder poiItemBuilder;
/// 显示的缩放登记
final double zoomLevel;
/// 缩放手势使能 默认false
final bool zoomGesturesEnabled;
/// 是否显示缩放控件 默认false
final bool showZoomControl;
/// 地图中心指示器
final Widget centerIndicator;
/// 是否开启加载更多
final bool enableLoadMore;
/// 选中回调
final ValueChanged<PoiInfo> onItemSelected;
@override
_LocationPickerState createState() => _LocationPickerState();
}
class _LocationPickerState extends State<LocationPicker>
with SingleTickerProviderStateMixin, _BLoCMixin, _AnimationMixin {
// 地图控制器
late AmapController _controller;
final PanelController _panelController = PanelController();
// 是否用户手势移动地图
bool _moveByUser = true;
// 当前请求到的poi列表
List<PoiInfo> _poiInfoList = [];
// 当前地图中心点
late LatLng _currentCenterCoordinate;
// 页数
int _page = 1;
@override
Widget build(BuildContext context) {
final minPanelHeight = MediaQuery.of(context).size.height * 0.4;
final maxPanelHeight = MediaQuery.of(context).size.height * 0.7;
return SlidingUpPanel(
controller: _panelController,
parallaxEnabled: true,
parallaxOffset: 0.5,
minHeight: minPanelHeight,
maxHeight: maxPanelHeight,
borderRadius: BorderRadius.circular(8),
onPanelSlide: (double pos) => setState(() {
_fabHeight = pos * (maxPanelHeight - minPanelHeight) * .5 + 16;
}),
body: Column(
children: <Widget>[
Flexible(
child: Stack(
children: <Widget>[
AmapView(
zoomLevel: widget.zoomLevel,
zoomGesturesEnabled: widget.zoomGesturesEnabled,
showZoomControl: widget.showZoomControl,
onMapMoveEnd: (move) async {
if (_moveByUser) {
// 地图移动结束, 显示跳动动画
_jumpController
.forward()
.then((it) => _jumpController.reverse());
_search(move.coordinate!);
}
_moveByUser = true;
// 保存当前地图中心点数据
_currentCenterCoordinate = move.coordinate!;
},
onMapCreated: (controller) async {
_controller = controller;
if (await widget.requestPermission()) {
await _showMyLocation();
var latLng = await _controller.getLocation();
_search(latLng!);
} else {
debugPrint('权限请求被拒绝!');
}
},
),
// 中心指示器
Center(
child: AnimatedBuilder(
animation: _tween,
builder: (context, child) {
return Transform.translate(
offset: Offset(
_tween.value.dx,
_tween.value.dy - _iconSize / 2,
),
child: child,
);
},
child: widget.centerIndicator ??
Image.asset(
_indicator,
height: _iconSize,
package: _package,
),
),
),
// 定位按钮
Positioned(
right: 16.0,
bottom: _fabHeight,
child: FloatingActionButton(
child: StreamBuilder<bool>(
stream: _onMyLocation.stream,
initialData: true,
builder: (context, snapshot) {
return Icon(
Icons.gps_fixed,
color: snapshot.data!
? Theme.of(context).primaryColor
: Colors.black54,
);
},
),
onPressed: _showMyLocation,
backgroundColor: Colors.white,
),
),
],
),
),
// 用来抵消panel的最小高度
SizedBox(height: minPanelHeight),
],
),
panelBuilder: (scrollController) {
return StreamBuilder<List<PoiInfo>>(
stream: _poiStream.stream,
builder: (context, snapshot) {
if (snapshot.hasData) {
final data = snapshot.data;
return EasyRefresh(
footer: MaterialFooter(),
onLoad: widget.enableLoadMore ? _handleLoadMore : null,
child: ListView.builder(
padding: EdgeInsets.zero,
controller: scrollController,
shrinkWrap: true,
itemCount: data!.length,
itemBuilder: (context, index) {
final poi = data[index].poi;
final selected = data[index].selected;
return GestureDetector(
onTap: () {
// 遍历数据列表, 设置当前被选中的数据项
for (int i = 0; i < data.length; i++) {
data[i].selected = i == index;
}
// 如果索引是0, 说明是当前位置, 更新这个数据
_onMyLocation.add(index == 0);
// 刷新数据
_poiStream.add(data);
// 设置地图中心点
_setCenterCoordinate(poi.latLng!);
// 回调
widget.onItemSelected(data[index]);
},
child: widget.poiItemBuilder(poi, selected),
);
},
),
);
} else {
return Center(child: CircularProgressIndicator());
}
},
);
},
);
}
Future<void> _search(LatLng location) async {
final poiList = await AmapSearch.instance.searchAround(location);
_poiInfoList = poiList.map((poi) => PoiInfo(poi)).toList();
// 默认勾选第一项
if (_poiInfoList.isNotEmpty) _poiInfoList[0].selected = true;
_poiStream.add(_poiInfoList);
// 重置页数
_page = 1;
}
Future<void> _showMyLocation() async {
_onMyLocation.add(true);
await _controller.showMyLocation(MyLocationOption(
strokeColor: Colors.transparent,
fillColor: Colors.transparent,
));
}
Future<void> _setCenterCoordinate(LatLng coordinate) async {
await _controller.setCenterCoordinate(coordinate);
_moveByUser = false;
}
Future<void> _handleLoadMore() async {
final poiList = await AmapSearch.instance.searchAround(
_currentCenterCoordinate,
page: ++_page,
);
_poiInfoList.addAll(poiList.map((poi) => PoiInfo(poi)).toList());
_poiStream.add(_poiInfoList);
}
}
mixin _BLoCMixin on State<LocationPicker> {
// poi流
final _poiStream = StreamController<List<PoiInfo>>();
// 是否在我的位置
final _onMyLocation = StreamController<bool>();
@override
void dispose() {
_poiStream.close();
_onMyLocation.close();
super.dispose();
}
}
mixin _AnimationMixin on SingleTickerProviderStateMixin<LocationPicker> {
// 动画相关
late AnimationController _jumpController;
late Animation<Offset> _tween;
@override
void initState() {
super.initState();
_jumpController =
AnimationController(vsync: this, duration: Duration(milliseconds: 300));
_tween = Tween(begin: Offset(0, 0), end: Offset(0, -15)).animate(
CurvedAnimation(parent: _jumpController, curve: Curves.easeInOut));
}
@override
void dispose() {
_jumpController.dispose();
super.dispose();
}
}

View File

@ -0,0 +1,13 @@
import 'package:amap_search_fluttify/amap_search_fluttify.dart';
class PoiInfo {
PoiInfo(this.poi);
final Poi poi;
bool selected = false;
@override
String toString() {
return 'PoiInfo{poi: $poi, selected: $selected}';
}
}