Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: added interactivity to CircleLayer & refactored interactivity out into seperate classes #1886

Merged
merged 8 commits into from
May 23, 2024
180 changes: 159 additions & 21 deletions example/lib/pages/circle.dart
Original file line number Diff line number Diff line change
@@ -1,49 +1,187 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:flutter_map_example/misc/tile_providers.dart';
import 'package:flutter_map_example/widgets/drawer/menu_drawer.dart';
import 'package:latlong2/latlong.dart';

class CirclePage extends StatelessWidget {
typedef HitValue = ({String title, String subtitle});

class CirclePage extends StatefulWidget {
static const String route = '/circle';

const CirclePage({super.key});

@override
State<CirclePage> createState() => _CirclePageState();
}

class _CirclePageState extends State<CirclePage> {
final LayerHitNotifier<HitValue> _hitNotifier = ValueNotifier(null);
List<HitValue>? _prevHitValues;
List<CircleMarker<HitValue>>? _hoverCircles;

final _circlesRaw = <CircleMarker<HitValue>>[
CircleMarker(
point: const LatLng(51.5, -0.09),
color: Colors.white.withOpacity(0.7),
borderColor: Colors.black,
borderStrokeWidth: 2,
useRadiusInMeter: false,
radius: 100,
hitValue: (title: 'White', subtitle: 'Radius in logical pixels'),
),
CircleMarker(
point: const LatLng(51.5, -0.09),
color: Colors.black.withOpacity(0.7),
borderColor: Colors.black,
borderStrokeWidth: 2,
useRadiusInMeter: false,
radius: 50,
hitValue: (
title: 'Black',
subtitle: 'Radius in logical pixels, should be above white.',
),
),
CircleMarker(
point: const LatLng(51.4937, -0.6638),
// Dorney Lake is ~2km long
color: Colors.green.withOpacity(0.9),
borderColor: Colors.black,
borderStrokeWidth: 2,
useRadiusInMeter: true,
radius: 1000, // 1000 meters
hitValue: (
title: 'Green',
subtitle: 'Radius in meters, calibrated over ~2km rowing lake'
),
),
];
late final _circles =
Map.fromEntries(_circlesRaw.map((e) => MapEntry(e.hitValue, e)));

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Circle')),
drawer: const MenuDrawer(route),
appBar: AppBar(title: const Text('Circles')),
drawer: const MenuDrawer(CirclePage.route),
body: FlutterMap(
options: const MapOptions(
initialCenter: LatLng(51.5, -0.09),
initialZoom: 11,
),
children: [
openStreetMapTileLayer,
CircleLayer(
circles: [
CircleMarker(
point: const LatLng(51.5, -0.09),
color: Colors.blue.withOpacity(0.7),
borderColor: Colors.black,
borderStrokeWidth: 2,
useRadiusInMeter: true,
radius: 2000, // 2000 meters
MouseRegion(
hitTestBehavior: HitTestBehavior.deferToChild,
cursor: SystemMouseCursors.click,
onHover: (_) {
final hitValues = _hitNotifier.value?.hitValues.toList();
if (hitValues == null) return;

if (listEquals(hitValues, _prevHitValues)) return;
_prevHitValues = hitValues;

final hoverCircles = hitValues.map((v) {
final original = _circles[v]!;

return CircleMarker<HitValue>(
point: original.point,
radius: original.radius + (15 / 2) - 1,
JaffaKetchup marked this conversation as resolved.
Show resolved Hide resolved
useRadiusInMeter: original.useRadiusInMeter,
color: Colors.transparent,
borderStrokeWidth: 15,
borderColor: Colors.green,
);
}).toList();
setState(() => _hoverCircles = hoverCircles);
},
onExit: (_) {
_prevHitValues = null;
setState(() => _hoverCircles = null);
},
child: GestureDetector(
onTap: () => _openTouchedCirclesModal(
'Tapped',
_hitNotifier.value!.hitValues,
_hitNotifier.value!.coordinate,
),
onLongPress: () => _openTouchedCirclesModal(
'Long pressed',
_hitNotifier.value!.hitValues,
_hitNotifier.value!.coordinate,
),
onSecondaryTap: () => _openTouchedCirclesModal(
'Secondary tapped',
_hitNotifier.value!.hitValues,
_hitNotifier.value!.coordinate,
),
CircleMarker(
point: const LatLng(51.4937, -0.6638),
// Dorney Lake is ~2km long
color: Colors.green.withOpacity(0.9),
borderColor: Colors.black,
borderStrokeWidth: 2,
useRadiusInMeter: true,
radius: 1000, // 1000 meters
child: CircleLayer(
hitNotifier: _hitNotifier,
circles: [
..._circlesRaw,
...?_hoverCircles,
],
),
],
),
),
],
),
);
}

void _openTouchedCirclesModal(
String eventType,
List<HitValue> tappedCircles,
LatLng coords,
) {
showModalBottomSheet<void>(
context: context,
builder: (context) => Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Tapped Circle(s)',
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
Text(
'$eventType at point: (${coords.latitude.toStringAsFixed(6)}, ${coords.longitude.toStringAsFixed(6)})',
),
const SizedBox(height: 8),
Expanded(
child: ListView.builder(
itemBuilder: (context, index) {
final tappedLineData = tappedCircles[index];
return ListTile(
leading: index == 0
? const Icon(Icons.vertical_align_top)
: index == tappedCircles.length - 1
? const Icon(Icons.vertical_align_bottom)
: const SizedBox.shrink(),
title: Text(tappedLineData.title),
subtitle: Text(tappedLineData.subtitle),
dense: true,
);
},
itemCount: tappedCircles.length,
),
),
const SizedBox(height: 8),
Align(
alignment: Alignment.bottomCenter,
child: SizedBox(
width: double.infinity,
child: OutlinedButton(
onPressed: () => Navigator.pop(context),
child: const Text('Close'),
),
),
),
],
),
),
);
}
}
9 changes: 5 additions & 4 deletions lib/flutter_map.dart
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,15 @@ export 'package:flutter_map/src/layer/attribution_layer/rich/widget.dart';
export 'package:flutter_map/src/layer/attribution_layer/simple.dart';
export 'package:flutter_map/src/layer/circle_layer/circle_layer.dart';
export 'package:flutter_map/src/layer/marker_layer/marker_layer.dart';
export 'package:flutter_map/src/layer/misc/hit_detection.dart';
export 'package:flutter_map/src/layer/misc/line_patterns/stroke_pattern.dart';
export 'package:flutter_map/src/layer/misc/mobile_layer_transformer.dart';
export 'package:flutter_map/src/layer/misc/translucent_pointer.dart';
export 'package:flutter_map/src/layer/overlay_image_layer/overlay_image_layer.dart';
export 'package:flutter_map/src/layer/polygon_layer/polygon_layer.dart';
export 'package:flutter_map/src/layer/polyline_layer/polyline_layer.dart';
export 'package:flutter_map/src/layer/scalebar/scalebar.dart';
export 'package:flutter_map/src/layer/shared/layer_interactivity/layer_hit_notifier.dart';
export 'package:flutter_map/src/layer/shared/layer_interactivity/layer_hit_result.dart';
export 'package:flutter_map/src/layer/shared/line_patterns/stroke_pattern.dart';
export 'package:flutter_map/src/layer/shared/mobile_layer_transformer.dart';
export 'package:flutter_map/src/layer/shared/translucent_pointer.dart';
export 'package:flutter_map/src/layer/tile_layer/tile_builder.dart';
export 'package:flutter_map/src/layer/tile_layer/tile_coordinates.dart';
export 'package:flutter_map/src/layer/tile_layer/tile_display.dart';
Expand Down
24 changes: 19 additions & 5 deletions lib/src/layer/circle_layer/circle_layer.dart
Original file line number Diff line number Diff line change
@@ -1,27 +1,41 @@
import 'dart:math';
import 'dart:ui';

