Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.

Commit a705bd5

Browse files
committed
[google_maps_flutter] custom marker sizing
1 parent 9302d87 commit a705bd5

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+1536
-450
lines changed

packages/google_maps_flutter/google_maps_flutter/AUTHORS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,4 @@ Anton Borries <mail@antonborri.es>
6565
Alex Li <google@alexv525.com>
6666
Rahul Raj <64.rahulraj@gmail.com>
6767
Taha Tesser <tesser@gmail.com>
68+
Joonas Kerttula <joonas.kerttula@codemate.com>

packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1-
## NEXT
1+
## 2.3.0
22

3+
* Adds better support for marker size and scaling behaviour with `BitmapDescriptor.createFromAsset` and `BitmapDescriptor.createFromBytes`.
4+
* Deprecates `BitmapDescriptor.fromAssetImage` in favor of `BitmapDescriptor.createFromAsset`
5+
* Deprecates `BitmapDescriptor.fromBytes` in favor of `BitmapDescriptor.createFromBytes`
36
* Updates minimum Flutter version to 3.0.
47

58
## 2.2.3

packages/google_maps_flutter/google_maps_flutter/example/integration_test/google_maps_test.dart

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -940,17 +940,21 @@ void main() {
940940
expect(iwVisibleStatus, false);
941941
});
942942

