Support changing the dismissable threshold (#8007)
* Support changing the dismissable threshold for any direction for dismissable widget. * Fixing review comments.
This commit is contained in:
parent
d1d1c50cbe
commit
ca2c54bac2
@ -79,7 +79,8 @@ class Dismissable extends StatefulWidget {
|
|||||||
this.onResize,
|
this.onResize,
|
||||||
this.onDismissed,
|
this.onDismissed,
|
||||||
this.direction: DismissDirection.horizontal,
|
this.direction: DismissDirection.horizontal,
|
||||||
this.resizeDuration: const Duration(milliseconds: 300)
|
this.resizeDuration: const Duration(milliseconds: 300),
|
||||||
|
this.dismissThresholds: const <DismissDirection, double>{},
|
||||||
}) : super(key: key) {
|
}) : super(key: key) {
|
||||||
assert(key != null);
|
assert(key != null);
|
||||||
assert(secondaryBackground != null ? background != null : true);
|
assert(secondaryBackground != null ? background != null : true);
|
||||||
@ -113,6 +114,14 @@ class Dismissable extends StatefulWidget {
|
|||||||
/// immediately after the the widget is dismissed.
|
/// immediately after the the widget is dismissed.
|
||||||
final Duration resizeDuration;
|
final Duration resizeDuration;
|
||||||
|
|
||||||
|
/// The offset threshold the item has to be dragged in order to be considered dismissed.
|
||||||
|
///
|
||||||
|
/// Represented as a fraction, e.g. if it is 0.4, then the item has to be dragged at least
|
||||||
|
/// 40% towards one direction to be considered dismissed. Clients can define different
|
||||||
|
/// thresholds for each dismiss direction. This allows for use cases where item can be
|
||||||
|
/// dismissed to end but not to start.
|
||||||
|
final Map<DismissDirection, double> dismissThresholds;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_DismissableState createState() => new _DismissableState();
|
_DismissableState createState() => new _DismissableState();
|
||||||
}
|
}
|
||||||
@ -195,6 +204,10 @@ class _DismissableState extends State<Dismissable> with TickerProviderStateMixin
|
|||||||
return _dragExtent > 0 ? DismissDirection.down : DismissDirection.up;
|
return _dragExtent > 0 ? DismissDirection.down : DismissDirection.up;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
double get _dismissThreshold {
|
||||||
|
return config.dismissThresholds[_dismissDirection] ?? _kDismissThreshold;
|
||||||
|
}
|
||||||
|
|
||||||
bool get _isActive {
|
bool get _isActive {
|
||||||
return _dragUnderway || _moveController.isAnimating;
|
return _dragUnderway || _moveController.isAnimating;
|
||||||
}
|
}
|
||||||
@ -262,6 +275,9 @@ class _DismissableState extends State<Dismissable> with TickerProviderStateMixin
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool _isFlingGesture(Velocity velocity) {
|
bool _isFlingGesture(Velocity velocity) {
|
||||||
|
// Cannot fling an item if it cannot be dismissed by drag.
|
||||||
|
if (_dismissThreshold >= 1.0)
|
||||||
|
return false;
|
||||||
final double vx = velocity.pixelsPerSecond.dx;
|
final double vx = velocity.pixelsPerSecond.dx;
|
||||||
final double vy = velocity.pixelsPerSecond.dy;
|
final double vy = velocity.pixelsPerSecond.dy;
|
||||||
if (_directionIsXAxis) {
|
if (_directionIsXAxis) {
|
||||||
@ -299,7 +315,7 @@ class _DismissableState extends State<Dismissable> with TickerProviderStateMixin
|
|||||||
final double flingVelocity = _directionIsXAxis ? details.velocity.pixelsPerSecond.dx : details.velocity.pixelsPerSecond.dy;
|
final double flingVelocity = _directionIsXAxis ? details.velocity.pixelsPerSecond.dx : details.velocity.pixelsPerSecond.dy;
|
||||||
_dragExtent = flingVelocity.sign;
|
_dragExtent = flingVelocity.sign;
|
||||||
_moveController.fling(velocity: flingVelocity.abs() * _kFlingVelocityScale);
|
_moveController.fling(velocity: flingVelocity.abs() * _kFlingVelocityScale);
|
||||||
} else if (_moveController.value > _kDismissThreshold) {
|
} else if (_moveController.value > _dismissThreshold) {
|
||||||
_moveController.forward();
|
_moveController.forward();
|
||||||
} else {
|
} else {
|
||||||
_moveController.reverse();
|
_moveController.reverse();
|
||||||
|
@ -23,31 +23,38 @@ void handleOnDismissed(DismissDirection direction, int item) {
|
|||||||
dismissedItems.add(item);
|
dismissedItems.add(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget buildDismissableItem(int item) {
|
Widget buildTest({ double startToEndThreshold }) {
|
||||||
return new Dismissable(
|
Widget buildDismissableItem(int item) {
|
||||||
key: new ValueKey<int>(item),
|
return new Dismissable(
|
||||||
direction: dismissDirection,
|
key: new ValueKey<int>(item),
|
||||||
onDismissed: (DismissDirection direction) { handleOnDismissed(direction, item); },
|
direction: dismissDirection,
|
||||||
onResize: () { handleOnResize(item); },
|
onDismissed: (DismissDirection direction) {
|
||||||
background: background,
|
handleOnDismissed(direction, item);
|
||||||
child: new Container(
|
},
|
||||||
width: itemExtent,
|
onResize: () {
|
||||||
height: itemExtent,
|
handleOnResize(item);
|
||||||
child: new Text(item.toString())
|
},
|
||||||
)
|
background: background,
|
||||||
);
|
dismissThresholds: startToEndThreshold == null
|
||||||
}
|
? <DismissDirection, double>{}
|
||||||
|
: <DismissDirection, double>{DismissDirection.startToEnd: startToEndThreshold},
|
||||||
|
child: new Container(
|
||||||
|
width: itemExtent,
|
||||||
|
height: itemExtent,
|
||||||
|
child: new Text(item.toString())
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Widget widgetBuilder() {
|
|
||||||
return new Container(
|
return new Container(
|
||||||
padding: const EdgeInsets.all(10.0),
|
padding: const EdgeInsets.all(10.0),
|
||||||
child: new ScrollableList(
|
child: new ScrollableList(
|
||||||
scrollDirection: scrollDirection,
|
scrollDirection: scrollDirection,
|
||||||
itemExtent: itemExtent,
|
itemExtent: itemExtent,
|
||||||
children: <int>[0, 1, 2, 3, 4].where(
|
children: <int>[0, 1, 2, 3, 4]
|
||||||
(int i) => !dismissedItems.contains(i)
|
.where((int i) => !dismissedItems.contains(i))
|
||||||
).map(buildDismissableItem)
|
.map(buildDismissableItem)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,7 +65,7 @@ Future<Null> dismissElement(WidgetTester tester, Finder finder, { DismissDirecti
|
|||||||
|
|
||||||
Point downLocation;
|
Point downLocation;
|
||||||
Point upLocation;
|
Point upLocation;
|
||||||
switch(gestureDirection) {
|
switch (gestureDirection) {
|
||||||
case DismissDirection.endToStart:
|
case DismissDirection.endToStart:
|
||||||
// getTopRight() returns a point that's just beyond itemWidget's right
|
// getTopRight() returns a point that's just beyond itemWidget's right
|
||||||
// edge and outside the Dismissable event listener's bounds.
|
// edge and outside the Dismissable event listener's bounds.
|
||||||
@ -99,11 +106,11 @@ Future<Null> dismissItem(WidgetTester tester, int item, { DismissDirection gestu
|
|||||||
|
|
||||||
await dismissElement(tester, itemFinder, gestureDirection: gestureDirection);
|
await dismissElement(tester, itemFinder, gestureDirection: gestureDirection);
|
||||||
|
|
||||||
await tester.pumpWidget(widgetBuilder()); // start the slide
|
await tester.pumpWidget(buildTest()); // start the slide
|
||||||
await tester.pumpWidget(widgetBuilder(), const Duration(seconds: 1)); // finish the slide and start shrinking...
|
await tester.pumpWidget(buildTest(), const Duration(seconds: 1)); // finish the slide and start shrinking...
|
||||||
await tester.pumpWidget(widgetBuilder()); // first frame of shrinking animation
|
await tester.pumpWidget(buildTest()); // first frame of shrinking animation
|
||||||
await tester.pumpWidget(widgetBuilder(), const Duration(seconds: 1)); // finish the shrinking and call the callback...
|
await tester.pumpWidget(buildTest(), const Duration(seconds: 1)); // finish the shrinking and call the callback...
|
||||||
await tester.pumpWidget(widgetBuilder()); // rebuild after the callback removes the entry
|
await tester.pumpWidget(buildTest()); // rebuild after the callback removes the entry
|
||||||
}
|
}
|
||||||
|
|
||||||
class Test1215DismissableWidget extends StatelessWidget {
|
class Test1215DismissableWidget extends StatelessWidget {
|
||||||
@ -133,7 +140,7 @@ void main() {
|
|||||||
scrollDirection = Axis.vertical;
|
scrollDirection = Axis.vertical;
|
||||||
dismissDirection = DismissDirection.horizontal;
|
dismissDirection = DismissDirection.horizontal;
|
||||||
|
|
||||||
await tester.pumpWidget(widgetBuilder());
|
await tester.pumpWidget(buildTest());
|
||||||
expect(dismissedItems, isEmpty);
|
expect(dismissedItems, isEmpty);
|
||||||
|
|
||||||
await dismissItem(tester, 0, gestureDirection: DismissDirection.startToEnd);
|
await dismissItem(tester, 0, gestureDirection: DismissDirection.startToEnd);
|
||||||
@ -151,7 +158,7 @@ void main() {
|
|||||||
scrollDirection = Axis.horizontal;
|
scrollDirection = Axis.horizontal;
|
||||||
dismissDirection = DismissDirection.vertical;
|
dismissDirection = DismissDirection.vertical;
|
||||||
|
|
||||||
await tester.pumpWidget(widgetBuilder());
|
await tester.pumpWidget(buildTest());
|
||||||
expect(dismissedItems, isEmpty);
|
expect(dismissedItems, isEmpty);
|
||||||
|
|
||||||
await dismissItem(tester, 0, gestureDirection: DismissDirection.up);
|
await dismissItem(tester, 0, gestureDirection: DismissDirection.up);
|
||||||
@ -169,7 +176,7 @@ void main() {
|
|||||||
scrollDirection = Axis.vertical;
|
scrollDirection = Axis.vertical;
|
||||||
dismissDirection = DismissDirection.endToStart;
|
dismissDirection = DismissDirection.endToStart;
|
||||||
|
|
||||||
await tester.pumpWidget(widgetBuilder());
|
await tester.pumpWidget(buildTest());
|
||||||
expect(dismissedItems, isEmpty);
|
expect(dismissedItems, isEmpty);
|
||||||
|
|
||||||
await dismissItem(tester, 0, gestureDirection: DismissDirection.startToEnd);
|
await dismissItem(tester, 0, gestureDirection: DismissDirection.startToEnd);
|
||||||
@ -187,7 +194,7 @@ void main() {
|
|||||||
scrollDirection = Axis.vertical;
|
scrollDirection = Axis.vertical;
|
||||||
dismissDirection = DismissDirection.startToEnd;
|
dismissDirection = DismissDirection.startToEnd;
|
||||||
|
|
||||||
await tester.pumpWidget(widgetBuilder());
|
await tester.pumpWidget(buildTest());
|
||||||
expect(dismissedItems, isEmpty);
|
expect(dismissedItems, isEmpty);
|
||||||
|
|
||||||
await dismissItem(tester, 0, gestureDirection: DismissDirection.endToStart);
|
await dismissItem(tester, 0, gestureDirection: DismissDirection.endToStart);
|
||||||
@ -203,7 +210,7 @@ void main() {
|
|||||||
scrollDirection = Axis.horizontal;
|
scrollDirection = Axis.horizontal;
|
||||||
dismissDirection = DismissDirection.up;
|
dismissDirection = DismissDirection.up;
|
||||||
|
|
||||||
await tester.pumpWidget(widgetBuilder());
|
await tester.pumpWidget(buildTest());
|
||||||
expect(dismissedItems, isEmpty);
|
expect(dismissedItems, isEmpty);
|
||||||
|
|
||||||
await dismissItem(tester, 0, gestureDirection: DismissDirection.down);
|
await dismissItem(tester, 0, gestureDirection: DismissDirection.down);
|
||||||
@ -219,7 +226,7 @@ void main() {
|
|||||||
scrollDirection = Axis.horizontal;
|
scrollDirection = Axis.horizontal;
|
||||||
dismissDirection = DismissDirection.down;
|
dismissDirection = DismissDirection.down;
|
||||||
|
|
||||||
await tester.pumpWidget(widgetBuilder());
|
await tester.pumpWidget(buildTest());
|
||||||
expect(dismissedItems, isEmpty);
|
expect(dismissedItems, isEmpty);
|
||||||
|
|
||||||
await dismissItem(tester, 0, gestureDirection: DismissDirection.up);
|
await dismissItem(tester, 0, gestureDirection: DismissDirection.up);
|
||||||
@ -231,6 +238,22 @@ void main() {
|
|||||||
expect(dismissedItems, equals(<int>[0]));
|
expect(dismissedItems, equals(<int>[0]));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('drag-left has no effect on dismissable with a high dismiss threshold', (WidgetTester tester) async {
|
||||||
|
scrollDirection = Axis.vertical;
|
||||||
|
dismissDirection = DismissDirection.horizontal;
|
||||||
|
|
||||||
|
await tester.pumpWidget(buildTest(startToEndThreshold: 1.0));
|
||||||
|
expect(dismissedItems, isEmpty);
|
||||||
|
|
||||||
|
await dismissItem(tester, 0, gestureDirection: DismissDirection.startToEnd);
|
||||||
|
expect(find.text('0'), findsOneWidget);
|
||||||
|
expect(dismissedItems, isEmpty);
|
||||||
|
|
||||||
|
await dismissItem(tester, 0, gestureDirection: DismissDirection.endToStart);
|
||||||
|
expect(find.text('0'), findsNothing);
|
||||||
|
expect(dismissedItems, equals(<int>[0]));
|
||||||
|
});
|
||||||
|
|
||||||
// This is a regression test for an fn2 bug where dragging a card caused an
|
// This is a regression test for an fn2 bug where dragging a card caused an
|
||||||
// assert "'!_disqualifiedFromEverAppearingAgain' is not true". The old URL
|
// assert "'!_disqualifiedFromEverAppearingAgain' is not true". The old URL
|
||||||
// was https://github.com/domokit/sky_engine/issues/1068 but that issue is 404
|
// was https://github.com/domokit/sky_engine/issues/1068 but that issue is 404
|
||||||
@ -241,22 +264,22 @@ void main() {
|
|||||||
scrollDirection = Axis.horizontal;
|
scrollDirection = Axis.horizontal;
|
||||||
dismissDirection = DismissDirection.down;
|
dismissDirection = DismissDirection.down;
|
||||||
|
|
||||||
await tester.pumpWidget(widgetBuilder());
|
await tester.pumpWidget(buildTest());
|
||||||
Point location = tester.getTopLeft(find.text('0'));
|
Point location = tester.getTopLeft(find.text('0'));
|
||||||
Offset offset = const Offset(0.0, 5.0);
|
Offset offset = const Offset(0.0, 5.0);
|
||||||
TestGesture gesture = await tester.startGesture(location, pointer: 5);
|
TestGesture gesture = await tester.startGesture(location, pointer: 5);
|
||||||
await gesture.moveBy(offset);
|
await gesture.moveBy(offset);
|
||||||
await tester.pumpWidget(widgetBuilder());
|
await tester.pumpWidget(buildTest());
|
||||||
await gesture.moveBy(offset);
|
await gesture.moveBy(offset);
|
||||||
await tester.pumpWidget(widgetBuilder());
|
await tester.pumpWidget(buildTest());
|
||||||
await gesture.moveBy(offset);
|
await gesture.moveBy(offset);
|
||||||
await tester.pumpWidget(widgetBuilder());
|
await tester.pumpWidget(buildTest());
|
||||||
await gesture.moveBy(offset);
|
await gesture.moveBy(offset);
|
||||||
await tester.pumpWidget(widgetBuilder());
|
await tester.pumpWidget(buildTest());
|
||||||
await gesture.up();
|
await gesture.up();
|
||||||
});
|
});
|
||||||
|
|
||||||
// This one is for a case where dssmissing a widget above a previously
|
// This one is for a case where dismissing a widget above a previously
|
||||||
// dismissed widget threw an exception, which was documented at the
|
// dismissed widget threw an exception, which was documented at the
|
||||||
// now-obsolete URL https://github.com/flutter/engine/issues/1215 (the URL
|
// now-obsolete URL https://github.com/flutter/engine/issues/1215 (the URL
|
||||||
// died in the migration to the new repo). Don't copy this test; it doesn't
|
// died in the migration to the new repo). Don't copy this test; it doesn't
|
||||||
@ -294,7 +317,7 @@ void main() {
|
|||||||
dismissDirection = DismissDirection.horizontal;
|
dismissDirection = DismissDirection.horizontal;
|
||||||
background = new Text('background');
|
background = new Text('background');
|
||||||
|
|
||||||
await tester.pumpWidget(widgetBuilder());
|
await tester.pumpWidget(buildTest());
|
||||||
expect(dismissedItems, isEmpty);
|
expect(dismissedItems, isEmpty);
|
||||||
|
|
||||||
Finder itemFinder = find.text('0');
|
Finder itemFinder = find.text('0');
|
||||||
|
Loading…
x
Reference in New Issue
Block a user