尝试在谷歌地图上显示自定义标记时,BitmapDescriptor.fromBytes() 不起作用

Posted

技术标签:

【中文标题】尝试在谷歌地图上显示自定义标记时,BitmapDescriptor.fromBytes() 不起作用【英文标题】:BitmapDescriptor.fromBytes() is not working when trying to show custom marker on google maps 【发布时间】:2020-09-06 21:53:24 【问题描述】:

我正在尝试将自定义图像显示为谷歌地图上的标记。问题是 BitmapDescriptor.fromAssetImage() 工作正常,但 BitmapDescriptor.fromBytes() 不工作。由于我必须进一步使用画布,我需要使用 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);
            ,
          );
        
      ,
    );
  

【问题讨论】:

【参考方案1】:

我找到了解决办法。虽然我不确定上面的代码有什么问题。但这似乎是某种同步或渲染问题。并且使用 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);
      ,
    );
  

【讨论】:

以上是关于尝试在谷歌地图上显示自定义标记时,BitmapDescriptor.fromBytes() 不起作用的主要内容,如果未能解决你的问题,请参考以下文章

在谷歌地图自定义标记未显示在三星 S8 或 Android 的相同版本 7.0

谷歌地图上的自定义标记

单击标记时,新按钮会自动出现在谷歌地图(Android)上?如何删除?

在谷歌地图颤动中设置标记

在谷歌地图标记上显示信息

Android Google Map API 自定义标记在自定义标记上带来谷歌地图数据