지난번에 구현한 구글 지도를 다른 패키지를 사용해서 구현해 보겠습니다. 로딩 및 drag 기능을 추가해보겠습니다.
개발환경 : 윈도우11, 안드로이드 스튜디오, flutter 3.0.1
구현 화면은 아래와 같습니다. - 게시글 제목이 같은 동일한 게시글입니다. 다만, 이미지는 랜덤이라서 다를수 있습니다.
첫번째 스크린 - map/latlng 패키지로 구현
두번째 스크린 - google_maps_flutter 패키지로 구현
세번째 스크린 - 지도에서 클릭한 마커의 상세 페이지
새로운 패키지(google_maps_flutter)에는 강력한 기능들이 많지만 오늘은 기본적인 부분만 확인해 보았습니다.
google_maps_flutter 패키지로 구현한 화면은 화면 이동이 조금 더 부드럽다, 하지만 이미지 사이즈를 변경하려면 비트맵이미지를 변경해야해서, 이미지 편집이 복잡하고 제약이 많다.
map/latlng 패키지로 구현한 경우는 일반 위젯을 마커로 사용이 가능해서(위도/경도 정보를 x,y 정보로 변경이 필요하지만) decoration 을 이용해서 쉽게 이미지 편집이 용이하고, 다른 위젯과 합쳐서 다양한 정보를 표시가 가능하다.
테스트 장치는 삼성 A12 입니다.
관련 패키지들
map: ^1.3.3
latlng: ^0.2.0
google_maps_flutter: ^2.1.12
./src/models/item_model.dart - 변수추가
class ItemModel2 {
String itemKey;
String userKey;
String userPhone;
List<String> imageDownloadUrls;
String title;
String category;
num price;
bool negotiable;
String detail;
String address;
GeoFirePoint geoFirePoint;
DateTime createdDate;
DocumentReference? reference;
Uint8List? iconBytes;
./src/screens/main_screen.dart - bottomNavigationBarItem 추가
body: IndexedStack(
index: _bottomSelectedIndex,
children: <Widget>[
const ItemsScreen(),
(UserController.to.userModel.value == null)
? Container()
: MapScreen(UserController.to.userModel.value!),
(UserController.to.userModel.value == null)
? Container()
: GoogleMapScreen(UserController.to.userModel.value!),
Container(color: Colors.accents[3]),
Container(color: Colors.accents[5]),
Container(color: Colors.accents[7]),
],
),
bottomNavigationBar: BottomNavigationBar(
// 아이콘이 선택되지 않아도 label 이 보이게 하는 옵션
// shifting 으로 설정하면 클릭시에만 label 이 보임,
type: BottomNavigationBarType.fixed,
// 아이콘이 클릭되면 onTap 이 실행되고, 이걸 currentIndex 에 전달해야 함
onTap: (index) {
setState(() {
debugPrint('BottomNavigationBar(index): $index');
_bottomSelectedIndex = index;
});
},
// 클릭된 화면으로 이동하려면 매핑해야함
currentIndex: _bottomSelectedIndex,
// free icons : flaticon.com 에서 다운로드
items: [
// 아이콘이 클릭되면 onTap 이 실행됨,
BottomNavigationBarItem(
icon: ImageIcon(AssetImage(_bottomSelectedIndex == 0
? 'assets/imgs/house_filled.png'
: 'assets/imgs/house.png')),
label: 'home',
),
BottomNavigationBarItem(
icon: ImageIcon(AssetImage(_bottomSelectedIndex == 1
? 'assets/imgs/near-me_filled.png'
: 'assets/imgs/near-me.png')),
label: 'near',
),
BottomNavigationBarItem(
icon: ImageIcon(AssetImage(_bottomSelectedIndex == 2
? 'assets/imgs/near-me_filled.png'
: 'assets/imgs/near-me.png')),
label: 'near2',
),
BottomNavigationBarItem(
icon: ImageIcon(AssetImage(_bottomSelectedIndex == 3
? 'assets/imgs/chat_filled.png'
: 'assets/imgs/chat.png')),
label: 'chat',
),
BottomNavigationBarItem(
// backgroundColor: Theme.of(context).bottomNavigationBarTheme.selectedItemColor,
icon: ImageIcon(AssetImage(_bottomSelectedIndex == 4
? 'assets/imgs/user_filled.png'
: 'assets/imgs/user.png')),
label: 'me',
),
],
),
./src/repo/item_service.dart - 신규 화면에 필요한 함수 추가
Future<List<ItemModel2>> getNearByItemsGoogle(String userKey, LatLng latLng) async {
// GeoFlutterFire is an open-source library that allows you to store
// and query firestore documents based on their geographic location.
final geo = Geoflutterfire();
final itemCol = FirebaseFirestore.instance.collection(COL_ITEMS);
GeoFirePoint center = GeoFirePoint(latLng.latitude, latLng.longitude);
double radius = 2; // unit is km
var field = 'geoFirePoint';
// within - 리턴 타입 Stream, first - 리턴 타입 Future,
List<DocumentSnapshot<Map<String, dynamic>>> snapshots = await geo
.collection(collectionRef: itemCol)
.within(center: center, radius: radius, field: field)
.first;
List<ItemModel2> items = [];
for (var snapshot in snapshots) {
// ItemModel2 itemModel = ItemModel2.fromSnapshot(snapshot);
ItemModel2 itemModel = ItemModel2.fromJson(snapshot.data()!);
itemModel.itemKey = snapshot.id;
itemModel.reference = snapshot.reference;
String imgUrl = itemModel.imageDownloadUrls[0];
itemModel.iconBytes = await getBytesFromNetwork(imgUrl: imgUrl, width: 48);
// (await NetworkAssetBundle(Uri.parse(imgUrl)).load(imgUrl)).buffer.asUint8List();
//todo: remove my own item
// print(
// 'myUserKey[${userKey}], itemUserKey[${itemModel.userKey}][${itemModel.geoFirePoint.latitude}][${itemModel.geoFirePoint.longitude}]');
if (itemModel.userKey != userKey) {
items.add(itemModel);
}
}
return items;
}
// 이미지 사이즈 변경 가능, for asset ,
Future<Uint8List> getBytesFromAsset({required String path, required int width}) async {
ByteData data = await rootBundle.load(path);
ui.Codec codec = await ui.instantiateImageCodec(data.buffer.asUint8List(), targetWidth: width);
ui.FrameInfo fi = await codec.getNextFrame();
return (await fi.image.toByteData(format: ui.ImageByteFormat.png))!.buffer.asUint8List();
}
// 이미지 사이즈 변경 가능, for networkImage
Future<Uint8List> getBytesFromNetwork({required String imgUrl, required int width}) async {
ByteData data = await NetworkAssetBundle(Uri.parse(imgUrl)).load(imgUrl);
ui.Codec codec = await ui.instantiateImageCodec(
data.buffer.asUint8List(),
targetWidth: width,
targetHeight: width,
);
ui.FrameInfo fi = await codec.getNextFrame();
return (await fi.image.toByteData(format: ui.ImageByteFormat.png))!.buffer.asUint8List();
}
./src/screens/near/google_map_screen.dart - 다른 패키지로 만든 화면
import 'dart:async';
import 'package:apple_market3/src/models/user_model.dart';
import 'package:flutter/material.dart';
// LatLng 함수가 google_maps_flutter 와 충돌되어 별칭 추가. 변경 이유는 실사용 부분 참고,
import 'package:latlng/latlng.dart' as map_lat_lng;
import 'package:get/get.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import '../../constants/data_keys.dart';
import '../../models/item_model.dart';
import '../../repo/item_service.dart';
import '../../utils/logger.dart';
class GoogleMapScreen extends StatefulWidget {
final UserModel1 _userModel;
const GoogleMapScreen(this._userModel, {Key? key}) : super(key: key);
@override
State<GoogleMapScreen> createState() => _GoogleMapScreenState();
}
class _GoogleMapScreenState extends State<GoogleMapScreen> {
final Completer<GoogleMapController> _googleMapController = Completer();
MapType _googleMapType = MapType.normal;
int _mapType = 0;
final Set<Marker> _markers = {};
late final CameraPosition _initialCameraPosition;
Marker? userMarker;
@override
void initState() {
// 초기 맵 위치 설정
_initialCameraPosition = CameraPosition(
target: LatLng(
widget._userModel.geoFirePoint.latitude,
widget._userModel.geoFirePoint.longitude,
),
zoom: 14,
);
userMarker = setMarker(
latLng: LatLng(
widget._userModel.geoFirePoint.latitude,
widget._userModel.geoFirePoint.longitude,
),
color: BitmapDescriptor.hueBlue,
);
_markers.add(userMarker!);
super.initState();
}
@override
void dispose() {
// TODO: implement dispose
super.dispose();
}
Marker setMarker({required LatLng latLng, required double color}) {
return Marker(
markerId: const MarkerId('myInitPosition'),
position: latLng,
infoWindow: const InfoWindow(title: 'My Position', snippet: 'Where am I?'),
icon: BitmapDescriptor.defaultMarkerWithHue(color),
);
}
// 마커 위치/색상 설정, x/y 자표로 입력
Widget _buildMarkerWidget(Offset offset, {Color color = Colors.red}) {
return Positioned(
left: offset.dx,
top: offset.dy,
width: 32,
height: 32,
child: Icon(
Icons.location_on,
color: color,
),
);
}
void _onMapCreated(GoogleMapController controller) {
// 이제 이 콘트롤을 프로그램애서 사용하기 위한 준비 완료.
_googleMapController.complete(controller);
}
// 지도 타입 설정,
void _changeMapType() {
setState(() {
_mapType++;
_mapType = _mapType % 4;
switch (_mapType) {
case 0:
_googleMapType = MapType.normal;
break;
case 1:
_googleMapType = MapType.satellite;
break;
case 2:
_googleMapType = MapType.terrain;
break;
case 3:
_googleMapType = MapType.hybrid;
break;
default:
_googleMapType = MapType.normal;
break;
}
});
}
@override
Widget build(BuildContext context) {
// 여기서 mapLatLng 별칭 처리한 이유는 getNearByItemsGoogle 함수에서 사용하는 LatLng 버전이 달라서임,
// 해당 LatLng 타입은 google_maps_flutter.dart 것이 아니고 latlng.dart 것을 참고하기때문입니다,
var myLatLng = map_lat_lng.LatLng(
widget._userModel.geoFirePoint.latitude,
widget._userModel.geoFirePoint.longitude,
);
// 화면 중심을 표시하려하는데, 자꾸 아랫쪽으로 치우친다.
Size _size = MediaQuery.of(context).size;
final middleOnScreen = Offset(_size.width / 2, _size.height / 2);
// logger.d('middleOnScreen: $middleOnScreen');
return Scaffold(
body: FutureBuilder<List<ItemModel2>>(
future: ItemService().getNearByItemsGoogle(widget._userModel.userKey, myLatLng),
builder: (context, snapshot) {
_markers.clear();
// 로그인 사용자 위치를 화면에 추가합니다.
_markers.add(userMarker!);
// _markers.add(setMarker(latLng: _currentCenter, color: BitmapDescriptor.hueOrange));
if (snapshot.hasData) {
for (var item in snapshot.data!) {
// final offset = transformer
// .toOffset(LatLng(item.geoFirePoint.latitude, item.geoFirePoint.longitude));
// nearByItems.add(_buildImgWidget(offset, item, myLatLng));
_markers.add(
Marker(
// markerId: MarkerId(foundPlaces[i]['id']),
markerId: MarkerId(item.itemKey),
position: LatLng(
item.geoFirePoint.latitude,
item.geoFirePoint.longitude,
),
infoWindow: InfoWindow(
title: item.title,
snippet: 'test',
),
icon: BitmapDescriptor.fromBytes(item.iconBytes!),
//Icon for Marker
onTap: () {
logger.d('Marker clicked:[${item.title}]');
Get.toNamed(ROUTE_ITEM_DETAIL,
arguments: {'itemKey': item.itemKey},
// 같은 페이지는 호출시, 중복방지가 기본설정인, false 하면 중복 호출 가능,
preventDuplicates: false);
},
),
);
}
}
return Stack(
children: <Widget>[
GoogleMap(
mapType: _googleMapType,
initialCameraPosition: _initialCameraPosition,
onMapCreated: _onMapCreated,
myLocationEnabled: true,
markers: _markers,
),
Container(
margin: const EdgeInsets.only(top: 60, right: 10),
alignment: Alignment.topRight,
child: Column(
children: <Widget>[
FloatingActionButton.extended(
heroTag: 'btn_googleMap',
label: Text('$_googleMapType'),
icon: const Icon(Icons.map),
elevation: 8,
backgroundColor: Colors.red[400],
onPressed: _changeMapType,
),
],
),
),
_buildMarkerWidget(middleOnScreen),
],
);
},
),
);
}
}
'Flutter > 12 Clone 'Used Goods app'' 카테고리의 다른 글
[Flutter] Clone - 당근마켓49(Chat - 1) (1) | 2022.09.01 |
---|---|
[Flutter] Clone - 당근마켓48(chatroomModel, chatModel) (0) | 2022.08.31 |
[Flutter] Clone - 당근마켓46(Map - 3) (0) | 2022.08.26 |
[Flutter] Clone - 당근마켓45(Map - 2) (0) | 2022.08.26 |
[Flutter] Clone - 당근마켓44(Map - 1) (0) | 2022.08.25 |