https://github.com/cjk09083/ftweather
이번 포스트에서는 지도 하단에 ListView를 만들어서 지도에서 추가한 Marker들의 정보를 리스트로 확인해보자.
1. ListView 추가
Home.dart에 아래와 같이 ListView를 추가하고 Marker추가를 위한 버튼을 Stack과 Positioned 위젯을 사용해 NaverMap 위에 겹치도록 수정한다.
import 'package:flutter/material.dart';
import 'package:ftweather/provider/MapModel.dart';
import 'package:naver_map_plugin/naver_map_plugin.dart';
import 'package:provider/provider.dart';
class Home extends StatelessWidget {
const Home({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final mapPosition = Provider.of<MapModel>(context).mapPosition;
final model = Provider.of<MapModel>(context, listen: false);
final markers = Provider.of<MapModel>(context).markers;
return Scaffold(
appBar: AppBar(
title: const Text('Flutter Weather App'),
),
body: Column(
children: [
Expanded(
flex: 6,
child: Stack(
children: [
NaverMap(
// NaverMapController를 model에 전달
onMapCreated: (controller) {
model.setController(controller);
},
mapType: MapType.Hybrid,
locationButtonEnable: true,
initialCameraPosition: mapPosition,
markers: markers, // MapModel에서 가져온 markers 사용
),
Positioned(
bottom: 16,
right: 16,
child: FloatingActionButton(
onPressed: () {
// addMarker() 메서드를 호출하여 현재 위치에 마커 추가
Provider.of<MapModel>(context, listen: false).addMarker();
},
tooltip: 'Increment',
child: const Icon(Icons.add),
),
),
],
),
),
Expanded(
flex: 4,
child: ListView.builder(
itemCount: markers.length,
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(markers[index].markerId.toString()), // Marker id
Text(markers[index].position!.latitude.toString()), // Marker lat
Text(markers[index].position!.longitude.toString()), // Marker lon
],
),
);
},
),
),
],
),
);
}
}
ListView 추가시 아래와 같이 Marker를 추가할때마다 ListView에 마커들의 정보가 나타난다.
2. Marker 이름 및 InfoWindow 설정
Marker를 추가할때 LatLng 뿐만 아니라. Name, CreatedAt 을 추가하여 Marker별로 구분할 수 있도록 하자.
우선 Home.dart의 코드가 너무 길어져 NaverMap과 ListView 위젯을 각각 MapWidget.dart와 MarkerList.dart로 구분하였다.
수정된 Home.dart는 아래와 같다.
class Home extends StatelessWidget {
const Home({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Flutter Weather App'),
),
body: Column(
children: [
MapWidget(),
MarkerList(),
],
),
);
}
}
MapWidget.dart와 MarkerList.dart 코드는 각각 아래와 같다.
MapWidget.dart
import 'package:flutter/material.dart';
import 'package:ftweather/provider/MapModel.dart';
import 'package:naver_map_plugin/naver_map_plugin.dart';
import 'package:provider/provider.dart';
class MapWidget extends StatelessWidget {
const MapWidget({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final mapPosition = Provider.of<MapModel>(context).mapPosition;
final model = Provider.of<MapModel>(context, listen: false);
final markers = Provider.of<MapModel>(context).markers;
return Expanded(
flex: 6,
child: Stack(
children: [
NaverMap(
// NaverMapController를 model에 전달
onMapCreated: (controller) {
model.setController(controller);
},
mapType: MapType.Basic,
locationButtonEnable: true,
initialCameraPosition: mapPosition, // MyModel에서 가져온 mapPosition 사용
// initLocationTrackingMode: LocationTrackingMode.Follow, // 권한 획득 시 현재 위치를 따라가는 모드 설정
markers: markers, // MapModel에서 가져온 markers 사용
),
Positioned(
bottom: 16,
right: 16,
child: FloatingActionButton(
onPressed: () {
// addMarker() 메서드를 호출하여 현재 위치에 마커 추가
Provider.of<MapModel>(context, listen: false).addMarker(context);
},
child: const Icon(Icons.add),
),
),
],
),
);
}
}
MarkerList.dart : 텍스트와 구분선 관리를 위해 buildHeader와 buildDivider 위젯을 생성해서 사용
import 'package:flutter/material.dart';
import 'package:ftweather/provider/MapModel.dart';
import 'package:naver_map_plugin/naver_map_plugin.dart';
import 'package:provider/provider.dart';
class MarkerList extends StatelessWidget {
MarkerList({Key? key}) : super(key: key);
List<int> flexList = [20,40,30,15];
@override
Widget build(BuildContext context) {
final markers = Provider.of<MapModel>(context).markers;
return Expanded(
flex: 4,
child: Column(
children: [
Container(
padding: const EdgeInsets.symmetric(horizontal: 15.0, vertical: 10),
child: Row(
children: [
buildHeader('이름', flex: flexList[0], size: 13, weight: FontWeight.bold),
buildDivider(thick: 1),
buildHeader('경도, 위도', flex: flexList[1], size: 13, weight: FontWeight.bold),
buildDivider(thick: 1),
buildHeader('시간', flex: flexList[2], size: 13, weight: FontWeight.bold),
buildDivider(thick: 1),
buildHeader('삭제', flex: flexList[3], size: 13, weight: FontWeight.bold),
],
),
),
const Divider(color: Colors.black, thickness: 1,),
Expanded(
child: markerListView(markers),
),
],
),
);
}
ListView markerListView(List<Marker> markers) {
return ListView.separated(
separatorBuilder: (context, index) => const Divider(color:Colors.black),
itemCount: markers.length,
itemBuilder: (BuildContext context, int index) {
final markerInfo = markers[index].infoWindow!.split("\n");
final name = markerInfo[0];
final lat = markerInfo[1].substring(5);
final lng = markerInfo[2].substring(5);
final createdAt = markerInfo[3];
return ListTile(
title: Row(
children: [
buildHeader(name, flex: flexList[0], ),
buildDivider(),
buildHeader('$lat,\n$lng', flex: flexList[1], ),
buildDivider(),
buildHeader(createdAt, flex: flexList[2], ),
buildDivider(),
Expanded(flex: flexList[3],
child: IconButton(
onPressed: () {
Provider.of<MapModel>(context, listen: false).removeMarker(index);
},
icon: const Icon(Icons.delete_forever_rounded),
iconSize: 30,
),
),
],
),
);
},
);
}
Widget buildHeader(String text,
{int flex = 1, double size = 12, weight = FontWeight.normal, color = Colors.black}) {
return Expanded(
flex: flex,
child: Text(text,textAlign: TextAlign.center,
style: TextStyle(color: color, fontSize: size, fontWeight: weight)
)
);
}
Widget buildDivider({double thick = 0.0}) {
return SizedBox(
height: 20,
child: VerticalDivider(
color: Colors.black,
width: 1,
thickness: thick,
),
);
}
}
MarkerList에서는 각 Marker의 InfoWindow 내용을 가져와서 split 해서 출력하였다.
우측에는 삭제버튼을 추가해서 onPressed시 해당 index의 marker를 markers에서 삭제하도록 구현했다.
removeMarker와 infowindow를 구현을 추가한 MapModel.dart는 아래와 같다.
import 'package:flutter/material.dart';
import 'package:naver_map_plugin/naver_map_plugin.dart';
import 'dart:developer';
import 'package:geolocator/geolocator.dart';
class MapModel extends ChangeNotifier {
final TAG = "ftweather"; // 로그 태그
// 초기 카메라 위치 설정
final CameraPosition _mapPosition = const CameraPosition(
target: LatLng(37.5666805, 126.9784147),
zoom: 15,
);
// 카메라 위치를 가져오는 getter
CameraPosition get mapPosition => _mapPosition;
// NaverMapController 변수
NaverMapController? _controller;
// 마커 목록 관리
final List<Marker> _markers = [];
List<Marker> get markers => _markers;
// NaverMapController 초기화 메서드
void setController(NaverMapController controller) {
_controller = controller;
}
// 현재 위치에 마커를 추가하는 메서드
Future<void> addMarker(BuildContext context) async {
// 로그 출력
log("$TAG : addMarker");
final TextEditingController nameController = TextEditingController();
final formKey = GlobalKey<FormState>();
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text("Marker 추가"),
content: Form(
key: formKey,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextFormField(
controller: nameController,
validator: (value) {
if (value == null || value.isEmpty) {
return "Name cannot be empty";
}
return null;
},
),
const SizedBox(height: 16),
TextButton(
onPressed: () {
if (formKey.currentState!.validate()) {
Navigator.of(context).pop();
_createMarker(nameController.text);
}
},
child: const Text("추가하기"),
),
],
),
),
);
},
);
}
// 마커 생성 및 목록에 추가하는 메서드
void _createMarker(String name) async {
// NaverMapController가 초기화되었는지 확인
if (_controller != null) {
// 현재 카메라 위치 가져오기
CameraPosition currentCameraPosition = await _controller!.getCameraPosition();
// 로그 출력
log("$TAG : Camera Pos $currentCameraPosition");
final now = DateTime.now();
final timeWithoutMicroseconds = DateTime(now.year, now.month, now.day, now.hour, now.minute, now.second);
final createdAt = timeWithoutMicroseconds.toString().replaceAll('.000', '');
// 현재 위치에 마커 추가
final marker = Marker(
markerId: (_markers.length.toString()),
position: currentCameraPosition.target,
captionText: name,
infoWindow: "$name\n"
"lat: ${currentCameraPosition.target.latitude.toStringAsFixed(7)}\n"
"lon: ${currentCameraPosition.target.longitude.toStringAsFixed(7)}\n"
"$createdAt",
);
Position position = await Geolocator.getCurrentPosition(desiredAccuracy: LocationAccuracy.high);
log("$TAG : Real Pos $position");
// 마커 목록에 추가
_markers.add(marker);
}
// 마커가 추가되었음을 알림
notifyListeners();
}
void removeMarker(int index) {
_markers.removeAt(index);
notifyListeners();
}
}
해당 기능들을 통해 마커를 추가하고 삭제하는 날씨앱을 구현하였다.
'프로젝트들 > 날씨앱' 카테고리의 다른 글
[날씨앱] P8 CameraMove 추가 & NaverMaps 플러그인 변경 (0) | 2023.04.19 |
---|---|
[날씨앱] P7. Flutter Shared Preferences 추가 (0) | 2023.04.14 |
[날씨앱] P5. Flutter file, func 분류 (0) | 2023.04.07 |
[날씨앱] P4. Flutter fcm 알림 기능 추가 (iOS) (0) | 2023.04.06 |
[날씨앱] P3. Flutter fcm 알림 기능 추가 (Android) (0) | 2023.04.04 |