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);
      },
    );
  }
}