943-
testWidgets('fromAssetImage', (WidgetTester tester) async {
943+
testWidgets('createFromAsset', (WidgetTester tester) async {
944944
const double pixelRatio = 2;
945945
const ImageConfiguration imageConfiguration =
946946
ImageConfiguration(devicePixelRatio: pixelRatio);
947-
final BitmapDescriptor mip = await BitmapDescriptor.fromAssetImage(
948-
imageConfiguration, 'red_square.png');
949-
final BitmapDescriptor scaled = await BitmapDescriptor.fromAssetImage(
950-
imageConfiguration, 'red_square.png',
951-
mipmaps: false);
952-
expect((mip.toJson() as List<dynamic>)[2], 1);
953-
expect((scaled.toJson() as List<dynamic>)[2], 2);
947+
final BitmapDescriptor mip = await BitmapDescriptor.createFromAsset(
948+
imageConfiguration,
949+
'red_square.png',
950+
);
951+
final BitmapDescriptor scaled = await BitmapDescriptor.createFromAsset(
952+
imageConfiguration,
953+
'red_square.png',
954+
mipmaps: false,
955+
);
956+
expect((mip.toJson() as List<dynamic>)[3], 1.0);
957+
expect((scaled.toJson() as List<dynamic>)[3], 2);
954958
});
955959

956960
testWidgets('testTakeSnapshot', (WidgetTester tester) async {
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
// ignore_for_file: public_member_api_docs
6+
7+
import 'dart:typed_data';
8+
import 'dart:ui' as ui;
9+
10+
import 'package:flutter/material.dart';
11+
12+
Future<ByteData> createCustomMarkerIconImage({required Size size}) async {
13+
final ui.PictureRecorder recorder = ui.PictureRecorder();
14+
final Canvas canvas = Canvas(recorder);
15+
final _MarkerPainter painter = _MarkerPainter();
16+
17+
painter.paint(canvas, size);
18+
19+
final ui.Image image = await recorder
20+
.endRecording()
21+
.toImage(size.width.floor(), size.height.floor());
22+
23+
final ByteData? bytes =
24+
await image.toByteData(format: ui.ImageByteFormat.png);
25+
return bytes!;
26+
}
27+
28+
class _MarkerPainter extends CustomPainter {
29+
@override
30+
void paint(Canvas canvas, Size size) {
31+
final Rect rect = Offset.zero & size;
32+
const RadialGradient gradient = RadialGradient(
33+
colors: <Color>[Colors.yellow, Colors.red],
34+
stops: <double>[0.4, 1.0],
35+
);
36+
canvas.drawRect(
37+
rect,
38+
Paint()..shader = gradient.createShader(rect),
39+
);
40+
}
41+
42+
@override
43+
bool shouldRepaint(_MarkerPainter oldDelegate) => false;
44+
@override
45+
bool shouldRebuildSemantics(_MarkerPainter oldDelegate) => false;
46+
}

packages/google_maps_flutter/google_maps_flutter/example/lib/marker_icons.dart

Lines changed: 211 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,14 @@
55
// ignore_for_file: public_member_api_docs
66
// ignore_for_file: unawaited_futures
77

8+
import 'dart:async';
9+
import 'dart:math';
10+
import 'dart:typed_data';
11+
812
import 'package:flutter/material.dart';
913
import 'package:google_maps_flutter/google_maps_flutter.dart';
1014

15+
import 'custom_marker_icon.dart';
1116
import 'page.dart';
1217

1318
class MarkerIconsPage extends GoogleMapExampleAppPage {
@@ -30,65 +35,235 @@ class MarkerIconsBody extends StatefulWidget {
3035
const LatLng _kMapCenter = LatLng(52.4478, -3.5402);
3136

3237
class MarkerIconsBodyState extends State<MarkerIconsBody> {
38+
final double _markerAssetImageSize = 48;
39+
final Size _customSize = const Size(55, 30);
40+
Set<Marker> _markers = <Marker>{};
41+
double _markerScale = 1.0;
42+
bool _scalingEnabled = true;
43+
bool _customSizeEnabled = false;
44+
bool _mipMapsEnabled = true;
3345
GoogleMapController? controller;
34-
BitmapDescriptor? _markerIcon;
46+
BitmapDescriptor? _markerIconAsset;
47+
BitmapDescriptor? _markerIconBytes;
48+
final int _markersAmountPerType = 15;
3549

3650
@override
3751
Widget build(BuildContext context) {
38-
_createMarkerImageFromAsset(context);
52+
_createCustomMarkerIconImages(context);
53+
final Size size = getCurrentMarkerSize();
3954
return Column(
4055
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
4156
crossAxisAlignment: CrossAxisAlignment.stretch,
4257
children: <Widget>[
43-
Center(
44-
child: SizedBox(
45-
width: 350.0,
46-
height: 300.0,
47-
child: GoogleMap(
48-
initialCameraPosition: const CameraPosition(
49-
target: _kMapCenter,
50-
zoom: 7.0,
58+
Column(children: <Widget>[
59+
Center(
60+
child: SizedBox(
61+
width: 350.0,
62+
height: 300.0,
63+
child: GoogleMap(
64+
initialCameraPosition: const CameraPosition(
65+
target: _kMapCenter,
66+
zoom: 7.0,
67+
),
68+
markers: _markers,
69+
onMapCreated: _onMapCreated,
5170
),
52-
markers: <Marker>{_createMarker()},
53-
onMapCreated: _onMapCreated,
5471
),
5572
),
56-
)
73+
TextButton(
74+
onPressed: () => _toggleScaling(context),
75+
child: Text(_scalingEnabled
76+
? 'Disable auto scaling'
77+
: 'Enable auto scaling'),
78+
),
79+
if (_scalingEnabled) ...<Widget>[
80+
Container(
81+
width: size.width,
82+
height: size.height,
83+
color: Colors.red,
84+
),
85+
Text(
86+
'Reference box with size of ${size.width} x ${size.height} in logical pixels.'),
87+
const SizedBox(height: 10),
88+
TextButton(
89+
onPressed: () => _toggleCustomSize(context),
90+
child: Text(_customSizeEnabled
91+
? 'Disable custom size'
92+
: 'Enable custom size'),
93+
),
94+
if (_customSizeEnabled)
95+
Row(
96+
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
97+
children: <Widget>[
98+
TextButton(
99+
onPressed: _markerScale <= 0.5
100+
? null
101+
: () => _decreaseScale(context),
102+
child: const Text(
103+
'-',
104+
textScaleFactor: 2,
105+
),
106+
),
107+
Text('scale ${_markerScale}x'),
108+
TextButton(
109+
onPressed: _markerScale >= 2.5
110+
? null
111+
: () => _increaseScale(context),
112+
child: const Text(
113+
'+',
114+
textScaleFactor: 2,
115+
),
116+
),
117+
],
118+
),
119+
],
120+
TextButton(
121+
onPressed: () => _toggleMipMaps(context),
122+
child: Text(_mipMapsEnabled ? 'Disable mipmaps' : 'Enable mipmaps'),
123+
),
124+
])
57125
],
58126
);
59127
}
60128

61-
Marker _createMarker() {
62-
if (_markerIcon != null) {
63-
return Marker(
64-
markerId: const MarkerId('marker_1'),
65-
position: _kMapCenter,
66-
icon: _markerIcon!,
67-
);
68-
} else {
69-
return const Marker(
70-
markerId: MarkerId('marker_1'),
71-
position: _kMapCenter,
72-
);
73-
}
129+
Size getCurrentMarkerSize() {
130+
return _scalingEnabled && _customSizeEnabled
131+
? _customSize * _markerScale
132+
: Size(_markerAssetImageSize, _markerAssetImageSize);
74133
}
75134

76-
Future<void> _createMarkerImageFromAsset(BuildContext context) async {
77-
if (_markerIcon == null) {
78-
final ImageConfiguration imageConfiguration =
79-
createLocalImageConfiguration(context, size: const Size.square(48));
80-
BitmapDescriptor.fromAssetImage(
81-
imageConfiguration, 'assets/red_square.png')
82-
.then(_updateBitmap);
83-
}
135+
void _toggleMipMaps(BuildContext context) {
136+
_mipMapsEnabled = !_mipMapsEnabled;
137+
_updateMarkerImages(context);
138+
}
139+
140+
void _toggleScaling(BuildContext context) {
141+
_scalingEnabled = !_scalingEnabled;
142+
_updateMarkerImages(context);
143+
}
144+
145+
void _toggleCustomSize(BuildContext context) {
146+
_customSizeEnabled = !_customSizeEnabled;
147+
_updateMarkerImages(context);
148+
}
149+
150+
void _decreaseScale(BuildContext context) {
151+
_markerScale = max(_markerScale - 0.5, 0.0);
152+
_updateMarkerImages(context);
153+
}
154+
155+
void _increaseScale(BuildContext context) {
156+
_markerScale = min(_markerScale + 0.5, 2.5);
157+
_updateMarkerImages(context);
158+
}
159+
160+
void _updateMarkerImages(BuildContext context) {
161+
_updateMarkerAssetImage(context);
162+
_updateMarkerBytesImage(context);
163+
_updateMarkers();
164+
}
165+
166+
Marker _createAssetMarker(int index) {
167+
final LatLng position =
168+
LatLng(_kMapCenter.latitude - (index * 0.5), _kMapCenter.longitude - 1);
169+
170+
return Marker(
171+
markerId: MarkerId('marker_asset_$index'),
172+
position: position,
173+
icon: _markerIconAsset!,
174+
);
175+
}
176+
177+
Marker _createBytesMarker(int index) {
178+
final LatLng position =
179+
LatLng(_kMapCenter.latitude - (index * 0.5), _kMapCenter.longitude + 1);
180+
181+
return Marker(
182+
markerId: MarkerId('marker_bytes_$index'),
183+
position: position,
184+
icon: _markerIconBytes!,
185+
);
84186
}
85187

86-
void _updateBitmap(BitmapDescriptor bitmap) {
188+
void _updateMarkers() {
189+
final Set<Marker> markers = <Marker>{};
190+
for (int i = 0; i < _markersAmountPerType; i++) {
191+
if (_markerIconAsset != null) {
192+
markers.add(_createAssetMarker(i));
193+
}
194+
if (_markerIconBytes != null) {
195+
markers.add(_createBytesMarker(i));
196+
}
197+
}
87198
setState(() {
88-
_markerIcon = bitmap;
199+
_markers = markers;
89200
});
90201
}
91202

203+
Future<void> _updateMarkerAssetImage(BuildContext context) async {
204+
final Size? size =
205+
_scalingEnabled && _customSizeEnabled ? getCurrentMarkerSize() : null;
206+
final ImageConfiguration imageConfiguration = createLocalImageConfiguration(
207+
context,
208+
size: size,
209+
);
210+
BitmapDescriptor.createFromAsset(
211+
imageConfiguration, 'assets/red_square.png',
212+
mipmaps: _mipMapsEnabled,
213+
imagePixelRatio: _mipMapsEnabled ? null : 1.0,
214+
bitmapScaling:
215+
_scalingEnabled ? BitmapScaling.auto : BitmapScaling.noScaling)
216+
.then(_updateAssetBitmap);
217+
}
218+
219+
Future<void> _updateMarkerBytesImage(BuildContext context) async {
220+
final double devicePixelRatio =
221+
WidgetsBinding.instance.window.devicePixelRatio;
222+
223+
final Size markerSize = getCurrentMarkerSize();
224+
225+
final double? imagePixelRatio = _scalingEnabled ? devicePixelRatio : null;
226+
227+
// Create canvasSize with physical marker size
228+
final Size canvasSize = Size(markerSize.width * (imagePixelRatio ?? 1.0),
229+
markerSize.height * (imagePixelRatio ?? 1.0));
230+
231+
final ByteData bytes = await createCustomMarkerIconImage(size: canvasSize);
232+
233+
// Size is used only for custom size
234+
final Size? size =
235+
_scalingEnabled && _customSizeEnabled ? getCurrentMarkerSize() : null;
236+
237+
final BitmapDescriptor bitmap = BitmapDescriptor.createFromBytes(
238+
bytes.buffer.asUint8List(),
239+
imagePixelRatio: _customSizeEnabled ? null : imagePixelRatio,
240+
size: size,
241+
bitmapScaling:
242+
_scalingEnabled ? BitmapScaling.auto : BitmapScaling.noScaling);
243+
244+
_updateBytesBitmap(bitmap);
245+
}
246+
247+
void _updateAssetBitmap(BitmapDescriptor bitmap) {
248+
_markerIconAsset = bitmap;
249+
_updateMarkers();
250+
}
251+
252+
void _updateBytesBitmap(BitmapDescriptor bitmap) {
253+
_markerIconBytes = bitmap;
254+
_updateMarkers();
255+
}
256+
257+
void _createCustomMarkerIconImages(BuildContext context) {
258+
if (_markerIconAsset == null) {
259+
_updateMarkerAssetImage(context);
260+
}
261+
262+
if (_markerIconBytes == null) {
263+
_updateMarkerBytesImage(context);
264+
}
265+
}
266+
92267
void _onMapCreated(GoogleMapController controllerParam) {
93268
setState(() {
94269
controller = controllerParam;

0 commit comments

Comments
 (0)