Flutter Syncfusion Map move marker programmatically from MapLatLng to another - flutter

Thanks in advance for anyone that help with this question. I have one marker (blue) representing user current location of user, but thus is mock MapLatLng as the user will be able to tap on the map where they will like to go next next. I would like to know how I can programmatically move the blue to the red marker to mimic a travel. I am using flutter syncfusion map, thanks again for any feedback

You can use MapShapeLayerController.pixelToLatLng method to convert the tapped local position into the MapLatLng. To update the map zoomLevel and focalLatLng programmatically, set the new zoom level to the MapZoomPanBehavior.zoomLevel property and center position to the MapZoomPanBehavior.focalLatLng property. I have attached the code snippet for your reference.
late MapZoomPanBehavior _zoomPanBehavior;
late MapShapeLayerController _shapeLayerController;
late MapShapeSource _mapShapeSource;
late List<MapLatLng> _markers;
#override
void initState() {
_mapShapeSource =
const MapShapeSource.asset('assets/usa.json', shapeDataField: 'name');
_shapeLayerController = MapShapeLayerController();
_zoomPanBehavior = MapZoomPanBehavior();
_markers = [const MapLatLng(40.26755819027706, -74.5658675759431)];
super.initState();
}
#override
void dispose() {
_shapeLayerController.dispose();
_markers.clear();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(widget.title)),
body: Stack(children: <Widget>[
_buildMapsWidget(),
GestureDetector(
onTapUp: (details) {
// Converting the tapped point into MapLatLng and adding the marker
// in that position.
final MapLatLng latLng =
_shapeLayerController.pixelToLatLng(details.localPosition);
_markers.add(latLng);
_shapeLayerController.insertMarker(_markers.length - 1);
},
),
Align(
alignment: Alignment.bottomCenter,
child: ElevatedButton(
onPressed: (() {
// Programmatically change the zoomLevel and focalLatLng with animation.
_zoomPanBehavior.zoomLevel = 3;
_zoomPanBehavior.focalLatLng = const MapLatLng(39.466, -116.83);
}),
child: const Text('Nevada, USA')),
)
]),
);
}
Widget _buildMapsWidget() {
return SfMaps(
layers: <MapLayer>[
MapShapeLayer(
source: _mapShapeSource,
showDataLabels: true,
initialMarkersCount: _markers.length,
markerBuilder: (context, index) {
return MapMarker(
latitude: _markers[index].latitude,
longitude: _markers[index].longitude,
child: const Icon(Icons.location_on, color: Colors.red),
);
},
zoomPanBehavior: _zoomPanBehavior,
controller: _shapeLayerController,
dataLabelSettings: const MapDataLabelSettings(
textStyle: TextStyle(color: Colors.black, fontSize: 10),
overflowMode: MapLabelOverflow.hide),
),
],
);
}
Also, refer to the following knowledge base and user guide documentation links to know more about updating the markers and changing zoomLevel and focalLatLng dynamically.
KB links:
Add/Update markers - https://www.syncfusion.com/kb/13025/how-to-update-the-markers-dynamically-using-flutter-maps
Update latitude and longitude programmatically - https://www.syncfusion.com/kb/12366/how-to-zoom-and-pan-programmatically-in-flutter-maps
UG Links:
Add/Update markers - https://help.syncfusion.com/flutter/maps/markers#for-shape-layer
Update latitude and longitude programmatically - https://help.syncfusion.com/flutter/maps/zoom-pan#update-the-center-latitude-and-longitude-programmatically

Related

How to access current location and set that as initial location?

