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

WIP: Gestures update - two finger rotation / interactions filter / map events #719

Merged
merged 44 commits into from Jan 29, 2021
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
3f16f70
Add two finger rotation support
maRci002 Aug 5, 2020
921f85c
Add InteractiveFlags
maRci002 Aug 5, 2020
b1b9a80
Add example page
maRci002 Aug 5, 2020
1f298ab
Emit MapEvents basic
maRci002 Aug 6, 2020
cbeff6a
Update interactive test page
maRci002 Aug 6, 2020
55930bb
update interactive page
maRci002 Aug 6, 2020
d67c0bc
Emit move / rotate events
maRci002 Aug 6, 2020
95e9afa
Emit rotation events
maRci002 Aug 6, 2020
08e22d5
Rotate only when rotationThreshold reached
maRci002 Aug 6, 2020
e32e3ce
Update examples
maRci002 Aug 6, 2020
79262fd
add some document
maRci002 Aug 6, 2020
e813762
fix some event's source
maRci002 Aug 6, 2020
d1e418c
typo fix
maRci002 Aug 6, 2020
8299d81
Call onRotationChanged correctly
maRci002 Aug 7, 2020
a6b5c6f
Fix arbitrary jumps when map is panned in rotated state
maRci002 Aug 7, 2020
f885845
tap / longPress / double tap use rotated offset / fix GestureDetector…
maRci002 Aug 17, 2020
6e05482
code refactor / organize
maRci002 Aug 17, 2020
b503315
Wip-gesture race: time to test on real device
maRci002 Aug 21, 2020
a7206b6
Fix multi finger gesture race
maRci002 Aug 21, 2020
919cce2
Reset gesture winner correctly
maRci002 Aug 23, 2020
55dd172
Use MultiFingerGesture during gesture race
maRci002 Aug 24, 2020
fe893a7
update MultiFingerGesture doc
maRci002 Aug 24, 2020
3ff9e08
add debugMultiFingerGestureWinner / enableMultiFingerGestureRace
maRci002 Aug 24, 2020
ea3d268
Do not override original start point
maRci002 Aug 24, 2020
11df8b3
mapEventStream do not rely on MapController.ready
maRci002 Aug 24, 2020
fa4c644
emit MapEventFlingAnimationStart correctly
maRci002 Aug 24, 2020
9b643aa
remove expensive _positionedTapDetectorKey
maRci002 Aug 24, 2020
40fd4c3
rebuild layers just once when Move and Rotate happens at the same time
maRci002 Aug 24, 2020
71f7112
use different eventKey in example
maRci002 Aug 25, 2020
d0d44e6
GestureDetector isn't rotated anymore
maRci002 Aug 26, 2020
709a039
Correct fling animation when map is rotated
maRci002 Aug 26, 2020
2c14e68
Make rotation operation cheaper
maRci002 Aug 26, 2020
9e550fe
add rotate flag for layers
maRci002 Aug 26, 2020
805e1e5
Revert "add rotate flag for layers"
maRci002 Aug 27, 2020
12e779d
create rotate and non rotate layers
maRci002 Aug 27, 2020
0d88682
Use Stream<Null> rebuild instead of dynamic
maRci002 Aug 27, 2020
25042f0
#736 fix - rebuild layers when size changed with new pixelOrigin
maRci002 Aug 29, 2020
a9c1a9c
#736 fix2 - do not call onMoveSink after init
maRci002 Aug 29, 2020
12ae8ce
#736 fix2 - handle rebuild layers without _updateSizeByOriginalSizeAn…
maRci002 Aug 29, 2020
075b0a7
fix emit MapEventMove while multifinger
maRci002 Sep 18, 2020
200400f
try to fix dartanalyzer for Travis
maRci002 Sep 18, 2020
239c151
try to fix dartanalyzer for Travis 2
maRci002 Sep 18, 2020
67996ff
Merge branch 'master' into feature/gesture-update
johnpryan Jan 29, 2021
711c455
Update tile_layer.dart
johnpryan Jan 29, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions example/android/app/src/main/AndroidManifest.xml
Expand Up @@ -5,6 +5,10 @@
In most cases you can leave this as-is, but you if you want to provide
additional functionality it is fine to subclass or reimplement
FlutterApplication and put your custom class here. -->

<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

<application
android:name="io.flutter.app.FlutterApplication"
android:label="example"
Expand Down
2 changes: 2 additions & 0 deletions example/lib/main.dart
@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter_map_example/pages/interactive_test_page.dart';

