flutter_map перемещает камеру с контроллером карты через BLoC
Я создаю приложение флаттера, используя шаблон BLoC и пакет flutter_map. Я хочу переместить камеру в определенное положение. Я пытаюсь передать контроллер карты в структуру моего блока и переместить камеру оттуда, но получаю сообщение об ошибке:NoSuchMethodError: The getter 'onReady' was called on null.
Я не уверен, что это правильный подход.
class HomePage extends StatelessWidget {
const HomePage({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
...,
BlocProvider<MapBloc>(
create: (BuildContext context) => MapBloc(mapController: MapController()) // passing map controller
..add(MapDataInit()),
)
],
...
);
}
}
map_bloc.dart
class MapBloc extends Bloc<MapEvent, MapState> {
final MapController mapController;
LocationRepository _locationRepository = LocationRepository();
MapBloc({@required this.mapController});
@override
get initialState => MapDataUninitialized();
@override
Stream<MapState> mapEventToState(MapEvent event) async* {
final currentState = state;
if (event is AddMarker) {
yield MapDataLoaded(
mapController: this.mapController,
markers: [...]);
this.add(MoveCamera(event.latLan)); // not sure about this
}
if (event is MoveCamera) {
mapController.onReady.then((result) { // i'm getting an error here
mapController.move(event.latLan, 15.0);
});
}
}
}
Виджет с картой
class SelectLocationView extends StatelessWidget {
Widget build(BuildContext context) {
return BlocBuilder<MapBloc, MapState>(
builder: (context, state) {
...
if (state is MapDataLoaded) {
return Container(
child: Center(
child: Container(
child: FlutterMap(
mapController: state.mapController, // I'm trying to get previously defined controller
options: MapOptions(...),
layers: [
TileLayerOptions(
urlTemplate:
"https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
subdomains: ['a', 'b', 'c']),
MarkerLayerOptions(...),
],
))),
);
}
},
);
}
}
Я понятия не имею, почему у контроллера карты проблема с методом onReady.
5 ответов
У меня была аналогичная проблема с GetX.
Я решил, применив некоторые предпосылки: во-первых, я сохранил в виджете (без сохранения состояния) любые и все манипуляции с картой, потому что мне нужно переключаться между плагином google и flutter_map.
Затем в контроллере (в вашем случае в BloC) он просто запускает необходимую информацию, чтобы представление получило от него новое местоположение, которое должно быть центрировано, но представление - это тот, кто знает и централизует карту, а не BloC.
Короче говоря, мне пришлось работать со статической переменной, чтобы сохранить одноэлементную ссылку на mapController в приложении, потому что метод onReady () был вызван только один раз, поэтому я сохранил bool для управления состоянием карты, тогда как "onReady" не выполняется, у нас нет доступа к объектам карты, таким как "масштабирование" и "перемещение".
Мой пример кода:
class FlutterMapWidget extends StatelessWidget {
static MapController _mapController;
bool isMapRead = false;
FlutterMapWidget() {
// create instance only not exists another reference
if(_mapController == null) {
_mapController = MapController();
}
// after onReady, flag local control variable
_mapController.onReady.then((v) {
isMapRead = _mapController.ready;
});
}
@override
Widget build(BuildContext context) {
return Stack(
children: [
_buildMap(),
// add another map components ...
],
);
}
void _gotoLocation(double lat,double long) {
//check if map is ready to move camera...
if(this.isMapRead) {
_mapController.move(LatLng(lat, long), _mapController?.zoom);
}
}
Widget _buildMap(){
return GetBuilder<MapEventsController>( // GetX state control
init: _mapEventsController, // GetX events controller
builder: (value) { // GetX event fire when update state in controller
updatePinOnMap(value.liveUpdate, value.location);
if(value.liveUpdate){
_gotoLocation(value.location.lat, value.location.lng);
}
return FlutterMap(
mapController: _mapController,
options: MapOptions(
center: LatLng(value?.location?.lat, value?.location?.lng),
zoom: 13.0,
plugins: [
],
onTap: _handleTap,
),
layers: [
TileLayerOptions(
urlTemplate:
'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
subdomains: ['a', 'b', 'c']
),
MarkerLayerOptions(markers: markers), //markers
],
);
},
);
}
void updatePinOnMap(bool liveUpdate, Location location) async {
if(location == null) {
return;
}
this.markers.clear();
Marker mark = Marker(
point: LatLng(location.lat, location.lng),
anchorPos: anchorPos,
builder: (ctx) =>
GestureDetector(
onTap: () => _configureModalBottomSheet(ctx),
child: Container(
child: Icon(Icons.my_location, size: 28, color: Colors.red,), // FlutterLogo(),
),
),
);
markers.add(mark);
}
void _handleTap(LatLng latlng) {
}
void _configureModalBottomSheet(context) {
}
}
Если вы используете BLoC (как управление состоянием), здесь есть пример, как вы можете сделать это безcontroller
.
- поместите свой виджет внутри вашего пользовательского виджета.
- При вызове этого виджета используйте
key
и оберните его с помощью BlocBuilder. В результатеFlutterMap
виджет будет перестроен с новым центром.
Пример:
class _MapView extends StatelessWidget {
const _MapView();
@override
Widget build(BuildContext context) {
return BlocBuilder<MapBloc, MapState>(
builder: (context, state) {
return Scaffold(
body: MapWidget(
key: Key('map${state.latitude}${state.longitude}'),
latitude: state.latitude,
longitude: state.longitude,
),
);
},
);
}
}
class MapWidget extends StatelessWidget {
const MapWidget({
super.key,
required this.latitude,
required this.longitude,
});
final double latitude;
final double longitude;
@override
Widget build(BuildContext context) {
final _searchingPoint = LatLng(
latitude,
longitude,
);
return FlutterMap(
options: MapOptions(
center: _searchingPoint,
zoom: 11,
keepAlive: true,
),
);
}
}
Источник: Гитхаб
Приведенные выше ответы верны, но я добавлю одну вещь, которая также была большой проблемой для меня. Как сохранить уровень масштабирования в соответствии с масштабированием пользователя, потому что, когда вы прослушиваете изменения текущего местоположения и обновляете положение камеры в соответствии с ним, уровень масштабирования также выбирает тот, который вы указали в начале.
например: вы предоставляете уровень масштабирования 13 в начале и масштабируете экран до 16, а затем, когда положение камеры обновляется, он снова возвращает вас на уровень масштабирования 13, и это повторяется каждую секунду, что действительно раздражает. Итак, у вас есть для динамического предоставления уровня масштабирования , который будет меняться в зависимости от уровня масштабирования пользователя.
Сначала прослушайте поток местоположения:
location.onLocationChanged.listen((event) {
final newLatLng = LatLng(event.latitude!, event.longitude!);
// Use mapController to access the move method
// move() method is used for the camera position changing e.g: move(LatLng center, double zoom)
mapController.move(newLatLng , 13); // this will set the zoom level static to 13
// but we want it to be dynamic according to the user zoom level,
// so then use the mapController.zoom property will dynamically adjust your zoom level
mapController.move(newLatLng , mapController.zoom);
});
Звучит глупо, но вы инициализируете контроллер карты? Нравится MapController mapController = MapController();
Вы также можете сделать это с помощью BLOC. Просто используйтеBlocListener
и обновляйте значения контроллера в соответствии с вашим состоянием при каждом изменении состояния.
Widget build(BuildContext context) {
//create your map controller
MapController mapController = MapController();
...
//Attach a listener that is called when a state changes and control the MapController
return BlocListener<AddFindingScreenBloc, AddFindingScreenState>(
listener: (context, state) {
mapController.move(LatLng(state.latitude, state.longitude), state.zoom);
},
child:
...
FlutterMap(
mapController: mapController
);