I have a Flutter app which requires location services. I have already implemented getting a users current location, permissions, etc...
My app has to position a marker on a map when first open based on the current location of the user. (This is what I need help with)
The user can later touch the map at any other point to place the marker at that point. (This I have already done)
I have successfully created a function which can retrieve the current location of a user like this, (Do note that that function is async)
Future<LatLng> get currentLocation async {
Position pos = await Geolocator.getCurrentPosition(
desiredAccuracy: LocationAccuracy.high,
);
return LatLng(pos.latitude, pos.longitude);
}
I currently have LatLng markerPoint = LatLng(12.9716, 77.5946); as my initial marker point inside of my HomePage like this,
class _HomePageState extends State<HomePage> {
// Initial marker location
LatLng markerPoint = LatLng(12.9716, 77.5946);
...
But I want the current location returned by my function to replace the values given here. I am unable to do something like LatLng markerPoint = await _locationService.currentLocation; because I cannot make that part of the class async.
So, that's basically my problem. I want to initialise my variable through an asynchronous function, but I haven't found a way to do that since the area I am in, does not allow for async functions.
Help will be appreciated.
PS:
My remaining build function which sets the marker point whenever touched is over here for your reference.
#override
Widget build(BuildContext context) {
return Stack(
children: [
FlutterMap(
options: MapOptions(
onTap: (tapPosition, point) async {
setState(() {
markerPoint = point;
});
},
//
center: markerPoint,
zoom: 10.0,
// this is required to disable rotation of the map
// the map will behave wierd when you rotate it if it's deleted
// the map will not scroll properly and not place markers at the exact locations.
// if the below line in removed
interactiveFlags: InteractiveFlag.all & ~InteractiveFlag.rotate,
),
nonRotatedChildren: [
TileLayer(
urlTemplate: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
subdomains: const ['a', 'b', 'c'],
),
MarkerLayer(
markers: [
Marker(
width: 100.0,
height: 100.0,
point: markerPoint,
builder: (ctx) => const Icon(
Icons.location_on,
color: Colors.red,
size: 40,
),
),
],
),
],
),
SafeArea(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Card(
child: TextField(
decoration: const InputDecoration(
prefixIcon: Icon(Icons.location_on_outlined),
hintText: "Search for a location",
contentPadding: EdgeInsets.all(16.0),
),
onTap: () async {
// This is here for debugging only, kindly ignore
await requestPermission();
},
),
),
],
),
),
),
],
);
}
hope you are calling the get current location api in the initState() method
eg :
#override
void initState() {
super.initState();
fetchCurrentLocation();
}
void fetchCurrentLocation() async{
//calling the api
var currentLocation = await _locationService.currentLocation;
//setState will update the values in real time
setState(() {
markerPoint = currentLocation
});
}

I am trying to get the latitude and longitude using mapbox but it always just print my initial location and not the updated center of camera location

I'm trying to get the the longitude and latitude of the map using mapbox, but instead of getting the lat, lng of the current camera location it only prints the intial camera location
late MapboxMapController _mapController;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
Expanded(
child: MapboxMap(
accessToken: dotenv.env['MAPBOX_ACCESS_TOKEN'],
initialCameraPosition: CameraPosition(
target: LatLng(37.7749, -122.4194),
zoom: 11.0,
),
onMapCreated: (controller) {
setState(() {
_mapController = controller;
});
},
),
),
],
),
floatingActionButton: FloatingActionButton.extended(
icon: const Icon(Icons.location_on_outlined),
onPressed: () async {
// Get the center point of the map
LatLng? center = _mapController.cameraPosition?.target;
// Do something with the picked location
print('Picked location: $center');
},
label: const Text('Pick Location')));
}
I am using web version of Mapbox, I think You need get it from Event not from your controller.
I found this package, maybe help you.
https://github.com/mapbox/mapbox-maps-flutter

Flutter Google Map Markers are shown only after hot reload - using with cubit and custom marker