import './pages/animated_map_controller.dart';
import './pages/circle.dart';
Expand Down Expand Up @@ -58,6 +59,7 @@ class MyApp extends StatelessWidget {
CustomCrsPage.route: (context) => CustomCrsPage(),
LiveLocationPage.route: (context) => LiveLocationPage(),
TileLoadingErrorHandle.route: (context) => TileLoadingErrorHandle(),
InteractiveTestPage.route: (context) => InteractiveTestPage(),
},
);
}
Expand Down
363 changes: 363 additions & 0 deletions example/lib/pages/interactive_test_page.dart
@@ -0,0 +1,363 @@
import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:latlong/latlong.dart';

import '../widgets/drawer.dart';

class InteractiveTestPage extends StatefulWidget {
static const String route = 'interactive_test_page';

@override
State createState() {
// TODO: try out InteractiveTestPageState it is using StreamBuilder for update Events view and using boolean flags for Filter
return InteractiveTestPageAlternativeState();
}
}

class InteractiveTestPageState extends State<InteractiveTestPage> {
MapController mapController;

bool move = false;
bool fling = false;
bool pinchZoom = true;
bool doubleTapZoom = true;
bool rotate = false;

@override
void initState() {
super.initState();
mapController = MapController();
}

int _collectInteractiveFlags() {
var flags = InteractiveFlag.none;
if (move) {
flags = flags | InteractiveFlag.move; // use |= operator
}
if (fling) {
flags |= InteractiveFlag.fling;
}
if (pinchZoom) {
flags |= InteractiveFlag.pinchZoom;
}
if (doubleTapZoom) {
flags |= InteractiveFlag.doubleTapZoom;
}
if (rotate) {
flags |= InteractiveFlag.rotate;
}

return flags;
}

@override
Widget build(BuildContext context) {
var circleMarkers = <CircleMarker>[
CircleMarker(
point: LatLng(51.5, -0.09),
color: Colors.blue.withOpacity(0.7),
borderStrokeWidth: 2,
useRadiusInMeter: true,
radius: 2000 // 2000 meters | 2 km
),
];

return Scaffold(
appBar: AppBar(title: Text('Test out Interactive flags!')),
drawer: buildDrawer(context, InteractiveTestPage.route),
body: Padding(
padding: EdgeInsets.all(8.0),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
MaterialButton(
child: Text('Move'),
color: move ? Colors.greenAccent : Colors.redAccent,
onPressed: () {
setState(() {
move = !move;
});
},
),
MaterialButton(
child: Text('Fling'),
color: fling ? Colors.greenAccent : Colors.redAccent,
onPressed: () {
setState(() {
fling = !fling;
});
},
),
MaterialButton(
child: Text('Pinch zoom'),
color: pinchZoom ? Colors.greenAccent : Colors.redAccent,
onPressed: () {
setState(() {
pinchZoom = !pinchZoom;
});
},
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
MaterialButton(
child: Text('Double tap zoom'),
color: doubleTapZoom ? Colors.greenAccent : Colors.redAccent,
onPressed: () {
setState(() {
doubleTapZoom = !doubleTapZoom;
});
},
),
MaterialButton(
child: Text('Rotate'),
color: rotate ? Colors.greenAccent : Colors.redAccent,
onPressed: () {
setState(() {
rotate = !rotate;
});
},
),
MaterialButton(
color: Colors.grey,
child: Text('Skew'),
onPressed: null,
),
],
),
Padding(
padding: EdgeInsets.only(top: 8.0, bottom: 8.0),
child: Center(
child: FutureBuilder<Null>(
future: mapController.onReady,
builder:
(BuildContext context, AsyncSnapshot<Null> snapshot) {
if (snapshot.connectionState != ConnectionState.done) {
return Text('No MapEvent fired');
}

return StreamBuilder<MapEvent>(
stream: mapController.mapEventStream,
builder: (BuildContext context,
AsyncSnapshot<MapEvent> snapshot) {
if (snapshot.connectionState == ConnectionState.none ||
!snapshot.hasData) {
return Text('No MapEvent fired');
}

return Text(
'Current event: ${snapshot.data.runtimeType}\nSource: ${snapshot.data.source}',
textAlign: TextAlign.center,
);
},
);
},
),
),
),
Flexible(
child: FlutterMap(
mapController: mapController,
options: MapOptions(
center: LatLng(51.5, -0.09),
zoom: 11.0,
interactiveFlags: _collectInteractiveFlags(),
),
layers: [
TileLayerOptions(
urlTemplate:
'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
subdomains: ['a', 'b', 'c']),
CircleLayerOptions(circles: circleMarkers),
],
),
),
],
),
),
);
}
}

