이번 시간에는 Flutter Map에서 마커를 생성하는 방법을 알아봅시다.
Flutter_Map 같은 경우, Layer 구조로 되어 있는데, 쉽게 말해서 2D 평면이 여러개 겹쳐있다고 보시면 됩니다.
Flutter_Map 여러가지 레이어가 존재하는데, 그중 대표적으로 MarkerLayer가 존재합니다.
구현 코드는 다음과 비슷합니다.
FlutterMap(
mapController: mapController.mapController,
options: MapOptions(
initialCenter: currentLocation ?? latLng.LatLng(51.509364, -0.128928),
initialZoom: zoom,
),
children: [
TileLayer(
urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
userAgentPackageName: 'com.example.app',
),
MarkerLayer(
markers: markers,
),
],
),
MarkerLayer는 기본적으로 List<Marker> 형태로 받습니다. 물론, 마커가 한개가 아니라 여러개일 가능성이 크니까요.
Marker 타입은 Flutter_Map에서 지원하는 클래스이므로 다음과 같은 형태를 갖습니다.
Marker(
point: latLng.LatLng(latitude, longitude),
width: 60,
height: 60,
child: myWidget()
)
)
point는 당연히 위도 경도 좌표가 되고, LatLng 형태의 객체를 받습니다. latitude와 longitude는 double 형태에 자료형입니다.
width와 height는 마커의 크기를 나타냅니다.
child는 widget 자식을 받는데, 원하는 Widget으로 나타낼 수 있습니다.
이전에 선언했던 currentLocation 변수를 통해 코드를 다음과 같이 수정하면 현재 위치에 마커를 놓을 수 있습니다.
Marker(
point: currentLocation!,
width: 60,
height: 60,
child: CustomMarkerIcon(
longitude: currentLocation!.longitude,
latitude: currentLocation!.latitude,
isPlace: false,
imagePath: "asset/img/portrait.webp",
size: Size(400.0, 400.0),
),
),
저같은 경우, CustomMarkerIcon 이라는 위젯을 만들어 화면에 나타내기로 했습니다.
다음 화면과 같이 여러개의 Marker를 놓아서 화면에 예쁘게 나타낼 수 있음을 확인할수 있습니다.
CustomMarkerIcon의 코드는 다음과 같습니다.
import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:mapdesign_flutter/LocationInfo/marker_clicked.dart';
import 'dart:async';
import 'dart:ui' as ui;
import 'dart:typed_data';
Future<ui.Image> getImageFromPath(String imagePath) async {
final Completer<ui.Image> completer = Completer();
final AssetImage assetImage = AssetImage(imagePath);
assetImage.resolve(ImageConfiguration()).addListener(
ImageStreamListener(
(ImageInfo info, bool _) => completer.complete(info.image),
),
);
return completer.future;
}
Future<Uint8List> createMarkerImage(String imagePath, Size size) async {
final ui.PictureRecorder pictureRecorder = ui.PictureRecorder();
final Canvas canvas = Canvas(pictureRecorder);
final Radius radius = Radius.circular(size.width / 2);
final Paint tagPaint = Paint()..color = Colors.blue;
final double tagWidth = 80.0;
final Paint shadowPaint = Paint()..color = Colors.blue.withAlpha(100);
final double shadowWidth = 15.0;
final Paint borderPaint = Paint()..color = Colors.white;
final double borderWidth = 3.0;
final double imageOffset = shadowWidth + borderWidth;
// Add shadow circle
canvas.drawRRect(
RRect.fromRectAndCorners(
Rect.fromLTWH(
0.0,
0.0,
size.width,
size.height
),
topLeft: radius,
topRight: radius,
bottomLeft: radius,
bottomRight: radius,
),
shadowPaint);
// Add border circle
canvas.drawRRect(
RRect.fromRectAndCorners(
Rect.fromLTWH(
shadowWidth,
shadowWidth,
size.width - (shadowWidth * 2),
size.height - (shadowWidth * 2)
),
topLeft: radius,
topRight: radius,
bottomLeft: radius,
bottomRight: radius,
),
borderPaint);
// Add tag text
TextPainter textPainter = TextPainter(textDirection: TextDirection.ltr);
textPainter.text = TextSpan(
text: '1',
style: TextStyle(fontSize: 40.0, color: Colors.white),
);
textPainter.layout();
textPainter.paint(
canvas,
Offset(
size.width - tagWidth / 2 - textPainter.width / 2,
tagWidth / 2 - textPainter.height / 2
)
);
// Oval for the image
Rect oval = Rect.fromLTWH(
imageOffset,
imageOffset,
size.width - (imageOffset * 2),
size.height - (imageOffset * 2)
);
// Add path for oval image
canvas.clipPath(Path()
..addOval(oval));
// Add image
ui.Image image = await getImageFromPath(imagePath); // Alternatively use your own method to get the image
paintImage(canvas: canvas, image: image, rect: oval, fit: BoxFit.fitWidth);
final ui.Image markerAsImage = await pictureRecorder.endRecording().toImage(
size.width.toInt(),
size.height.toInt()
);
final ByteData? byteData = await markerAsImage.toByteData(format: ui.ImageByteFormat.png);
return byteData!.buffer.asUint8List();
}
Future<BitmapDescriptor> getMarkerIcon(String imagePath, Size size) async {
Uint8List markerImage = await createMarkerImage(imagePath, size);
return BitmapDescriptor.fromBytes(markerImage);
}
class CustomMarkerIcon extends StatefulWidget {
final String imagePath;
final Size size;
final bool isPlace;
final double latitude;
final double longitude;
final String category;
CustomMarkerIcon({required this.imagePath, required this.size, this.isPlace = false,
required this.latitude, required this.longitude, this.category = "none"});
@override
_CustomMarkerIconState createState() => _CustomMarkerIconState();
}
class _CustomMarkerIconState extends State<CustomMarkerIcon> {
Uint8List? imageBytes;
@override
void initState() {
super.initState();
_loadImage();
}
Future<dynamic> _getPlaceInfo(){
if(widget.isPlace){
return showModalBottomSheet(
isScrollControlled: true,
context: context,
builder: (BuildContext context){
return Container(
height: MediaQuery.of(context).size.height,
child: MarkerClicked(latitude:widget.latitude, longitude: widget.longitude, category: widget.category)
);
}
);
}
else {
return Future.value();
}
}
Future<void> _loadImage() async {
imageBytes = await createMarkerImage(widget.imagePath, widget.size);
setState(() {});
}
@override
Widget build(BuildContext context) {
if (imageBytes == null) {
return Container();
}
return GestureDetector(
onTap: _getPlaceInfo,
child: Image.memory(imageBytes!),
);
}
}
GestureDetector를 Image.memory 만 사용하시는걸 권장합니다.
제가 사용하는 코드는 마커를 클릭시 별도의 API를 요청하기 때문에 그부분은 따로 빼셔야 합니다.
'Programming Language > Flutter' 카테고리의 다른 글
[Dart, Flutter] SecureStorage와 Singleton Pattern (0) | 2023.12.04 |
---|---|
[Dart, Flutter] GridView 사용하기 (3) | 2023.12.04 |
[Dart, Flutter] HTTP 통신을 이용하여 Marker에 대한 데이터 가져오기 (0) | 2023.12.04 |
[Dart, Flutter] Flutter_Map에서 Device 위치 받아오기(Geolocator) (0) | 2023.12.03 |
[Dart, Flutter] Flutter_Map Package 사용하기 (0) | 2023.12.03 |