I'm struggling second day on this issue.
I use flutter google map to show about hundred custom markers with network image icons, that can be svg or png (using MarkerGenerator).
After opening the map page, MapCubit start to load items from API. In build i have BlocConsumer, where is listener, that build markers when loaded in that cubit and builder that build GoogleMap.
Problem is, that on first opening of page there are no images in markers, only white circle. When I tried to set one image url to all markers, it was loaded properly. Then, when i go on previous page or hot reload (not always), icons are there. On same page i have legend, that draw images from same urls, where images are set properly in most times. Sometimes it is need to go back and forward more times.
I can load icons after click on item in filter, that calls MapCubit, too.
I dont know, if it means something, but next problem, what i have is, that on release and appbundle build, no map is shown, only grey screen, buttons on side and google logo on bottom left.
I tried many tips on internet, but nothing helped.
Preview of first opening of MapPage
Preview of filter at first opening (has all icons)
Preview of second opening of MapPage
Preview of third opening of MapPage (has all icons)
MapPage (MarkerGenerator is in listener and initState becouse of two different uses that needs it)
class _MapAreaState extends State<MapArea> {
MapCubit _mapCubit;
Set<Marker> markers = {};
List<CustomMarker> markerWidgets = [];
bool markersLoaded = false;
#override
void initState() {
_mapCubit = BlocProvider.of<MapCubit>(context);
markers = {};
MarkerGenerator(
_mapCubit.state.items.map((e) => CustomMarker(type: e.type)).toList(),
(bitmaps) {
setState(() {
bitmaps.asMap().forEach((mid, bmp) {
IMapItem item = _mapCubit.state.items[mid];
markers.add(Marker(
markerId: MarkerId(item.title),
position: item.latLng,
icon: BitmapDescriptor.fromBytes(bmp),
// markerId: MarkerId(item.title),
// position: item.latLng,
onTap: () async {
await _mapCubit.showDetail(item);
}));
});
});
}).generate(context);
super.initState();
}
#override
Widget build(BuildContext context) {
return Container(
color: tercialBackgroundColor,
child: BlocConsumer<MapCubit, MapState>(
bloc: _mapCubit,
listener: (context, state) {
if (state.changedItems && state.items.isNotEmpty) {
markerWidgets = _mapCubit.state.items
.map((e) => CustomMarker(type: e.type))
.toList();
markers = {};
MarkerGenerator(markerWidgets, (bitmaps) {
setState(() {
bitmaps.asMap().forEach((mid, bmp) {
log(bmp.toString());
IMapItem item = _mapCubit.state.items[mid];
markers.add(Marker(
markerId: MarkerId(item.title),
position: item.latLng,
icon: BitmapDescriptor.fromBytes(bmp),
// markerId: MarkerId(item.title),
// position: item.latLng,
onTap: () async {
await _mapCubit.showDetail(item);
}));
});
});
}).generate(context);
}
},
builder: (context, state) {
return Stack(
children: [
GoogleMap(
zoomControlsEnabled: false,
compassEnabled: false,
markers: markers,
// markers: Set<Marker>.of(state.markers),
initialCameraPosition: CameraPosition(
target: state.items.length == 1
? state.items[0].latLng
: LatLng(49.07389317899512, 19.30980263713778),
zoom: 8.5,
),
minMaxZoomPreference: MinMaxZoomPreference(8, 22),
cameraTargetBounds: CameraTargetBounds(LatLngBounds(
northeast: LatLng(50.16477808289659, 20.56397637952818),
southwest: LatLng(48.75267922516721, 18.76330228064009),
)),
onMapCreated: (GoogleMapController controller) {
if (!_mapCubit.controller.isCompleted) {
rootBundle
.loadString('assets/googleMapsStyle.json')
.then((string) async {
controller.setMapStyle(string);
});
_mapCubit.controller.complete(controller);
log(_mapCubit.controller.toString());
log(controller.toString());
setState(() {
});
}
},
),
// if(state.items.isEmpty)
// FullScreenLoadingSpinner()
],
);
},
),
);
}
}
CustomMarker class
class CustomMarker extends StatelessWidget {
final ItemType type;
const CustomMarker({Key key, this.type}) : super(key: key);
#override
Widget build(BuildContext context) {
// precachePicture(
// svgPicture.pictureProvider,
// Get.context!,
// );
// if(type.icon is /-
return Stack(
clipBehavior: Clip.none,
children: [
Icon(
Icons.add_location,
color: type.color,
size: 56,
),
Positioned(
left: 16,
top: 10,
child: Container(
width: 24,
height: 24,
decoration: BoxDecoration(
color: primaryBackgroundColor,
borderRadius: BorderRadius.circular(10)),
child: Padding(
padding: const EdgeInsets.all(1.0),
child: Center(child: type.icon),
),
),
),
],
);
}
}
Icon setting in ItemType factory, that is used in CustomMarker
icon: map['icon'] != null
? (map['icon'] is Image
? map['icon']
: (map['icon'].substring(map['icon'].length - 4) == '.svg'
? WebsafeSvg.network(
map['icon'],
width: 18,
height: 18,
color: Colors.black,
placeholderBuilder: (BuildContext context) => Container(
padding: const EdgeInsets.all(30.0),
child: const CircularProgressIndicator()),
)
: Image.network(map['icon'])))
Lately somewhen this exception is in console
======== Exception caught by image resource service =====================
The following HttpException was thrown resolving an image codec:
, uri = https://www.xxx.sk/images/svgs/culture.png
When the exception was thrown, this was the stack:
Image provider: NetworkImage("https://www.xxx.sk/images/svgs/culture.png", scale: 1.0)
Image key: NetworkImage("https://www.xxx.sk/images/svgs/culture.png", scale: 1.0)
I dont know, what all to send, so far at least this. Thanks.