class InteractiveTestPageAlternativeState extends State<InteractiveTestPage> {
MapController mapController;

// Enable pinchZoom and doubleTapZoomBy by default
int flags = InteractiveFlag.pinchZoom | InteractiveFlag.doubleTapZoom;

// Here is the last moveEvent
MapEvent lastMapEvent;

StreamSubscription<MapEvent> subscription;

@override
void initState() {
super.initState();
mapController = MapController();

mapController.onReady.then((_) {
// at this point we can listen to [mapEventStream]
// use stream transformer or anything you want
subscription = mapController.mapEventStream.listen((MapEvent mapEvent) {
setState(() {
// uncomment to filter specific events (hot reload won't take any effect,
// because this callback is already registered in initState, so after hot reload
// you should go away another page then come back OR just use hot restart...)
// if (mapEvent is! MapEventWithMove) {
lastMapEvent = mapEvent;
//}
});
});
});
}

@override
void dispose() {
if (subscription != null) {
subscription.cancel();
}

super.dispose();
}

void updateFlags(int flag) {
if (InteractiveFlag.hasFlag(flags, flag)) {
// remove flag from flags
flags &= ~flag;
} else {
// add flag to flags
flags |= flag;
}
}

@override
Widget build(BuildContext context) {
var circleMarkers = <CircleMarker>[
CircleMarker(
point: LatLng(51.5, -0.09),
color: Colors.blue.withOpacity(0.7),
borderStrokeWidth: 2,
useRadiusInMeter: true,
radius: 2000 // 2000 meters | 2 km
),
];

return Scaffold(
appBar: AppBar(title: Text('Test out Interactive flags!')),
drawer: buildDrawer(context, InteractiveTestPage.route),
body: Padding(
padding: EdgeInsets.all(8.0),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
MaterialButton(
child: Text('Move'),
color: InteractiveFlag.hasFlag(flags, InteractiveFlag.move)
? Colors.greenAccent
: Colors.redAccent,
onPressed: () {
setState(() {
updateFlags(InteractiveFlag.move);
});
},
),
MaterialButton(
child: Text('Fling'),
color: InteractiveFlag.hasFlag(flags, InteractiveFlag.fling)
? Colors.greenAccent
: Colors.redAccent,
onPressed: () {
setState(() {
updateFlags(InteractiveFlag.fling);
});
},
),
MaterialButton(
child: Text('Pinch zoom'),
color:
InteractiveFlag.hasFlag(flags, InteractiveFlag.pinchZoom)
? Colors.greenAccent
: Colors.redAccent,
onPressed: () {
setState(() {
updateFlags(InteractiveFlag.pinchZoom);
});
},
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
MaterialButton(
child: Text('Double tap zoom'),
color: InteractiveFlag.hasFlag(
flags, InteractiveFlag.doubleTapZoom)
? Colors.greenAccent
: Colors.redAccent,
onPressed: () {
setState(() {
updateFlags(InteractiveFlag.doubleTapZoom);
});
},
),
MaterialButton(
child: Text('Rotate'),
color: InteractiveFlag.hasFlag(flags, InteractiveFlag.rotate)
? Colors.greenAccent
: Colors.redAccent,
onPressed: () {
setState(() {
updateFlags(InteractiveFlag.rotate);
});
},
),
MaterialButton(
color: Colors.grey,
child: Text('Skew'),
onPressed: null,
),
],
),
Padding(
padding: EdgeInsets.only(top: 8.0, bottom: 8.0),
child: Center(
child: Text(
lastMapEvent == null
? 'No MapEvent fired'
: 'Current event: ${lastMapEvent.runtimeType}\nSource: ${lastMapEvent.source}',
textAlign: TextAlign.center,
),
),
),
Flexible(
child: FlutterMap(
mapController: mapController,
options: MapOptions(
center: LatLng(51.5, -0.09),
zoom: 11.0,
interactiveFlags: flags,
),
layers: [
TileLayerOptions(
urlTemplate:
'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
subdomains: ['a', 'b', 'c']),
CircleLayerOptions(circles: circleMarkers),
],
),
),
],
),
),
);
}
}