diff --git a/example/lib/custom_marker.dart b/example/lib/custom_marker.dart index e69de29bb..3a529708b 100644 --- a/example/lib/custom_marker.dart +++ b/example/lib/custom_marker.dart @@ -0,0 +1,267 @@ +import 'dart:io'; +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:mapbox_gl/mapbox_gl.dart'; + +import 'main.dart'; +import 'page.dart'; + +const RANDOM_MARKER_NUM = 100; + +class CustomMarker extends ExamplePage { + CustomMarker() : super(const Icon(Icons.place), 'Custom marker'); + + @override + Widget build(BuildContext context) { + return FullMap(); + } +} + +class FullMap extends StatefulWidget { + const FullMap(); + + @override + State createState() => FullMapState(); +} + +abstract class PositionChangeListener { + LatLng onPositionWillChange(); + void onPositionChanged(Point point); +} + +class FullMapState extends State { + final Random _rnd = new Random(); + + MapboxMapController _mapController; + List _markers = []; + List _positionChangeListeners = []; + + void _addPositionChangeListener(PositionChangeListener listener) { + _positionChangeListeners.add(listener); + } + + void _onMapCreated(MapboxMapController controller) { + _mapController = controller; + controller.addListener(() { + if (controller.isCameraMoving) { + _updateMarkerPosition(); + } + }); + } + + void _onStyleLoadedCallback() { + print('onStyleLoadedCallback'); + } + + void _onMapLongClickCallback(Point point, LatLng coordinates) { + _addMarker(point, coordinates); + } + + void _onCameraIdleCallback() { + _updateMarkerPosition(); + } + + void _updateMarkerPosition() { + var param = []; + + for (final listner in _positionChangeListeners) { + param.add(listner.onPositionWillChange()); + } + + _mapController.toScreenLocationBatch(param).then((points){ + for (final listner in _positionChangeListeners) { + param.add(listner.onPositionWillChange()); + } + + _positionChangeListeners.asMap().forEach((i, value){ + final point = Point(points[i].x, points[i].y); + _positionChangeListeners[i].onPositionChanged(point); + }); + }); + } + + void _addMarker(Point point, LatLng coordinates) { + setState(() { + _markers.add(Marker(_rnd.nextInt(100000).toString(), coordinates, point, _addPositionChangeListener)); + }); + } + + @override + Widget build(BuildContext context) { + return new Scaffold( + body: Stack( + children: [ + MapboxMap( + accessToken: MapsDemo.ACCESS_TOKEN, + trackCameraPosition: true, + onMapCreated: _onMapCreated, + onMapLongClick: _onMapLongClickCallback, + onCameraIdle: _onCameraIdleCallback, + onStyleLoadedCallback: _onStyleLoadedCallback, + initialCameraPosition: const CameraPosition(target: LatLng(35.0, 135.0), zoom: 5), + ), + IgnorePointer( + ignoring: true, + child: + Stack( + children: _markers, + ) + ) + ] + ), + floatingActionButton: FloatingActionButton( + onPressed: (){ + //_measurePerformance(); + + // Generate random markers + var param = []; + for (var i = 0; i < RANDOM_MARKER_NUM; i++) { + final lat = _rnd.nextDouble() * 20 + 30; + final lng = _rnd.nextDouble() * 20 + 125; + param.add(LatLng(lat, lng)); + } + + _mapController.toScreenLocationBatch(param).then((value) { + for (var i = 0; i < RANDOM_MARKER_NUM; i++) { + var point = Point(value[i].x, value[i].y); + _addMarker(point, param[i]); + } + }); + }, + child: Icon(Icons.add), + ), + ); + } + + // ignore: unused_element + void _measurePerformance() { + final trial = 10; + final batches = [500, 1000, 1500, 2000, 2500, 3000]; + var results = Map>(); + for (final batch in batches) { + results[batch] = [0.0, 0.0]; + } + + _mapController.toScreenLocation(LatLng(0, 0)); + Stopwatch sw = Stopwatch(); + + for (final batch in batches) { + // + // primitive + // + for (var i = 0; i < trial; i++) { + sw.start(); + var list = >>[]; + for (var j = 0; j < batch; j++) { + var p = _mapController.toScreenLocation(LatLng(j.toDouble() % 80, j.toDouble() % 300)); + list.add(p); + } + Future.wait(list); + sw.stop(); + results[batch][0] += sw.elapsedMilliseconds; + sw.reset(); + } + + // + // batch + // + for (var i = 0; i < trial; i++) { + sw.start(); + var param = []; + for (var j = 0; j < batch; j++) { + param.add(LatLng(j.toDouble() % 80, j.toDouble() % 300)); + } + Future.wait([_mapController.toScreenLocationBatch(param)]); + sw.stop(); + results[batch][1] += sw.elapsedMilliseconds; + sw.reset(); + } + + print('batch=$batch,primitive=${results[batch][0] / trial}ms, batch=${results[batch][1] / trial}ms'); + } + + } +} + +class Marker extends StatefulWidget { + final Point _initialPosition; + final LatLng _coordinate; + final void Function(PositionChangeListener) _addPositionChangeListener; + + Marker(String key, this._coordinate, this._initialPosition, this._addPositionChangeListener) : super(key: Key(key)); + + @override + State createState() { + final state = _MarkerState(_coordinate, _initialPosition); + _addPositionChangeListener(state); + return state; + } +} + +class _MarkerState extends State with TickerProviderStateMixin, PositionChangeListener { + final _iconSize = 20.0; + + final LatLng _coordinate; + Point _position; + + AnimationController _controller; + Animation _animation; + + _MarkerState(this._coordinate, this._position); + + @override + void initState() { + super.initState(); + _controller = AnimationController( + duration: const Duration(seconds: 2), + vsync: this, + )..repeat(reverse: true); + _animation = CurvedAnimation( + parent: _controller, + curve: Curves.elasticOut, + ); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + var ratio = 1.0; + + try { + ratio = Platform.isIOS ? 1.0 : MediaQuery.of(context).devicePixelRatio; //TODO: iOS returns lp + } catch (UnsupportedError) { + //web does not support Platform._operatingSystem + }; // ignore: empty_statements + + return + Positioned( + left: _position.x / ratio - _iconSize / 2, + top: _position.y / ratio - _iconSize / 2, + child: + RotationTransition( + turns: _animation, + child: + Image.asset('assets/symbols/2.0x/custom-icon.png', height: _iconSize)) + ); + } + + @override + void onPositionChanged(Point point) { + setState(() { + _position = point; + }); + } + + @override + LatLng onPositionWillChange() { + return _coordinate; + } +} + diff --git a/example/lib/main.dart b/example/lib/main.dart index ff2076302..e7aec6ca8 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -5,6 +5,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:location/location.dart'; +import 'package:mapbox_gl_example/custom_marker.dart'; import 'package:mapbox_gl_example/full_map.dart'; import 'package:mapbox_gl_example/offline_regions.dart'; @@ -36,6 +37,7 @@ final List _allPages = [ ScrollingMapPage(), OfflineRegionsPage(), AnnotationOrderPage(), + CustomMarker(), ]; class MapsDemo extends StatelessWidget {