Flutter - how to add animation to a Widget so that it slides into the view when a button is pressed?

Let's make a simple example, given a Column(), I have 2 Containers in it, and a button.
Column(
children: [
MyButton(
label: "Expand me"
onTap: () => setState(() => isOpen = !isOpen)
),
Container(
child: Text("Container 1"),
height: 200
),
if (isOpen)
Container(
child: Text("Container 2"),
height: 150
)
]
)
so basically, if we press the button, the second Container will appear right under the first one, like an expansion panel.
Now I want to add an animation, and I'm having a hard time finding the best fit for my use case, as most solutions look really complex for such a simple task.
The animation is really simple, instead of making the Container 2 appear out of nowhere under the first one, it would be nice if the Container 2 would start behind Container 1, and then slide towards the bottom, until in position.
What is the cleanest way to achieve this in flutter?
import 'package:flutter/material.dart';
// ignore: must_be_immutable
class EasyAnimatedOffset extends StatefulWidget {
EasyAnimatedOffset();
#override
_EasyAnimatedOffsetState createState() => _EasyAnimatedOffsetState();
}
class _EasyAnimatedOffsetState extends State<EasyAnimatedOffset>
with SingleTickerProviderStateMixin {
//Notice the "SingleTickerProviderStateMixin" above
//Must add "AnimationController"
late AnimationController _animationController;
#override
void initState() {
super.initState();
_animationController = AnimationController(
vsync: this,
//change the animation duration for a slower or faster animation.
//For example, replacing 1000 with 5000 will give you a 5x slower animation.
duration: Duration(milliseconds: 1000),
);
}
animateForward() {
_animationController.forward();
//this controller will move the animation forward
//you can also create a reverse animation using "_animationController.reverse()"
}
#override
void dispose() {
_animationController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
//the offset has a x value and y value.
//changing the y axis value moves the animation vertically
//changing the x axis value moves the animation horizantaly
double xAxisValue = 0;
double yAxisValue = 10;
return AnimatedBuilder(
animation: _animationController,
// child: widget.child,
builder: (context, child) {
return Transform.translate(
offset: Offset(_animationController.value * xAxisValue,
_animationController.value * yAxisValue),
//add your button or widget here
child: InkWell(
onTap: () {
animateForward();
},
child: Center(
child: Container(
height: 100,
width: 200,
color: Colors.amber,
child: Center(
child: Text(
"Animate Me",
style: TextStyle(
color: Colors.black,
fontSize: 20,
),
),
)),
)));
});
}
}

Widgets sliding from outside the screen in Flutter ? Similar to Android 8 app drawer

