@@ -1042,259 +1042,6 @@ class ViewportPageParentData extends ContainerBoxParentData<RenderBox> with Cont
1042
1042
}
1043
1043
}
1044
1044
1045
- /// Controls a [PageListViewportController] with scale gestures to pan and zoom the
1046
- /// associated [PageListViewport] .
1047
- class PageListViewportGestures extends StatefulWidget {
1048
- const PageListViewportGestures ({
1049
- Key ? key,
1050
- required this .controller,
1051
- this .onTapUp,
1052
- this .onLongPressStart,
1053
- this .onLongPressMoveUpdate,
1054
- this .onLongPressEnd,
1055
- this .onDoubleTapDown,
1056
- this .onDoubleTap,
1057
- this .onDoubleTapCancel,
1058
- this .panAndZoomPointerDevices = const {
1059
- PointerDeviceKind .mouse,
1060
- PointerDeviceKind .trackpad,
1061
- PointerDeviceKind .touch,
1062
- },
1063
- this .clock = const Clock (),
1064
- required this .child,
1065
- }) : super (key: key);
1066
-
1067
- final PageListViewportController controller;
1068
-
1069
- // All of these methods were added because our client needs to
1070
- // respond to them, and we internally respond to other gestures.
1071
- // Flutter won't let gestures pass from parent to child, so we're
1072
- // forced to expose all of these callbacks so that our client can
1073
- // hook into them.
1074
- final void Function (TapUpDetails )? onTapUp;
1075
- final void Function (LongPressStartDetails )? onLongPressStart;
1076
- final void Function (LongPressMoveUpdateDetails )? onLongPressMoveUpdate;
1077
- final void Function (LongPressEndDetails )? onLongPressEnd;
1078
- final void Function (TapDownDetails )? onDoubleTapDown;
1079
- final void Function ()? onDoubleTap;
1080
- final void Function ()? onDoubleTapCancel;
1081
-
1082
- final Set <PointerDeviceKind > panAndZoomPointerDevices;
1083
-
1084
- /// Reports the time, so that the gesture system can track how much
1085
- /// time has passed.
1086
- ///
1087
- /// [clock] is configurable so that a fake version can be injected
1088
- /// in tests.
1089
- final Clock clock;
1090
-
1091
- final Widget child;
1092
-
1093
- @override
1094
- State <PageListViewportGestures > createState () => _PageListViewportGesturesState ();
1095
- }
1096
-
1097
- class _PageListViewportGesturesState extends State <PageListViewportGestures > with TickerProviderStateMixin {
1098
- bool _isPanningEnabled = true ;
1099
- bool _isPanning = false ;
1100
-
1101
- late PanAndScaleVelocityTracker _panAndScaleVelocityTracker;
1102
- double ? _startContentScale;
1103
- Offset ? _startOffset;
1104
- int ? _endTimeInMillis;
1105
- late Ticker _ticker;
1106
-
1107
- @override
1108
- void initState () {
1109
- super .initState ();
1110
- _panAndScaleVelocityTracker = PanAndScaleVelocityTracker (clock: widget.clock);
1111
- _ticker = createTicker (_onFrictionTick);
1112
- }
1113
-
1114
- @override
1115
- void dispose () {
1116
- _ticker.dispose ();
1117
- super .dispose ();
1118
- }
1119
-
1120
- void _onPointerDown (PointerDownEvent event) {
1121
- if (event.kind == PointerDeviceKind .stylus) {
1122
- _isPanningEnabled = false ;
1123
- }
1124
-
1125
- // Stop any on-going friction simulation.
1126
- _stopMomentum ();
1127
- }
1128
-
1129
- void _onPointerUp (PointerUpEvent event) {
1130
- _isPanningEnabled = true ;
1131
- }
1132
-
1133
- void _onPointerCancel (PointerCancelEvent event) {
1134
- _isPanningEnabled = true ;
1135
- }
1136
-
1137
- void _onScaleStart (ScaleStartDetails details) {
1138
- PageListViewportLogs .pagesListGestures.finer ("onScaleStart()" );
1139
- if (! _isPanningEnabled) {
1140
- // The user is interacting with a stylus. We don't want to pan
1141
- // or scale with a stylus.
1142
- return ;
1143
- }
1144
-
1145
- _isPanning = true ;
1146
-
1147
- final timeSinceLastGesture = _endTimeInMillis != null ? _timeSinceEndOfLastGesture : null ;
1148
- _startContentScale = widget.controller.scale;
1149
- _startOffset = widget.controller.origin;
1150
-
1151
- _panAndScaleVelocityTracker.onScaleStart (details);
1152
-
1153
- if ((timeSinceLastGesture == null || timeSinceLastGesture > const Duration (milliseconds: 30 ))) {
1154
- // We've started a new gesture after a reasonable period of time since the
1155
- // last gesture. Stop any momentum from the last gesture.
1156
- _stopMomentum ();
1157
- }
1158
- }
1159
-
1160
- void _onScaleUpdate (ScaleUpdateDetails details) {
1161
- PageListViewportLogs .pagesList
1162
- .finer ("onScaleUpdate() - new focal point ${details .focalPoint }, focal delta: ${details .focalPointDelta }" );
1163
- if (! _isPanning) {
1164
- // The user is interacting with a stylus. We don't want to pan
1165
- // or scale with a stylus.
1166
- return ;
1167
- }
1168
-
1169
- if (! _isPanningEnabled) {
1170
- PageListViewportLogs .pagesListGestures.finer ("Started panning when the stylus was down. Resetting transform to:" );
1171
- PageListViewportLogs .pagesListGestures.finer (" - origin: ${widget .controller .origin }" );
1172
- PageListViewportLogs .pagesListGestures.finer (" - scale: ${widget .controller .scale }" );
1173
-
1174
- _isPanning = false ;
1175
-
1176
- // When this condition is triggered, _startOffset and _startContentScale
1177
- // should be non-null. But sometimes they are null. I don't know why. When that
1178
- // happens, return.
1179
- if (_startOffset == null || _startContentScale == null ) {
1180
- return ;
1181
- }
1182
-
1183
- widget.controller
1184
- ..setScale (_startContentScale! , details.focalPoint)
1185
- ..translate (_startOffset! - widget.controller.origin);
1186
- return ;
1187
- }
1188
-
1189
- _panAndScaleVelocityTracker.onScaleUpdate (details);
1190
-
1191
- widget.controller //
1192
- ..setScale (details.scale * _startContentScale! , details.localFocalPoint)
1193
- ..translate (details.focalPointDelta);
1194
- PageListViewportLogs .pagesListGestures
1195
- .finer ("New origin: ${widget .controller .origin }, scale: ${widget .controller .scale }" );
1196
- }
1197
-
1198
- void _onScaleEnd (ScaleEndDetails details) {
1199
- PageListViewportLogs .pagesListGestures.finer ("onScaleEnd()" );
1200
- if (! _isPanning) {
1201
- return ;
1202
- }
1203
-
1204
- _panAndScaleVelocityTracker.onScaleEnd (details);
1205
-
1206
- if (details.pointerCount == 0 ) {
1207
- _startMomentum ();
1208
- _isPanning = false ;
1209
- }
1210
- }
1211
-
1212
- Duration get _timeSinceEndOfLastGesture => Duration (milliseconds: widget.clock.millis - _endTimeInMillis! );
1213
-
1214
- void _startMomentum () {
1215
- PageListViewportLogs .pagesListGestures.fine ("Starting momentum..." );
1216
- final velocity = _panAndScaleVelocityTracker.velocity;
1217
- PageListViewportLogs .pagesListGestures.fine ("Starting momentum with velocity: $velocity " );
1218
-
1219
- _velocity = velocity;
1220
-
1221
- if (! _ticker.isTicking) {
1222
- _lastTime = Duration .zero;
1223
- _ticker.start ();
1224
- }
1225
- }
1226
-
1227
- void _stopMomentum () {
1228
- if (_ticker.isTicking) {
1229
- _ticker.stop ();
1230
- }
1231
- }
1232
-
1233
- Duration _lastTime = Duration .zero;
1234
- Offset _velocity = Offset .zero;
1235
- void _onFrictionTick (Duration elapsedTime) {
1236
- if (elapsedTime == Duration .zero) {
1237
- return ;
1238
- }
1239
-
1240
- final dt = elapsedTime - _lastTime;
1241
- _lastTime = elapsedTime;
1242
- final secondsFraction = dt.inMilliseconds / 1000 ;
1243
- final dp = _velocity * secondsFraction;
1244
- final originBeforeDelta = widget.controller.origin;
1245
- final newOrigin = widget.controller.origin + dp;
1246
- final translate = newOrigin - originBeforeDelta;
1247
-
1248
- final direction = Offset .fromDirection (_velocity.direction);
1249
-
1250
- // We want:
1251
- // - a decay of 3% when moving very fast (less drag when flinging fast)
1252
- // - a decay of 6% when moving slowly (more drag when coming to rest)
1253
- final velocityIntensity = math.pow ((_velocity.distance / kMaxFlingVelocity).clamp (0.0 , 1.0 ), 1 / 4.0 ).toDouble ();
1254
- final drag = lerpDouble (0.06 , 0.03 , velocityIntensity)! ;
1255
- _velocity = _velocity - (direction * _velocity.distance * drag);
1256
-
1257
- PageListViewportLogs .pagesListGestures
1258
- .finest ("Friction tick. Time: ${elapsedTime .inMilliseconds }ms. Velocity: $_velocity . Movement: $translate " );
1259
-
1260
- widget.controller.translate (translate);
1261
-
1262
- PageListViewportLogs .pagesListGestures.finest ("New origin: $newOrigin " );
1263
-
1264
- // If the viewport hit a wall, or if the simulations are done, stop
1265
- // ticking.
1266
- if (translate.distance < 0.5 ) {
1267
- _ticker.stop ();
1268
- }
1269
- }
1270
-
1271
- @override
1272
- Widget build (BuildContext context) {
1273
- return Listener (
1274
- // Listen for finger-down in a Listener so that we have zero
1275
- // latency when stopping a friction simulation. Also, track when
1276
- // a stylus is used, so we can prevent panning.
1277
- onPointerDown: _onPointerDown,
1278
- onPointerUp: _onPointerUp,
1279
- onPointerCancel: _onPointerCancel,
1280
- child: GestureDetector (
1281
- onTapUp: widget.onTapUp,
1282
- onLongPressStart: widget.onLongPressStart,
1283
- onLongPressMoveUpdate: widget.onLongPressMoveUpdate,
1284
- onLongPressEnd: widget.onLongPressEnd,
1285
- onDoubleTapDown: widget.onDoubleTapDown,
1286
- onDoubleTap: widget.onDoubleTap,
1287
- onDoubleTapCancel: widget.onDoubleTapCancel,
1288
- onScaleStart: _onScaleStart,
1289
- onScaleUpdate: _onScaleUpdate,
1290
- onScaleEnd: _onScaleEnd,
1291
- supportedDevices: widget.panAndZoomPointerDevices,
1292
- child: widget.child,
1293
- ),
1294
- );
1295
- }
1296
- }
1297
-
1298
1045
/// Tracks scale gesture events and calculates a velocity based on those
1299
1046
/// events.
1300
1047
///
0 commit comments