I am working on google maps and I am trying to drag my map(camera position) without changing marker. Marker should be at center and to pick the location when I stop dragging
This is my complete code of map functionality:
@override
Widget build(BuildContext context) {
return Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Color(0xFFFFFFFF),
Color(0xFFF2F2F4),
Color(0xFFF2F2F4),
],
),
),
alignment: Alignment.center,
child: SafeArea(
child: Scaffold(
resizeToAvoidBottomInset: true,
body: Stack(
alignment: Alignment.bottomCenter,
children: [
Stack(
children: [
GoogleMap(
initialCameraPosition: CameraPosition(
target: _initialCameraPosition, zoom: 13),
mapType: MapType.normal,
onMapCreated: _onMapCreated,
myLocationEnabled: true,
markers: _createMarker(),
onCameraMove: ((_position) => _updatePosition(_position)),
),
Padding(
padding:
EdgeInsets.symmetric(horizontal: 12.w, vertical: 12.h),
child: GestureDetector(
onTap: () {
Navigator.pop(context);
},
child: Card(
shape: const CircleBorder(),
child: Padding(
padding: EdgeInsets.all(8.w),
child: Icon(
Icons.arrow_back,
color: Constants.colorTheme,
),
),
),
),
),
],
),
DraggableScrollableSheet(
initialChildSize: 0.4,
minChildSize: 0.4,
maxChildSize: 0.65,
builder: (context, controller) => Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(22.r)),
child: SingleChildScrollView(
physics: const BouncingScrollPhysics(),
controller: controller,
child: Padding(
padding: EdgeInsets.only(
left: 22.w, right: 22.w, bottom: 22.w, top: 10.h),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Align(
alignment: Alignment.center,
child: Container(
height: 5,
width: 50,
decoration: ShapeDecoration(
shape: const StadiumBorder(),
color: Colors.grey.withOpacity(0.3),
),
),
),
child: GestureDetector(
onTap: () async {
final sessionToken = const Uuid().v4();
var result = await showSearch(
context: context,
delegate: AddressSearch(sessionToken),
);
if (result != null) {
setState(() {
String address = '';
address = result.description;
strSearchedAddress = address;
log(result.lat.toString());
log(result.long.toString());
strLongitude = result.long.toString();
strLatitude = result.lat.toString();
_mapsController.animateCamera(
CameraUpdate.newCameraPosition(
CameraPosition(
target: LatLng(
double.parse(strLatitude),
double.parse(strLongitude)),
zoom: 18),
),
);
_initialCameraPosition = LatLng(
double.parse(strLatitude),
double.parse(strLongitude));
_createMarker();
});
}
},
child: Row(
children: [
Expanded(
child: Row(
children: [
Icon(Icons.search_rounded,
color: Constants.colorBlack,
size: 18.w),
SizedBox(width: 10.w),
Text(
Languages.of(context)!
.labelSearchLocation,
style: TextStyle(
color: Constants.colorBlack,
fontFamily: Constants.appFont,
fontSize: 14.sp),
),
],
),
),
),
SizedBox(width: 12.w),
Text(
'OR',
style: TextStyle(
color: Constants.colorBlack,
fontFamily: Constants.appFont,
fontSize: 14.sp),
),
SizedBox(width: 12.w),
InkWell(
onTap: () async {
setState(() {
isAutoLocateLoading = true;
});
var locations = await Geocoder2
.getAddressFromCoordinates(
latitude: widget.currentLat ?? 0.0,
longitude: widget.currentLong ?? 0.0,
googleMapApiKey: apiKey,
);
setState(() {
strSearchedAddress = locations
.results.first.formattedAddress;
log(locations.results.first.geometry
.location.lat
.toString());
log(locations.results.first.geometry
.location.lng
.toString());
strLongitude = locations.results.first
.geometry.location.lng
.toString();
strLatitude = locations.results.first
.geometry.location.lat
.toString();
_mapsController.animateCamera(
CameraUpdate.newCameraPosition(
CameraPosition(
target: LatLng(
double.parse(strLatitude),
double.parse(
strLongitude)),
zoom: 18),
),
);
_initialCameraPosition = LatLng(
double.parse(strLatitude),
double.parse(strLongitude));
_createMarker();
isAutoLocateLoading = false;
});
},
child: Container(
padding: EdgeInsets.all(10.w),
decoration: BoxDecoration(
color: Constants.colorTheme,
borderRadius:
BorderRadius.circular(8.r),
),
width: 40.w,
height: 40.w,
child: isAutoLocateLoading
? CircularProgressIndicator(
strokeWidth: 2,
backgroundColor:
Constants.colorTheme,
valueColor:
const AlwaysStoppedAnimation(
Colors.white),
)
: Icon(
Icons.near_me_rounded,
color: Colors.white,
size: 18.w,
),
),
)
],
),
),
),
),
Visibility(
visible: strSearchedAddress.isNotEmpty,
child: SimpleShadow(
opacity: 0.6,
color: Colors.black12,
offset: const Offset(0, 3),
sigma: 5,
child: Container(
margin: EdgeInsets.only(top: 22.h),
padding: EdgeInsets.all(12.w),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12.r),
border: Border.all(
color: Colors.grey.withOpacity(0.2)),
),
child: Text(
'Selected Address: $strSearchedAddress',
style: TextStyle(
color: Colors.grey,
fontFamily: Constants.appFont),
),
),
),
),
SimpleShadow(
opacity: 0.6,
color: Colors.black12,
offset: const Offset(0, 3),
sigma: 5,
child: Container(
margin: EdgeInsets.only(top: 22.h),
padding: EdgeInsets.all(12.w),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12.r),
border: Border.all(
color: Colors.grey.withOpacity(0.2)),
),
child: Column(
children: [
Align(
alignment: Alignment.centerLeft,
child: Text(
Languages.of(context)!.labelHouseNo,
style: TextStyle(
fontFamily: Constants.appFontBold,
fontSize: 16,
fontWeight: FontWeight.bold),
SimpleShadow(
opacity: 0.6,
color: Colors.black12,
offset: const Offset(0, 3),
sigma: 5,
child: Column(
children: [
Align(
alignment: Alignment.centerLeft,
child: Text(
Languages.of(context)!.labelLandmark,
style: TextStyle(
fontSize: 18.sp,
fontFamily: Constants.appFontBold,
color: Constants.colorBlack,
),
),
),
SizedBox(height: 22.h),
TextField(
decoration: InputDecoration.collapsed(
hintText: Languages.of(context)!
.labelAnyLandmarkNearYourLocation,
border: InputBorder.none,
),
style: TextStyle(
fontFamily: Constants.appFont,
fontSize: 14.sp,
color: Constants.colorBlack,
SizedBox(height: 22.h),
Row(
children: [
Expanded(
child: CustomElevatedButton(
onPressed: () {
if (_textFullAddress.text.isNotEmpty) {
dialogShowDialog();
} else {
Constants.toastMessage(
'We are missing your complete address, please add one!');
}
},
buttonLabel: widget.isFromAddAddress
? Languages.of(context)!.labelAddAddress
: 'Set This & Proceed to Payment',
dialogShowDialog() {
showModalBottomSheet(
context: context,
isScrollControlled: true,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(22.r),
topRight: Radius.circular(22.r),
),
),
builder: (BuildContext context) {
return Padding(
padding:
EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
child: Padding(
padding: EdgeInsets.all(22.w),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
Languages.of(context)!.labelAttachLabel,
style: TextStyle(
fontSize: 18.sp,
fontFamily: Constants.appFontBold,
),
),
GestureDetector(
child: const Icon(Icons.cancel_rounded,
color: Colors.redAccent),
onTap: () {
Navigator.pop(context);
},
)
],
),
Container(
margin: EdgeInsets.only(top: 22.h),
padding:
EdgeInsets.symmetric(vertical: 10.w, horizontal: 12.w),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8.r),
border: Border.all(color: Colors.grey.withOpacity(0.3)),
),
child: Row(
children: [
Icon(Icons.new_label_rounded,
color: Constants.colorTheme, size: 18.w),
SizedBox(width: 10.w),
Expanded(
child: TextField(
keyboardType: TextInputType.text,
controller: _textAddressLabel,
decoration: InputDecoration.collapsed(
hintText: Languages.of(context)!
.labelAddLabelForThisLocation,
border: InputBorder.none,
),
style: TextStyle(
fontFamily: Constants.appFont,
fontSize: 14.sp,
color: Constants.colorBlack,
),
),
),
],
),
),
SizedBox(height: 22.h),
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Container(
padding: EdgeInsets.all(7.w),
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(color: Colors.grey.withOpacity(0.3)),
),
child: Icon(
Icons.public_rounded,
color: Constants.colorTheme,
size: 20.w,
),
),
SizedBox(width: 12.w),
Expanded(
child: Text(
strSearchedAddress,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 14.sp,
fontFamily: Constants.appFont,
color: Constants.colorBlack),
),
)
],
),
SizedBox(height: 22.h),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: () {
Navigator.pop(context);
},
child: Text(
Languages.of(context)!.labelCancel,
style: TextStyle(color: Constants.colorBlack),
),
),
SizedBox(width: 22.w),
TextButton(
onPressed: () {
if (strSearchedAddress.isEmpty) {
Constants.toastMessage(
Languages.of(context)!.labelPleaseSearchAddress);
} else if (_textAddressLabel.text.isEmpty) {
Constants.toastMessage(Languages.of(context)!
.labelPleaseAddLabelForAddress);
} else {
String strAddressLabel = _textAddressLabel.text;
if (strAddressLabel.trim().isNotEmpty) {
callAddUserAddress(strAddressLabel);
} else {
Constants.toastMessage(
'Please Add Label For Your Location');
}
}
},
child: Text(
'Save',
style: TextStyle(color: Constants.colorTheme),
),
Future<BaseModel<CommonResponse>> callAddUserAddress(strAddressLabel) async {
CommonResponse response;
try {
Constants.onLoading(context);
Map<String, String> body = {
'address': _textFullAddress.text.toString(),
'lat': strLatitude,
'lang': strLongitude,
'type': strAddressLabel,
};
response = await RestClient(RetroApi().dioData()).addAddress(body);
Constants.hideDialog(context);
log(response.success.toString());
if (response.success!) {
Navigator.pop(context);
Navigator.of(context).pushReplacement(
Transitions(
transitionType: TransitionType.slideUp,
curve: Curves.bounceInOut,
reverseCurve: Curves.fastLinearToSlowEaseIn,
widget: const ManageAddressesScreen(),
),
);
} else {
Constants.toastMessage(
Languages.of(context)!.labelErrorWhileAddAddress);
}
} catch (error, stacktrace) {
Constants.hideDialog(context);
log("Exception occurred: $error stackTrace: $stacktrace");
return BaseModel()..setException(ServerError.withError(error: error));
}
return BaseModel()..data = response;
}
void _updatePosition(CameraPosition _position) {
Position newMarkerPosition = Position(
latitude: _position.target.latitude,
longitude: _position.target.longitude,
accuracy: 10,
altitude: 1.0,
heading: 0.0,
speed: 10,
speedAccuracy: 1,
timestamp: null);
Marker marker = Marker(markerId: const MarkerId("marker_1"));
setState(() {
marker = marker.copyWith(
positionParam:
LatLng(newMarkerPosition.latitude, newMarkerPosition.longitude));
});
}
}
for a quick view to relevant parts
Maker method
Set<Marker> _createMarker() {
return <Marker>{
Marker(
draggable: true,
markerId: const MarkerId("marker_1"),
position: _initialCameraPosition,
icon: _markerIcon,
),
};
}
Initial value of map
@override
void initState() {
super.initState();
_markerIcon = widget.marker;
_initialCameraPosition = LatLng(widget.currentLat!, widget.currentLong!);
}
GoogleMap
GoogleMap(
initialCameraPosition: CameraPosition(
target: _initialCameraPosition, zoom: 13),
mapType: MapType.normal,
onMapCreated: _onMapCreated,
myLocationEnabled: true,
markers: _createMarker(),
onCameraMove: ((_position) => _updatePosition(_position)),
),
Update Position method
void _updatePosition(CameraPosition _position) {
Position newMarkerPosition = Position(
latitude: _position.target.latitude,
longitude: _position.target.longitude,
accuracy: 10,
altitude: 1.0,
heading: 0.0,
speed: 10,
speedAccuracy: 1,
timestamp: null);
Marker marker = Marker(markerId: const MarkerId("marker_1"));
setState(() {
marker = marker.copyWith(
positionParam:
LatLng(newMarkerPosition.latitude, newMarkerPosition.longitude));
});
}
On Map created method
void _onMapCreated(GoogleMapController controller) {
_mapsController = controller;
}
Ui view of map What I want(Video descrition) enter link description here
Steps
Your code will look like this
class MyLocationClass extends StatefulWidget {
@override
_MyLocationClassState createState() => _MyLocationClassState();
}
class _MyLocationClassState extends State<MyLocationClass> {
double currentLatitude, currentLongitude;
@override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: Stack(
children: [
GoogleMap(
myLocationEnabled: true,
initialCameraPosition: CameraPosition(
target: LatLng(34.0060495, 71.5179581),
zoom: 14.4746,
),
markers: Set.of([]), //replace this line with your marker/s code.
onCameraMove: (cameraPosition) {
currentLongitude = cameraPosition.target.longitude; //gets the center longitude
currentLatitude = cameraPosition.target.latitude; //gets the center lattitude
},
),
Align(
alignment: Alignment.center,
child: IconButton(
iconSize: 30,
icon: Icon(
Icons.place,
color: Colors.red,
),
onPressed: () {
},
)
),
//a button if you want to use the saved location
Positioned(
bottom: MediaQuery.of(context).size.height * 0.02,
right: MediaQuery.of(context).size.width * 0.25,
left: MediaQuery.of(context).size.width * 0.25,
child: TextButton(
onPressed: ()
{
//save the currentLongitude, currentLatitude or do something else.
},
child: Text('Save'),
)),
],
),
));
}
}