BitmapDescriptor.fromBytes() 尝试在 google 地图上显示自定义标记时不起作用
BitmapDescriptor.fromBytes() is not working when trying to show custom marker on google maps
我正在尝试将自定义图像显示为 google 地图上的标记。问题是 BitmapDescriptor.fromAssetImage() 工作正常,但 BitmapDescriptor.fromBytes() 不工作。因为我必须进一步使用 canvas,所以我需要使用 BitmapDescriptor.fromBytes()。任何帮助表示赞赏。下面是完整的代码。
class AqiMapPage extends StatefulWidget {
@override
_AqiMapPageState createState() => _AqiMapPageState();
}
Future<BitmapDescriptor> getCustomMapMarker(int aqi) async {
Levels levels = AqiCnAqiRange.getAqiLevel(aqi);
// this is working
/* return await BitmapDescriptor.fromAssetImage(
ImageConfiguration(devicePixelRatio: 2.5),
'assets/markers/${levels.markerIcon}');*/
// this is not working
return await _getAssetIcon(_context, 'assets/markers/${levels.markerIcon}');
}
Future<BitmapDescriptor> _getAssetIcon(BuildContext context, String imageUrl) async {
final Completer<BitmapDescriptor> bitmapIcon =
Completer<BitmapDescriptor>();
final ImageConfiguration config = createLocalImageConfiguration(context);
AssetImage(imageUrl)
.resolve(config)
.addListener(ImageStreamListener((ImageInfo image, bool sync) async {
final ByteData bytes =
await image.image.toByteData(format: ImageByteFormat.png);
final BitmapDescriptor bitmap =
BitmapDescriptor.fromBytes(bytes.buffer.asUint8List());
bitmapIcon.complete(bitmap);
}));
return await bitmapIcon.future;
}
Future<Set<Marker>> getMarkerList(List<MapData> mapData) async {
Set<Marker> markerList = Set();
mapData.forEach((element) async {
try {
BitmapDescriptor bitmapDescriptor =
await getCustomMapMarker(int.parse(element.aqi));
Marker marker = new Marker(
markerId: MarkerId(element.aqi),
position: LatLng(element.lat, element.lon),
icon: bitmapDescriptor,
);
markerList.add(marker);
} catch (e) {
print(e.toString());
}
});
return markerList;
}
Completer<GoogleMapController> _controller;
BuildContext _context;
final _scaffoldKey = GlobalKey<ScaffoldState>();
class _AqiMapPageState extends State<AqiMapPage> {
final GlobalKey<RefreshIndicatorState> _refreshIndicatorKey =
new GlobalKey<RefreshIndicatorState>();
AqiCnMapBloc _bloc;
@override
void initState() {
super.initState();
_controller = Completer();
_bloc = AqiCnMapBloc();
_bloc.getAqiCnMap(false);
}
@override
Widget build(BuildContext context) {
_context = context;
return Scaffold(
key: _scaffoldKey,
appBar: AppBar(
title: Text(AppLocalizations.of(context)
.getString(AppStringKeys.AQI_MAP_PAGE_KEY)),
centerTitle: true,
),
backgroundColor: HexColor.fromHex(AppColors.scaffoldBackgroundColor),
body: new RefreshIndicator(
key: _refreshIndicatorKey,
onRefresh: () => _bloc.getAqiCnMap(true),
child: StreamBuilder<Response<AqiCnMapApiResponse>>(
stream: _bloc.aqiCnMapStream,
builder: (context, snapshot) {
if (snapshot.hasData) {
switch (snapshot.data.status) {
case Status.LOADING:
return Loading(loadingKey: snapshot.data.message);
break;
case Status.SUCCESS:
return AqiMapData(mapData: snapshot.data.data);
break;
case Status.ERROR:
return Error(
error: snapshot.data.message,
onRetryPressed: () => _bloc.getAqiCnMap(false),
);
break;
}
}
return Container();
},
),
),
);
}
}
class AqiMapData extends StatelessWidget {
final AqiCnMapApiResponse mapData;
const AqiMapData({Key key, this.mapData}) : super(key: key);
@override
Widget build(BuildContext context) {
return FutureBuilder<Set<Marker>>(
future: getMarkerList(mapData.mapData),
builder: (BuildContext context, AsyncSnapshot<Set<Marker>> snapshot) {
if (!snapshot.hasData) {
// while data is loading:
return LoadingWithoutText();
} else {
// data loaded:
return GoogleMap(
mapType: MapType.hybrid,
myLocationEnabled: true,
// fixme - fix lat lng
initialCameraPosition:
CameraPosition(target: LatLng(25.6185024, 85.0726964), zoom: 3),
markers: snapshot.data,
onMapCreated: (GoogleMapController controller) {
_controller.complete(controller);
},
);
}
},
);
}
}
我找到了解决办法。虽然我不确定上面的代码有什么问题。但这似乎是某种同步或渲染问题。使用 setState() 我可以使用 BitmapDescriptor.fromBytes()。下面是我的代码。
class AqiMapPage extends StatefulWidget {
@override
_AqiMapPageState createState() => _AqiMapPageState();
}
final _scaffoldKey = GlobalKey<ScaffoldState>();
class _AqiMapPageState extends State<AqiMapPage> {
final GlobalKey<RefreshIndicatorState> _refreshIndicatorKey =
new GlobalKey<RefreshIndicatorState>();
AqiCnMapBloc _bloc;
@override
void initState() {
super.initState();
_bloc = AqiCnMapBloc();
_bloc.getAqiCnMap(false);
}
@override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
appBar: AppBar(
title: Text(AppLocalizations.of(context)
.getString(AppStringKeys.AQI_MAP_PAGE_KEY)),
centerTitle: true,
),
backgroundColor: HexColor.fromHex(AppColors.scaffoldBackgroundColor),
body: new RefreshIndicator(
key: _refreshIndicatorKey,
onRefresh: () => _bloc.getAqiCnMap(true),
child: StreamBuilder<Response<AqiCnMapApiResponse>>(
stream: _bloc.aqiCnMapStream,
builder: (context, snapshot) {
if (snapshot.hasData) {
switch (snapshot.data.status) {
case Status.LOADING:
return Loading(loadingKey: snapshot.data.message);
break;
case Status.SUCCESS:
return AqiMapData(mapData: snapshot.data.data);
break;
case Status.ERROR:
return Error(
error: snapshot.data.message,
onRetryPressed: () => _bloc.getAqiCnMap(false),
);
break;
}
}
return Container();
},
),
),
);
}
}
class AqiMapData extends StatefulWidget {
final AqiCnMapApiResponse mapData;
const AqiMapData({Key key, this.mapData}) : super(key: key);
@override
_AqiMapDataState createState() => _AqiMapDataState(mapData);
}
class _AqiMapDataState extends State<AqiMapData> {
final AqiCnMapApiResponse mapData;
_AqiMapDataState(this.mapData);
Set<Marker> _markers = Set();
Completer<GoogleMapController> _controller = Completer();
static Future<Uint8List> getBytesFromAsset(String path, 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: ImageByteFormat.png))
.buffer
.asUint8List();
}
populateMarkers() {
mapData.mapData.forEach((element) async {
try {
final MarkerId markerId = MarkerId(element.uid.toString());
Levels levels = AqiCnAqiRange.getAqiLevel(int.parse(element.aqi));
final Uint8List markerIcon = await getBytesFromAsset('assets/markers/${levels.markerIcon}', 100);
// creating a new MARKER
final Marker marker = new Marker(
icon: BitmapDescriptor.fromBytes(markerIcon),
markerId: markerId,
position: LatLng(element.lat, element.lon),
infoWindow: InfoWindow(
title: element.station.name, snippet: element.station.time),
);
// the solution
setState(() {
// adding a new marker to map
_markers.add(marker);
});
} catch (e) {
print(e.toString());
}
});
}
@override
void initState() {
super.initState();
populateMarkers();
}
@override
Widget build(BuildContext context) {
return GoogleMap(
mapType: MapType.hybrid,
myLocationEnabled: true,
// fixme - fix lat lng
initialCameraPosition:
CameraPosition(target: LatLng(25.6185024, 85.0726964), zoom: 3),
markers: _markers,
onMapCreated: (GoogleMapController controller) {
_controller.complete(controller);
},
);
}
}
我正在尝试将自定义图像显示为 google 地图上的标记。问题是 BitmapDescriptor.fromAssetImage() 工作正常,但 BitmapDescriptor.fromBytes() 不工作。因为我必须进一步使用 canvas,所以我需要使用 BitmapDescriptor.fromBytes()。任何帮助表示赞赏。下面是完整的代码。
class AqiMapPage extends StatefulWidget {
@override
_AqiMapPageState createState() => _AqiMapPageState();
}
Future<BitmapDescriptor> getCustomMapMarker(int aqi) async {
Levels levels = AqiCnAqiRange.getAqiLevel(aqi);
// this is working
/* return await BitmapDescriptor.fromAssetImage(
ImageConfiguration(devicePixelRatio: 2.5),
'assets/markers/${levels.markerIcon}');*/
// this is not working
return await _getAssetIcon(_context, 'assets/markers/${levels.markerIcon}');
}
Future<BitmapDescriptor> _getAssetIcon(BuildContext context, String imageUrl) async {
final Completer<BitmapDescriptor> bitmapIcon =
Completer<BitmapDescriptor>();
final ImageConfiguration config = createLocalImageConfiguration(context);
AssetImage(imageUrl)
.resolve(config)
.addListener(ImageStreamListener((ImageInfo image, bool sync) async {
final ByteData bytes =
await image.image.toByteData(format: ImageByteFormat.png);
final BitmapDescriptor bitmap =
BitmapDescriptor.fromBytes(bytes.buffer.asUint8List());
bitmapIcon.complete(bitmap);
}));
return await bitmapIcon.future;
}
Future<Set<Marker>> getMarkerList(List<MapData> mapData) async {
Set<Marker> markerList = Set();
mapData.forEach((element) async {
try {
BitmapDescriptor bitmapDescriptor =
await getCustomMapMarker(int.parse(element.aqi));
Marker marker = new Marker(
markerId: MarkerId(element.aqi),
position: LatLng(element.lat, element.lon),
icon: bitmapDescriptor,
);
markerList.add(marker);
} catch (e) {
print(e.toString());
}
});
return markerList;
}
Completer<GoogleMapController> _controller;
BuildContext _context;
final _scaffoldKey = GlobalKey<ScaffoldState>();
class _AqiMapPageState extends State<AqiMapPage> {
final GlobalKey<RefreshIndicatorState> _refreshIndicatorKey =
new GlobalKey<RefreshIndicatorState>();
AqiCnMapBloc _bloc;
@override
void initState() {
super.initState();
_controller = Completer();
_bloc = AqiCnMapBloc();
_bloc.getAqiCnMap(false);
}
@override
Widget build(BuildContext context) {
_context = context;
return Scaffold(
key: _scaffoldKey,
appBar: AppBar(
title: Text(AppLocalizations.of(context)
.getString(AppStringKeys.AQI_MAP_PAGE_KEY)),
centerTitle: true,
),
backgroundColor: HexColor.fromHex(AppColors.scaffoldBackgroundColor),
body: new RefreshIndicator(
key: _refreshIndicatorKey,
onRefresh: () => _bloc.getAqiCnMap(true),
child: StreamBuilder<Response<AqiCnMapApiResponse>>(
stream: _bloc.aqiCnMapStream,
builder: (context, snapshot) {
if (snapshot.hasData) {
switch (snapshot.data.status) {
case Status.LOADING:
return Loading(loadingKey: snapshot.data.message);
break;
case Status.SUCCESS:
return AqiMapData(mapData: snapshot.data.data);
break;
case Status.ERROR:
return Error(
error: snapshot.data.message,
onRetryPressed: () => _bloc.getAqiCnMap(false),
);
break;
}
}
return Container();
},
),
),
);
}
}
class AqiMapData extends StatelessWidget {
final AqiCnMapApiResponse mapData;
const AqiMapData({Key key, this.mapData}) : super(key: key);
@override
Widget build(BuildContext context) {
return FutureBuilder<Set<Marker>>(
future: getMarkerList(mapData.mapData),
builder: (BuildContext context, AsyncSnapshot<Set<Marker>> snapshot) {
if (!snapshot.hasData) {
// while data is loading:
return LoadingWithoutText();
} else {
// data loaded:
return GoogleMap(
mapType: MapType.hybrid,
myLocationEnabled: true,
// fixme - fix lat lng
initialCameraPosition:
CameraPosition(target: LatLng(25.6185024, 85.0726964), zoom: 3),
markers: snapshot.data,
onMapCreated: (GoogleMapController controller) {
_controller.complete(controller);
},
);
}
},
);
}
}
我找到了解决办法。虽然我不确定上面的代码有什么问题。但这似乎是某种同步或渲染问题。使用 setState() 我可以使用 BitmapDescriptor.fromBytes()。下面是我的代码。
class AqiMapPage extends StatefulWidget {
@override
_AqiMapPageState createState() => _AqiMapPageState();
}
final _scaffoldKey = GlobalKey<ScaffoldState>();
class _AqiMapPageState extends State<AqiMapPage> {
final GlobalKey<RefreshIndicatorState> _refreshIndicatorKey =
new GlobalKey<RefreshIndicatorState>();
AqiCnMapBloc _bloc;
@override
void initState() {
super.initState();
_bloc = AqiCnMapBloc();
_bloc.getAqiCnMap(false);
}
@override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
appBar: AppBar(
title: Text(AppLocalizations.of(context)
.getString(AppStringKeys.AQI_MAP_PAGE_KEY)),
centerTitle: true,
),
backgroundColor: HexColor.fromHex(AppColors.scaffoldBackgroundColor),
body: new RefreshIndicator(
key: _refreshIndicatorKey,
onRefresh: () => _bloc.getAqiCnMap(true),
child: StreamBuilder<Response<AqiCnMapApiResponse>>(
stream: _bloc.aqiCnMapStream,
builder: (context, snapshot) {
if (snapshot.hasData) {
switch (snapshot.data.status) {
case Status.LOADING:
return Loading(loadingKey: snapshot.data.message);
break;
case Status.SUCCESS:
return AqiMapData(mapData: snapshot.data.data);
break;
case Status.ERROR:
return Error(
error: snapshot.data.message,
onRetryPressed: () => _bloc.getAqiCnMap(false),
);
break;
}
}
return Container();
},
),
),
);
}
}
class AqiMapData extends StatefulWidget {
final AqiCnMapApiResponse mapData;
const AqiMapData({Key key, this.mapData}) : super(key: key);
@override
_AqiMapDataState createState() => _AqiMapDataState(mapData);
}
class _AqiMapDataState extends State<AqiMapData> {
final AqiCnMapApiResponse mapData;
_AqiMapDataState(this.mapData);
Set<Marker> _markers = Set();
Completer<GoogleMapController> _controller = Completer();
static Future<Uint8List> getBytesFromAsset(String path, 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: ImageByteFormat.png))
.buffer
.asUint8List();
}
populateMarkers() {
mapData.mapData.forEach((element) async {
try {
final MarkerId markerId = MarkerId(element.uid.toString());
Levels levels = AqiCnAqiRange.getAqiLevel(int.parse(element.aqi));
final Uint8List markerIcon = await getBytesFromAsset('assets/markers/${levels.markerIcon}', 100);
// creating a new MARKER
final Marker marker = new Marker(
icon: BitmapDescriptor.fromBytes(markerIcon),
markerId: markerId,
position: LatLng(element.lat, element.lon),
infoWindow: InfoWindow(
title: element.station.name, snippet: element.station.time),
);
// the solution
setState(() {
// adding a new marker to map
_markers.add(marker);
});
} catch (e) {
print(e.toString());
}
});
}
@override
void initState() {
super.initState();
populateMarkers();
}
@override
Widget build(BuildContext context) {
return GoogleMap(
mapType: MapType.hybrid,
myLocationEnabled: true,
// fixme - fix lat lng
initialCameraPosition:
CameraPosition(target: LatLng(25.6185024, 85.0726964), zoom: 3),
markers: _markers,
onMapCreated: (GoogleMapController controller) {
_controller.complete(controller);
},
);
}
}