diff --git a/lib/src/layer/tile_layer.dart b/lib/src/layer/tile_layer.dart index 0b0aaaed9..9d4748f07 100644 --- a/lib/src/layer/tile_layer.dart +++ b/lib/src/layer/tile_layer.dart @@ -141,17 +141,30 @@ class TileLayerOptions extends LayerOptions { /// final Map additionalOptions; - // Tiles will not update more than once every `updateInterval` milliseconds - // (default 200) when panning. - // It can be 0 (but it will calculating for loading tiles every frame when panning / zooming, flutter is fast) - // This can save some fps and even bandwidth - // (ie. when fast panning / animating between long distances in short time) + /// Tiles will not update more than once every `updateInterval` + /// (default 200 milliseconds) when panning. + /// It can be null (but it will calculating for loading tiles every frame when panning / zooming, flutter is fast) + /// This can save some fps and even bandwidth + /// (ie. when fast panning / animating between long distances in short time) final Duration updateInterval; - // Tiles fade in duration in milliseconds (default 100), - // it can 0 to avoid fade in + /// Tiles fade in duration in milliseconds (default 100), + /// it can be null to avoid fade in final Duration tileFadeInDuration; + /// Opacity start value when Tile starts fade in (0.0 - 1.0) + /// Takes effect if `tileFadeInDuration` is not null + final double tileFadeInStart; + + /// Opacity start value when an exists Tile starts fade in with different Url (0.0 - 1.0) + /// Takes effect when `tileFadeInDuration` is not null and if `overrideTilesWhenUrlChanges` if true + final double tileFadeInStartWhenOverride; + + /// `false`: current Tiles will be first dropped and then reload via new url (default) + /// `true`: current Tiles will be visible until new ones aren't loaded (new Tiles are loaded independently) + /// @see https://github.com/johnpryan/flutter_map/issues/583 + final bool overrideTilesWhenUrlChanges; + TileLayerOptions( {this.urlTemplate, this.tileSize = 256.0, @@ -181,12 +194,18 @@ class TileLayerOptions extends LayerOptions { // Tiles fade in duration in milliseconds (default 100), // it can 0 to avoid fade in int tileFadeInDuration = 100, + this.tileFadeInStart = 0.0, + this.tileFadeInStartWhenOverride = 0.0, + this.overrideTilesWhenUrlChanges = false, rebuild}) : updateInterval = updateInterval <= 0 ? null : Duration(milliseconds: updateInterval), tileFadeInDuration = tileFadeInDuration <= 0 ? null : Duration(milliseconds: tileFadeInDuration), + assert(tileFadeInStart >= 0.0 && tileFadeInStart <= 1.0), + assert(tileFadeInStartWhenOverride >= 0.0 && + tileFadeInStartWhenOverride <= 1.0), super(rebuild: rebuild); } @@ -317,6 +336,49 @@ class _TileLayerState extends State with TickerProviderStateMixin { _update(null); _moveSub = widget.stream.listen((_) => _handleMove()); + _initThrottleUpdate(); + } + + @override + void didUpdateWidget(TileLayer oldWidget) { + super.didUpdateWidget(oldWidget); + var reloadTiles = false; + + if (oldWidget.options.tileSize != options.tileSize) { + _tileSize = CustomPoint(options.tileSize, options.tileSize); + reloadTiles = true; + } + + if (oldWidget.options.updateInterval != options.updateInterval) { + _throttleUpdate?.close(); + _initThrottleUpdate(); + } + + if (!reloadTiles) { + final oldUrl = oldWidget.options.wmsOptions?._encodedBaseUrl ?? + oldWidget.options.urlTemplate; + final newUrl = options.wmsOptions?._encodedBaseUrl ?? options.urlTemplate; + if (oldUrl != newUrl) { + if (options.overrideTilesWhenUrlChanges) { + for (var tile in _tiles.values) { + tile.imageProvider = options.tileProvider + .getImage(_wrapCoords(tile.coords), options); + tile.loadTileImage(); + } + } else { + reloadTiles = true; + } + } + } + + if (reloadTiles) { + _removeAllTiles(); + _resetView(); + _update(null); + } + } + + void _initThrottleUpdate() { if (options.updateInterval == null) { _throttleUpdate = null; } else { @@ -331,12 +393,12 @@ class _TileLayerState extends State with TickerProviderStateMixin { @override void dispose() { - super.dispose(); - _removeAllTiles(); _moveSub?.cancel(); options.tileProvider.dispose(); _throttleUpdate?.close(); + + super.dispose(); } @override @@ -822,6 +884,8 @@ class _TileLayerState extends State with TickerProviderStateMixin { print(error); tile.loadError = true; + } else { + tile.loadError = false; } var key = _tileCoordsToKey(coords); @@ -830,12 +894,20 @@ class _TileLayerState extends State with TickerProviderStateMixin { return; } + var fadeInStart = tile.loaded == null + ? options.tileFadeInStart + : options.tileFadeInStartWhenOverride; tile.loaded = DateTime.now(); if (options.tileFadeInDuration == null || + fadeInStart == 1.0 || (tile.loadError && null == options.errorImage)) { tile.active = true; } else { - tile.startFadeInAnimation(options.tileFadeInDuration, this); + tile.startFadeInAnimation( + options.tileFadeInDuration, + this, + from: fadeInStart, + ); } setState(() {}); @@ -896,7 +968,7 @@ class Tile implements Comparable { final String coordsKey; final Coords coords; final CustomPoint tilePos; - final ImageProvider imageProvider; + ImageProvider imageProvider; final Level level; bool current; @@ -929,10 +1001,20 @@ class Tile implements Comparable { this.retain = false, this.loadError = false, }) { + loadTileImage(); + } + + void loadTileImage() { try { + final oldImageStream = _imageStream; _imageStream = imageProvider.resolve(ImageConfiguration()); - _listener = ImageStreamListener(_tileOnLoad, onError: _tileOnError); - _imageStream.addListener(_listener); + + if (_imageStream.key != oldImageStream?.key) { + oldImageStream?.removeListener(_listener); + + _listener = ImageStreamListener(_tileOnLoad, onError: _tileOnError); + _imageStream.addListener(_listener); + } } catch (e, s) { // make sure all exception is handled - #444 / #536 _tileOnError(e, s); @@ -952,11 +1034,14 @@ class Tile implements Comparable { _imageStream?.removeListener(_listener); } - void startFadeInAnimation(Duration duration, TickerProvider vsync) { + void startFadeInAnimation(Duration duration, TickerProvider vsync, + {double from}) { + animationController?.removeStatusListener(_onAnimateEnd); + animationController = AnimationController(duration: duration, vsync: vsync) ..addStatusListener(_onAnimateEnd); - animationController.forward(); + animationController.forward(from: from); } void _onAnimateEnd(AnimationStatus status) { @@ -974,7 +1059,8 @@ class Tile implements Comparable { void _tileOnError(dynamic exception, StackTrace stackTrace) { if (null != tileReady) { - tileReady(coords, exception, this); + tileReady( + coords, exception ?? 'Unknown exception during loadTileImage', this); } }