import 'package:flutter/widgets.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:flutter_map/src/layer/shared/layer_interactivity/internal_hit_detectable.dart';
import 'package:latlong2/latlong.dart' hide Path;

part 'circle_marker.dart';
part 'painter.dart';

/// A layer that displays a list of [CircleMarker] on the map
@immutable
class CircleLayer extends StatelessWidget {
class CircleLayer<R extends Object> extends StatelessWidget {
/// The list of [CircleMarker]s.
final List<CircleMarker> circles;
final List<CircleMarker<R>> circles;

/// Create a new [CircleLayer] as a child for flutter map
const CircleLayer({super.key, required this.circles});
/// {@macro fm.lhn.layerHitNotifier.usage}
final LayerHitNotifier<R>? hitNotifier;

/// Create a new [CircleLayer] as a child for [FlutterMap]
const CircleLayer({
super.key,
required this.circles,
this.hitNotifier,
});

@override
Widget build(BuildContext context) {
final camera = MapCamera.of(context);

return MobileLayerTransformer(
child: CustomPaint(
painter: CirclePainter(circles, camera),
painter: CirclePainter(
circles: circles,
camera: camera,
hitNotifier: hitNotifier,
),
size: Size(camera.size.x, camera.size.y),
isComplex: true,
),
Expand Down
3 changes: 2 additions & 1 deletion lib/src/layer/circle_layer/circle_marker.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ part of 'circle_layer.dart';
/// Immutable marker options for [CircleMarker]. Circle markers are a more
/// simple and performant way to draw markers as the regular [Marker]
@immutable
class CircleMarker {
base class CircleMarker<R extends Object> extends HitDetectableElement<R> {
/// An optional [Key] for the [CircleMarker].
/// This key is not used internally.
final Key? key;
Expand Down Expand Up @@ -36,5 +36,6 @@ class CircleMarker {
this.color = const Color(0xFF00FF00),
this.borderStrokeWidth = 0.0,
this.borderColor = const Color(0xFFFFFF00),
super.hitValue,
});
}
Loading
Loading