I am writing a flashcard app (an extension to the open source AnkiDroid app) in Flutter. The basic workflow is: the app shows me a question and I can reveal the answer. The gesture I want in order to reveal the answer is similar to the Android 8 swipe up from the bottom icon row to reveal the app drawer. A fast swipe (or fling in the android terminology?) can reveal the app list, but a drawn out, slow swipe can control the motion of the apps drawer.
My questions are the following:
What is the proper way to have widgets slide in from outside the screen ? Flutter complains that I'm trying to display widgets outside the screen, suggests I use ClipRect, but I haven't found a way for ClipRect to only display something the size of the screen (it seems to adjust itself to the size of the child)
What is the recommended layout for what I want to do ? Currently I have the question and answer in a Column, and in order to center the question initially and hide the question, I modify the padding. It feels like a bit of a hack.
Is there a helper library that can help me achieve the exact swipe/fling motion that I'm after? It needs to take into account momentum and position in order for the motion to feel just as natural as the android 8 app drawer.
Thank you for any suggestions you may have.
Here are the screens I have so far:
Question screen
Answer screen (after swiping up)
And here's the code:
import 'package:flutter/material.dart';
import 'dart:math';
// Uncomment lines 7 and 10 to view the visual layout at runtime.
//import 'package:flutter/rendering.dart' show debugPaintSizeEnabled;
void main() {
//debugPaintSizeEnabled = true;
runApp(MyApp());
}
/*
* travel around the world
* 環遊世界
* wàan jàu sâi gâai
*/
class Card extends StatefulWidget {
#override
createState() => CardState();
}
class CardState extends State<Card> with SingleTickerProviderStateMixin {
var _dragStartOffset;
Animation<double> questionAnimation;
Animation<double> answerAnimation;
Animation<double> opacityAnimation;
AnimationController controller;
initState() {
super.initState();
controller = AnimationController(duration: const Duration(milliseconds: 250), vsync: this);
questionAnimation = Tween(begin: 250.0, end: 150.0).animate(controller)
..addListener(() {
setState(() {
// the state that has changed here is the animation object’s value
});
});
answerAnimation = Tween(begin: 200.0, end: 32.0).animate(controller)
..addListener(() {
setState(() {
// the state that has changed here is the animation object’s value
});
});
opacityAnimation = Tween(begin: 0.0, end: 1.0).animate(controller)
..addListener(() {
setState(() {
// the state that has changed here is the animation object’s value
});
});
}
#override
Widget build(BuildContext context) {
Widget question = Container(
padding: EdgeInsets.only(top: questionAnimation.value),
child: Center (
child: Text(
"travel around the world",
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 48.0,
),
textAlign: TextAlign.center,
)
),
);
Widget answer = Container(
padding: EdgeInsets.only(top: answerAnimation.value),
child: Opacity(
opacity: opacityAnimation.value,
child: Text(
"wàan jàu sâi gâai 環遊世界",
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 48.0,
),
textAlign: TextAlign.center,
)
)
);
var children = [question, answer];
var child = GestureDetector(
onTap: () {
controller.reset();
},
onVerticalDragUpdate: (data) {
// print(data);
var currentOffset = data.globalPosition;
var travel = _dragStartOffset - currentOffset;
// print(travel);
if(travel.dy <0 )
{
return;
}
// cannot be lower than zero
var travelY = max<double>(0.0, travel.dy);
// cannot be higher than 100
travelY = min<double>(200.0, travelY);
var animationPosition = travelY / 200.0;
controller.value = animationPosition;
},
onVerticalDragEnd: (data) {
if(controller.value > 0.50) {
// make the animation continue on its own
controller.forward();
} else {
// go back the other way
controller.reverse();
}
},
onVerticalDragStart: (data) {
//print(data);
_dragStartOffset = data.globalPosition;
},
child: Scaffold(
appBar: AppBar(
title: Text('AnkiReview'),
),
body: Container(
child:Column(
children: children,
)
),
)
);
return child;
}
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: Card(),
);
}
}
I figured out one solution. It involves a Column, the top is just a Container with the question, but the bottom is a PageView which has a blank first page. The user can slide up to reveal the answer.
It solves the clipping issue, and also the physics issue, because PageView has built-in physics and snapping, which would otherwise not be trivial to build (I would probably have to use a CustomScrollView).
code:
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
import 'dart:math';
// Uncomment lines 7 and 10 to view the visual layout at runtime.
//import 'package:flutter/rendering.dart' show debugPaintSizeEnabled;
void main() {
//debugPaintSizeEnabled = true;
runApp(MyApp());
}
/*
* travel around the world
* 環遊世界
* wàan jàu sâi gâai
*/
class Card extends StatefulWidget {
#override
createState() => CardState();
}
class CardState extends State<Card> with SingleTickerProviderStateMixin {
var _dragStartOffset;
var _fontSize = 48.0;
static const _padding = 28.0;
initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
var questionText = Text(
"travel around the world",
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: _fontSize,
),
textAlign: TextAlign.center,
);
var answerText = Text(
"wàan jàu sâi gâai 環遊世界",
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: _fontSize,
),
textAlign: TextAlign.center
);
Widget question = Container(
padding: EdgeInsets.only(bottom: _padding),
alignment: Alignment.bottomCenter,
child: questionText
);
Widget answer = Container(
padding: EdgeInsets.only(top: _padding),
alignment: Alignment.topCenter,
child: answerText
);
var card = Column(
children: [
Expanded(
child: question,
),
Expanded(
child: PageView(
scrollDirection: Axis.vertical,
children: [
Container(),
answer
]
)
)
]
);
return card;
}
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: Scaffold(
appBar: AppBar(
title: Text('AnkiReview'),
),
body: Container(
child:Card()
),
),
);
}
}