Unverified Commit c83237f3 authored by xubaolin's avatar xubaolin Committed by GitHub

[New feature]Introduce iOS multi-touch drag behavior (#141355)

Fixes #38926

This patch implements the iOS behavior pointed out by @dkwingsmt at #38926 , which is also consistent with the performance of my settings application on the iPhone.

### iOS behavior (horizontal or vertical drag)

## Algorithm
When dragging: delta(combined) = max(i of n that are positive) delta(i) - max(i of n that are negative) delta(i)
It means that, if two fingers are moving +50 and +10 respectively, it will move +50; if they're moving at +50 and -10 respectively, it will move +40.

~~TODO~~
~~Write some test cases~~
parent 1da48594
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'package:flutter/gestures.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'button.dart'; import 'button.dart';
...@@ -492,6 +493,9 @@ class CupertinoScrollBehavior extends ScrollBehavior { ...@@ -492,6 +493,9 @@ class CupertinoScrollBehavior extends ScrollBehavior {
} }
return const BouncingScrollPhysics(); return const BouncingScrollPhysics();
} }
@override
MultitouchDragStrategy getMultitouchDragStrategy(BuildContext context) => MultitouchDragStrategy.averageBoundaryPointers;
} }
class _CupertinoAppState extends State<CupertinoApp> { class _CupertinoAppState extends State<CupertinoApp> {
......
...@@ -51,24 +51,53 @@ enum DragStartBehavior { ...@@ -51,24 +51,53 @@ enum DragStartBehavior {
/// Configuration of multi-finger drag strategy on multi-touch devices. /// Configuration of multi-finger drag strategy on multi-touch devices.
/// ///
/// When dragging with only one finger, there's no difference in behavior /// When dragging with only one finger, there's no difference in behavior
/// between the two settings. /// between all the settings.
/// ///
/// Used by [DragGestureRecognizer.multitouchDragStrategy]. /// Used by [DragGestureRecognizer.multitouchDragStrategy].
enum MultitouchDragStrategy { enum MultitouchDragStrategy {
/// Only the latest active pointer is tracked by the recognizer. /// Only the latest active pointer is tracked by the recognizer.
/// ///
/// If the tracked pointer is released, the latest of the remaining active /// If the tracked pointer is released, the first accepted of the remaining active
/// pointers will continue to be tracked. /// pointers will continue to be tracked.
/// ///
/// This is the behavior typically seen on Android. /// This is the behavior typically seen on Android.
latestPointer, latestPointer,
/// All active pointers will be tracked, and the result is computed from
/// the boundary pointers.
///
/// The scrolling offset is determined by the maximum deltas of both directions.
///
/// If the user is dragging with 3 pointers at the same time, each having
/// \[+10, +20, +33\] pixels of offset, the recognizer will report a delta of 33 pixels.
///
/// If the user is dragging with 5 pointers at the same time, each having
/// \[+10, +20, +33, -1, -12\] pixels of offset, the recognizer will report a
/// delta of (+33) + (-12) = 21 pixels.
///
/// The panning [PanGestureRecognizer] offset is the average of all pointers.
///
/// If the user is dragging with 3 pointers at the same time, each having
/// \[+10, +50, -30\] pixels of offset in one direction (horizontal or vertical),
/// the recognizer will report a delta of (10 + 50 -30) / 3 = 10 pixels in this direction.
///
/// This is the behavior typically seen on iOS.
averageBoundaryPointers,
/// All active pointers will be tracked together. The scrolling offset /// All active pointers will be tracked together. The scrolling offset
/// is the sum of the offsets of all active pointers. /// is the sum of the offsets of all active pointers.
/// ///
/// When a [Scrollable] drives scrolling by this drag strategy, the scrolling /// When a [Scrollable] drives scrolling by this drag strategy, the scrolling
/// speed will double or triple, depending on how many fingers are dragging /// speed will double or triple, depending on how many fingers are dragging
/// at the same time. /// at the same time.
///
/// If the user is dragging with 3 pointers at the same time, each having
/// \[+10, +20, +33\] pixels of offset, the recognizer will report a delta
/// of 10 + 20 + 33 = 63 pixels.
///
/// If the user is dragging with 5 pointers at the same time, each having
/// \[+10, +20, +33, -1, -12\] pixels of offset, the recognizer will report
/// a delta of 10 + 20 + 33 - 1 - 12 = 50 pixels.
sumAllPointers, sumAllPointers,
} }
......
...@@ -9,6 +9,7 @@ import 'package:flutter/rendering.dart'; ...@@ -9,6 +9,7 @@ import 'package:flutter/rendering.dart';
import 'basic.dart'; import 'basic.dart';
import 'framework.dart'; import 'framework.dart';
import 'media_query.dart'; import 'media_query.dart';
import 'scroll_configuration.dart';
export 'package:flutter/gestures.dart' show export 'package:flutter/gestures.dart' show
DragDownDetails, DragDownDetails,
...@@ -1020,6 +1021,7 @@ class GestureDetector extends StatelessWidget { ...@@ -1020,6 +1021,7 @@ class GestureDetector extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final Map<Type, GestureRecognizerFactory> gestures = <Type, GestureRecognizerFactory>{}; final Map<Type, GestureRecognizerFactory> gestures = <Type, GestureRecognizerFactory>{};
final DeviceGestureSettings? gestureSettings = MediaQuery.maybeGestureSettingsOf(context); final DeviceGestureSettings? gestureSettings = MediaQuery.maybeGestureSettingsOf(context);
final ScrollBehavior configuration = ScrollConfiguration.of(context);
if (onTapDown != null || if (onTapDown != null ||
onTapUp != null || onTapUp != null ||
...@@ -1137,6 +1139,7 @@ class GestureDetector extends StatelessWidget { ...@@ -1137,6 +1139,7 @@ class GestureDetector extends StatelessWidget {
..onEnd = onVerticalDragEnd ..onEnd = onVerticalDragEnd
..onCancel = onVerticalDragCancel ..onCancel = onVerticalDragCancel
..dragStartBehavior = dragStartBehavior ..dragStartBehavior = dragStartBehavior
..multitouchDragStrategy = configuration.getMultitouchDragStrategy(context)
..gestureSettings = gestureSettings ..gestureSettings = gestureSettings
..supportedDevices = supportedDevices; ..supportedDevices = supportedDevices;
}, },
...@@ -1158,6 +1161,7 @@ class GestureDetector extends StatelessWidget { ...@@ -1158,6 +1161,7 @@ class GestureDetector extends StatelessWidget {
..onEnd = onHorizontalDragEnd ..onEnd = onHorizontalDragEnd
..onCancel = onHorizontalDragCancel ..onCancel = onHorizontalDragCancel
..dragStartBehavior = dragStartBehavior ..dragStartBehavior = dragStartBehavior
..multitouchDragStrategy = configuration.getMultitouchDragStrategy(context)
..gestureSettings = gestureSettings ..gestureSettings = gestureSettings
..supportedDevices = supportedDevices; ..supportedDevices = supportedDevices;
}, },
...@@ -1179,6 +1183,7 @@ class GestureDetector extends StatelessWidget { ...@@ -1179,6 +1183,7 @@ class GestureDetector extends StatelessWidget {
..onEnd = onPanEnd ..onEnd = onPanEnd
..onCancel = onPanCancel ..onCancel = onPanCancel
..dragStartBehavior = dragStartBehavior ..dragStartBehavior = dragStartBehavior
..multitouchDragStrategy = configuration.getMultitouchDragStrategy(context)
..gestureSettings = gestureSettings ..gestureSettings = gestureSettings
..supportedDevices = supportedDevices; ..supportedDevices = supportedDevices;
}, },
......
...@@ -110,8 +110,20 @@ class ScrollBehavior { ...@@ -110,8 +110,20 @@ class ScrollBehavior {
/// {@macro flutter.gestures.monodrag.DragGestureRecognizer.multitouchDragStrategy} /// {@macro flutter.gestures.monodrag.DragGestureRecognizer.multitouchDragStrategy}
/// ///
/// By default, [MultitouchDragStrategy.latestPointer] is configured to /// By default, [MultitouchDragStrategy.latestPointer] is configured to
/// create drag gestures for all platforms. /// create drag gestures for non-Apple platforms, and
MultitouchDragStrategy get multitouchDragStrategy => MultitouchDragStrategy.latestPointer; /// [MultitouchDragStrategy.averageBoundaryPointers] for Apple platforms.
MultitouchDragStrategy getMultitouchDragStrategy(BuildContext context) {
switch (getPlatform(context)) {
case TargetPlatform.macOS:
case TargetPlatform.iOS:
return MultitouchDragStrategy.averageBoundaryPointers;
case TargetPlatform.linux:
case TargetPlatform.windows:
case TargetPlatform.android:
case TargetPlatform.fuchsia:
return MultitouchDragStrategy.latestPointer;
}
}
/// A set of [LogicalKeyboardKey]s that, when any or all are pressed in /// A set of [LogicalKeyboardKey]s that, when any or all are pressed in
/// combination with a [PointerDeviceKind.mouse] pointer scroll event, will /// combination with a [PointerDeviceKind.mouse] pointer scroll event, will
...@@ -253,12 +265,11 @@ class _WrappedScrollBehavior implements ScrollBehavior { ...@@ -253,12 +265,11 @@ class _WrappedScrollBehavior implements ScrollBehavior {
this.scrollbars = true, this.scrollbars = true,
this.overscroll = true, this.overscroll = true,
Set<PointerDeviceKind>? dragDevices, Set<PointerDeviceKind>? dragDevices,
MultitouchDragStrategy? multitouchDragStrategy, this.multitouchDragStrategy,
Set<LogicalKeyboardKey>? pointerAxisModifiers, Set<LogicalKeyboardKey>? pointerAxisModifiers,
this.physics, this.physics,
this.platform, this.platform,
}) : _dragDevices = dragDevices, }) : _dragDevices = dragDevices,
_multitouchDragStrategy = multitouchDragStrategy,
_pointerAxisModifiers = pointerAxisModifiers; _pointerAxisModifiers = pointerAxisModifiers;
final ScrollBehavior delegate; final ScrollBehavior delegate;
...@@ -267,17 +278,19 @@ class _WrappedScrollBehavior implements ScrollBehavior { ...@@ -267,17 +278,19 @@ class _WrappedScrollBehavior implements ScrollBehavior {
final ScrollPhysics? physics; final ScrollPhysics? physics;
final TargetPlatform? platform; final TargetPlatform? platform;
final Set<PointerDeviceKind>? _dragDevices; final Set<PointerDeviceKind>? _dragDevices;
final MultitouchDragStrategy? _multitouchDragStrategy; final MultitouchDragStrategy? multitouchDragStrategy;
final Set<LogicalKeyboardKey>? _pointerAxisModifiers; final Set<LogicalKeyboardKey>? _pointerAxisModifiers;
@override @override
Set<PointerDeviceKind> get dragDevices => _dragDevices ?? delegate.dragDevices; Set<PointerDeviceKind> get dragDevices => _dragDevices ?? delegate.dragDevices;
@override @override
MultitouchDragStrategy get multitouchDragStrategy => _multitouchDragStrategy ?? delegate.multitouchDragStrategy; Set<LogicalKeyboardKey> get pointerAxisModifiers => _pointerAxisModifiers ?? delegate.pointerAxisModifiers;
@override @override
Set<LogicalKeyboardKey> get pointerAxisModifiers => _pointerAxisModifiers ?? delegate.pointerAxisModifiers; MultitouchDragStrategy getMultitouchDragStrategy(BuildContext context) {
return multitouchDragStrategy ?? delegate.getMultitouchDragStrategy(context);
}
@override @override
Widget buildOverscrollIndicator(BuildContext context, Widget child, ScrollableDetails details) { Widget buildOverscrollIndicator(BuildContext context, Widget child, ScrollableDetails details) {
......
...@@ -758,7 +758,7 @@ class ScrollableState extends State<Scrollable> with TickerProviderStateMixin, R ...@@ -758,7 +758,7 @@ class ScrollableState extends State<Scrollable> with TickerProviderStateMixin, R
..maxFlingVelocity = _physics?.maxFlingVelocity ..maxFlingVelocity = _physics?.maxFlingVelocity
..velocityTrackerBuilder = _configuration.velocityTrackerBuilder(context) ..velocityTrackerBuilder = _configuration.velocityTrackerBuilder(context)
..dragStartBehavior = widget.dragStartBehavior ..dragStartBehavior = widget.dragStartBehavior
..multitouchDragStrategy = _configuration.multitouchDragStrategy ..multitouchDragStrategy = _configuration.getMultitouchDragStrategy(context)
..gestureSettings = _mediaQueryGestureSettings ..gestureSettings = _mediaQueryGestureSettings
..supportedDevices = _configuration.dragDevices; ..supportedDevices = _configuration.dragDevices;
}, },
...@@ -780,7 +780,7 @@ class ScrollableState extends State<Scrollable> with TickerProviderStateMixin, R ...@@ -780,7 +780,7 @@ class ScrollableState extends State<Scrollable> with TickerProviderStateMixin, R
..maxFlingVelocity = _physics?.maxFlingVelocity ..maxFlingVelocity = _physics?.maxFlingVelocity
..velocityTrackerBuilder = _configuration.velocityTrackerBuilder(context) ..velocityTrackerBuilder = _configuration.velocityTrackerBuilder(context)
..dragStartBehavior = widget.dragStartBehavior ..dragStartBehavior = widget.dragStartBehavior
..multitouchDragStrategy = _configuration.multitouchDragStrategy ..multitouchDragStrategy = _configuration.getMultitouchDragStrategy(context)
..gestureSettings = _mediaQueryGestureSettings ..gestureSettings = _mediaQueryGestureSettings
..supportedDevices = _configuration.dragDevices; ..supportedDevices = _configuration.dragDevices;
}, },
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
...@@ -352,6 +353,24 @@ void main() { ...@@ -352,6 +353,24 @@ void main() {
expect(ScrollConfiguration.of(capturedContext).runtimeType, CupertinoScrollBehavior); expect(ScrollConfiguration.of(capturedContext).runtimeType, CupertinoScrollBehavior);
}); });
testWidgets('CupertinoApp has correct default multitouchDragStrategy', (WidgetTester tester) async {
late BuildContext capturedContext;
await tester.pumpWidget(
CupertinoApp(
home: Builder(
builder: (BuildContext context) {
capturedContext = context;
return const Placeholder();
},
),
),
);
final ScrollBehavior scrollBehavior = ScrollConfiguration.of(capturedContext);
expect(scrollBehavior.runtimeType, CupertinoScrollBehavior);
expect(scrollBehavior.getMultitouchDragStrategy(capturedContext), MultitouchDragStrategy.averageBoundaryPointers);
});
testWidgets('A ScrollBehavior can be set for CupertinoApp', (WidgetTester tester) async { testWidgets('A ScrollBehavior can be set for CupertinoApp', (WidgetTester tester) async {
late BuildContext capturedContext; late BuildContext capturedContext;
await tester.pumpWidget( await tester.pumpWidget(
......
...@@ -416,7 +416,7 @@ void main() { ...@@ -416,7 +416,7 @@ void main() {
), ),
); );
await tester.drag(find.text('10'), const Offset(0.0, 32.0), touchSlopY: 0, warnIfMissed: false); // see top of file await tester.drag(find.text('10'), const Offset(0.0, 32.0), pointer: 1, touchSlopY: 0, warnIfMissed: false); // see top of file
await tester.pump(); await tester.pump();
await tester.pump(const Duration(milliseconds: 500)); await tester.pump(const Duration(milliseconds: 500));
...@@ -438,7 +438,7 @@ void main() { ...@@ -438,7 +438,7 @@ void main() {
), ),
); );
await tester.drag(find.text('9'), const Offset(0.0, 32.0), touchSlopY: 0, warnIfMissed: false); // see top of file await tester.drag(find.text('9'), const Offset(0.0, 32.0), pointer: 1, touchSlopY: 0, warnIfMissed: false); // see top of file
await tester.pump(); await tester.pump();
await tester.pump(const Duration(milliseconds: 500)); await tester.pump(const Duration(milliseconds: 500));
...@@ -1246,14 +1246,14 @@ void main() { ...@@ -1246,14 +1246,14 @@ void main() {
), ),
); );
await tester.drag(find.text('27'), const Offset(0.0, -32.0), touchSlopY: 0.0, warnIfMissed: false); // see top of file await tester.drag(find.text('27'), const Offset(0.0, -32.0), pointer: 1, touchSlopY: 0.0, warnIfMissed: false); // see top of file
await tester.pump(); await tester.pump();
expect( expect(
date, date,
DateTime(2018, 2, 28), DateTime(2018, 2, 28),
); );
await tester.drag(find.text('28'), const Offset(0.0, -32.0), touchSlopY: 0.0, warnIfMissed: false); // see top of file await tester.drag(find.text('28'), const Offset(0.0, -32.0), pointer: 1, touchSlopY: 0.0, warnIfMissed: false); // see top of file
await tester.pump(); // Once to trigger the post frame animate call. await tester.pump(); // Once to trigger the post frame animate call.
// Callback doesn't transiently go into invalid dates. // Callback doesn't transiently go into invalid dates.
......
...@@ -343,7 +343,7 @@ void main() { ...@@ -343,7 +343,7 @@ void main() {
); );
// Drag it by a bit but not enough to move to the next item. // Drag it by a bit but not enough to move to the next item.
await tester.drag(find.text('10'), const Offset(0.0, 30.0), touchSlopY: 0.0, warnIfMissed: false); // has an IgnorePointer await tester.drag(find.text('10'), const Offset(0.0, 30.0), pointer: 1, touchSlopY: 0.0, warnIfMissed: false); // has an IgnorePointer
// The item that was in the center now moved a bit. // The item that was in the center now moved a bit.
expect( expect(
...@@ -360,7 +360,7 @@ void main() { ...@@ -360,7 +360,7 @@ void main() {
expect(selectedItems.isEmpty, true); expect(selectedItems.isEmpty, true);
// Drag it by enough to move to the next item. // Drag it by enough to move to the next item.
await tester.drag(find.text('10'), const Offset(0.0, 70.0), touchSlopY: 0.0, warnIfMissed: false); // has an IgnorePointer await tester.drag(find.text('10'), const Offset(0.0, 70.0), pointer: 1, touchSlopY: 0.0, warnIfMissed: false); // has an IgnorePointer
await tester.pumpAndSettle(); await tester.pumpAndSettle();
......
...@@ -705,7 +705,7 @@ void main() { ...@@ -705,7 +705,7 @@ void main() {
), ),
); );
await tester.drag(find.text('0'), const Offset(0.0, 150.0), touchSlopY: 0.0); await tester.drag(find.text('0'), const Offset(0.0, 150.0), pointer: 1, touchSlopY: 0.0);
await tester.pump(); await tester.pump();
expect(mockHelper.invocations, contains(const RefreshTaskInvocation())); expect(mockHelper.invocations, contains(const RefreshTaskInvocation()));
...@@ -748,7 +748,7 @@ void main() { ...@@ -748,7 +748,7 @@ void main() {
// Start another drag by an amount that would have been enough to // Start another drag by an amount that would have been enough to
// trigger another refresh if it were in the right state. // trigger another refresh if it were in the right state.
await tester.drag(find.text('0'), const Offset(0.0, 150.0), touchSlopY: 0.0, warnIfMissed: false); await tester.drag(find.text('0'), const Offset(0.0, 150.0), pointer: 1, touchSlopY: 0.0, warnIfMissed: false);
await tester.pump(); await tester.pump();
// Instead, it's still in the done state because the sliver never // Instead, it's still in the done state because the sliver never
...@@ -779,7 +779,7 @@ void main() { ...@@ -779,7 +779,7 @@ void main() {
); );
// Start another drag. It's now in drag mode. // Start another drag. It's now in drag mode.
await tester.drag(find.text('0'), const Offset(0.0, 40.0), touchSlopY: 0.0); await tester.drag(find.text('0'), const Offset(0.0, 40.0), pointer: 1, touchSlopY: 0.0);
await tester.pump(); await tester.pump();
expect(mockHelper.invocations, contains(matchesBuilder( expect(mockHelper.invocations, contains(matchesBuilder(
refreshState: RefreshIndicatorMode.drag, refreshState: RefreshIndicatorMode.drag,
......
...@@ -3070,6 +3070,7 @@ void main() { ...@@ -3070,6 +3070,7 @@ void main() {
await tester.drag( await tester.drag(
find.byType(CustomScrollView), find.byType(CustomScrollView),
const Offset(0.0, -20.0), const Offset(0.0, -20.0),
pointer: 1,
); );
await tester.pumpAndSettle(); await tester.pumpAndSettle();
final NestedScrollViewState nestedScrollView = tester.state<NestedScrollViewState>( final NestedScrollViewState nestedScrollView = tester.state<NestedScrollViewState>(
......
...@@ -154,7 +154,7 @@ void main() { ...@@ -154,7 +154,7 @@ void main() {
expect(find.byType(GlowingOverscrollIndicator), findsOneWidget); expect(find.byType(GlowingOverscrollIndicator), findsOneWidget);
}, variant: TargetPlatformVariant.only(TargetPlatform.android)); }, variant: TargetPlatformVariant.only(TargetPlatform.android));
testWidgets('ScrollBehavior multitouchDragStrategy test', (WidgetTester tester) async { testWidgets('ScrollBehavior multitouchDragStrategy test - 1', (WidgetTester tester) async {
const ScrollBehavior behavior1 = ScrollBehavior(); const ScrollBehavior behavior1 = ScrollBehavior();
final ScrollBehavior behavior2 = const ScrollBehavior().copyWith( final ScrollBehavior behavior2 = const ScrollBehavior().copyWith(
multitouchDragStrategy: MultitouchDragStrategy.sumAllPointers multitouchDragStrategy: MultitouchDragStrategy.sumAllPointers
...@@ -201,11 +201,11 @@ void main() { ...@@ -201,11 +201,11 @@ void main() {
await gesture2.moveBy(const Offset(0, -50)); await gesture2.moveBy(const Offset(0, -50));
await tester.pump(); await tester.pump();
// The default multitouchDragStrategy should be MultitouchDragStrategy.latestPointer. // The default multitouchDragStrategy is 'latestPointer' or 'averageBoundaryPointers,
// Only the latest active pointer be tracked. // the received delta should be 50.0.
expect(controller.position.pixels, 50.0); expect(controller.position.pixels, 50.0);
// Change to MultitouchDragStrategy.sumAllPointers. // Change to sumAllPointers.
await tester.pumpWidget(buildFrame(behavior2)); await tester.pumpWidget(buildFrame(behavior2));
await gesture1.moveBy(const Offset(0, -50)); await gesture1.moveBy(const Offset(0, -50));
...@@ -218,6 +218,147 @@ void main() { ...@@ -218,6 +218,147 @@ void main() {
expect(controller.position.pixels, 50.0 + 50.0 + 50.0); expect(controller.position.pixels, 50.0 + 50.0 + 50.0);
}, variant: TargetPlatformVariant.all()); }, variant: TargetPlatformVariant.all());
testWidgets('ScrollBehavior multitouchDragStrategy test (non-Apple platforms) - 2', (WidgetTester tester) async {
const ScrollBehavior behavior1 = ScrollBehavior();
final ScrollBehavior behavior2 = const ScrollBehavior().copyWith(
multitouchDragStrategy: MultitouchDragStrategy.averageBoundaryPointers
);
final ScrollController controller = ScrollController();
late BuildContext capturedContext;
addTearDown(() => controller.dispose());
Widget buildFrame(ScrollBehavior behavior) {
return Directionality(
textDirection: TextDirection.ltr,
child: ScrollConfiguration(
behavior: behavior,
child: Builder(
builder: (BuildContext context) {
capturedContext = context;
return ListView(
controller: controller,
children: const <Widget>[
SizedBox(
height: 1000.0,
width: 1000.0,
child: Text('I Love Flutter!'),
),
],
);
},
),
),
);
}
await tester.pumpWidget(buildFrame(behavior1));
expect(controller.position.pixels, 0.0);
final Offset listLocation = tester.getCenter(find.byType(ListView));
final TestGesture gesture1 = await tester.createGesture(pointer: 1);
await gesture1.down(listLocation);
await tester.pump();
final TestGesture gesture2 = await tester.createGesture(pointer: 2);
await gesture2.down(listLocation);
await tester.pump();
await gesture1.moveBy(const Offset(0, -50));
await tester.pump();
await gesture2.moveBy(const Offset(0, -40));
await tester.pump();
// The default multitouchDragStrategy is latestPointer.
// Only the latest active pointer be tracked.
final ScrollBehavior scrollBehavior = ScrollConfiguration.of(capturedContext);
expect(scrollBehavior.getMultitouchDragStrategy(capturedContext), MultitouchDragStrategy.latestPointer);
expect(controller.position.pixels, 40.0);
// Change to averageBoundaryPointers.
await tester.pumpWidget(buildFrame(behavior2));
await gesture1.moveBy(const Offset(0, -70));
await tester.pump();
await gesture2.moveBy(const Offset(0, -60));
await tester.pump();
expect(controller.position.pixels, 40.0 + 70.0);
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.android, TargetPlatform.linux, TargetPlatform.fuchsia, TargetPlatform.windows }));
testWidgets('ScrollBehavior multitouchDragStrategy test (Apple platforms) - 3', (WidgetTester tester) async {
const ScrollBehavior behavior1 = ScrollBehavior();
final ScrollBehavior behavior2 = const ScrollBehavior().copyWith(
multitouchDragStrategy: MultitouchDragStrategy.latestPointer
);
final ScrollController controller = ScrollController();
late BuildContext capturedContext;
addTearDown(() => controller.dispose());
Widget buildFrame(ScrollBehavior behavior) {
return Directionality(
textDirection: TextDirection.ltr,
child: ScrollConfiguration(
behavior: behavior,
child: Builder(
builder: (BuildContext context) {
capturedContext = context;
return ListView(
controller: controller,
children: const <Widget>[
SizedBox(
height: 1000.0,
width: 1000.0,
child: Text('I Love Flutter!'),
),
],
);
},
),
),
);
}
await tester.pumpWidget(buildFrame(behavior1));
expect(controller.position.pixels, 0.0);
final Offset listLocation = tester.getCenter(find.byType(ListView));
final TestGesture gesture1 = await tester.createGesture(pointer: 1);
await gesture1.down(listLocation);
await tester.pump();
final TestGesture gesture2 = await tester.createGesture(pointer: 2);
await gesture2.down(listLocation);
await tester.pump();
await gesture1.moveBy(const Offset(0, -40));
await tester.pump();
await gesture2.moveBy(const Offset(0, -50));
await tester.pump();
// The default multitouchDragStrategy is averageBoundaryPointers.
final ScrollBehavior scrollBehavior = ScrollConfiguration.of(capturedContext);
expect(scrollBehavior.getMultitouchDragStrategy(capturedContext), MultitouchDragStrategy.averageBoundaryPointers);
expect(controller.position.pixels, 50.0);
// Change to latestPointer.
await tester.pumpWidget(buildFrame(behavior2));
await gesture1.moveBy(const Offset(0, -50));
await tester.pump();
await gesture2.moveBy(const Offset(0, -40));
await tester.pump();
expect(controller.position.pixels, 50.0 + 40.0);
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }));
group('ScrollBehavior configuration is maintained over multiple copies', () { group('ScrollBehavior configuration is maintained over multiple copies', () {
testWidgets('dragDevices', (WidgetTester tester) async { testWidgets('dragDevices', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/91673 // Regression test for https://github.com/flutter/flutter/issues/91673
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment