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.onDismissed,
|
||||
this.direction: DismissDirection.horizontal,
|
||||
this.resizeDuration: const Duration(milliseconds: 300)
|
||||
this.resizeDuration: const Duration(milliseconds: 300),
|
||||
this.dismissThresholds: const <DismissDirection, double>{},
|
||||
}) : super(key: key) {
|
||||
assert(key != null);
|
||||
assert(secondaryBackground != null ? background != null : true);
|
||||
@ -113,6 +114,14 @@ class Dismissable extends StatefulWidget {
|
||||
/// immediately after the the widget is dismissed.
|
||||
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
|
||||
_DismissableState createState() => new _DismissableState();
|
||||
}
|
||||
@ -195,6 +204,10 @@ class _DismissableState extends State<Dismissable> with TickerProviderStateMixin
|
||||
return _dragExtent > 0 ? DismissDirection.down : DismissDirection.up;
|
||||
}
|
||||
|
||||
double get _dismissThreshold {
|
||||
return config.dismissThresholds[_dismissDirection] ?? _kDismissThreshold;
|
||||
}
|
||||
|
||||
bool get _isActive {
|
||||
return _dragUnderway || _moveController.isAnimating;
|
||||
}
|
||||
@ -262,6 +275,9 @@ class _DismissableState extends State<Dismissable> with TickerProviderStateMixin
|
||||
}
|
||||
|
||||
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 vy = velocity.pixelsPerSecond.dy;
|
||||
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;
|
||||
_dragExtent = flingVelocity.sign;
|
||||
_moveController.fling(velocity: flingVelocity.abs() * _kFlingVelocityScale);
|
||||
} else if (_moveController.value > _kDismissThreshold) {
|
||||
} else if (_moveController.value > _dismissThreshold) {
|
||||
_moveController.forward();
|
||||
} else {
|
||||
_moveController.reverse();
|
||||
|
@ -23,31 +23,38 @@ void handleOnDismissed(DismissDirection direction, int item) {
|
||||
dismissedItems.add(item);
|
||||
}
|
||||
|
||||
Widget buildDismissableItem(int item) {
|
||||
return new Dismissable(
|
||||
key: new ValueKey<int>(item),
|
||||
direction: dismissDirection,
|
||||
onDismissed: (DismissDirection direction) { handleOnDismissed(direction, item); },
|
||||
onResize: () { handleOnResize(item); },
|
||||
background: background,
|
||||
child: new Container(
|
||||
width: itemExtent,
|
||||
height: itemExtent,
|
||||
child: new Text(item.toString())
|
||||
)
|
||||
);
|
||||
}
|
||||
Widget buildTest({ double startToEndThreshold }) {
|
||||
Widget buildDismissableItem(int item) {
|
||||
return new Dismissable(
|
||||
key: new ValueKey<int>(item),
|
||||
direction: dismissDirection,
|
||||
onDismissed: (DismissDirection direction) {
|
||||
handleOnDismissed(direction, item);
|
||||
},
|
||||
onResize: () {
|
||||
handleOnResize(item);
|
||||
},
|
||||
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(
|
||||
padding: const EdgeInsets.all(10.0),
|
||||
child: new ScrollableList(
|
||||
scrollDirection: scrollDirection,
|
||||
itemExtent: itemExtent,
|
||||
children: <int>[0, 1, 2, 3, 4].where(
|
||||
(int i) => !dismissedItems.contains(i)
|
||||
).map(buildDismissableItem)
|
||||
)
|
||||
padding: const EdgeInsets.all(10.0),
|
||||
child: new ScrollableList(
|
||||
scrollDirection: scrollDirection,
|
||||
itemExtent: itemExtent,
|
||||
children: <int>[0, 1, 2, 3, 4]
|
||||
.where((int i) => !dismissedItems.contains(i))
|
||||
.map(buildDismissableItem)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@ -58,7 +65,7 @@ Future<Null> dismissElement(WidgetTester tester, Finder finder, { DismissDirecti
|
||||
|
||||
Point downLocation;
|
||||
Point upLocation;
|
||||
switch(gestureDirection) {
|
||||
switch (gestureDirection) {
|
||||
case DismissDirection.endToStart:
|
||||
// getTopRight() returns a point that's just beyond itemWidget's right
|
||||
// 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 tester.pumpWidget(widgetBuilder()); // start the slide
|
||||
await tester.pumpWidget(widgetBuilder(), const Duration(seconds: 1)); // finish the slide and start shrinking...
|
||||
await tester.pumpWidget(widgetBuilder()); // first frame of shrinking animation
|
||||
await tester.pumpWidget(widgetBuilder(), 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()); // start the slide
|
||||
await tester.pumpWidget(buildTest(), const Duration(seconds: 1)); // finish the slide and start shrinking...
|
||||
await tester.pumpWidget(buildTest()); // first frame of shrinking animation
|
||||
await tester.pumpWidget(buildTest(), const Duration(seconds: 1)); // finish the shrinking and call the callback...
|
||||
await tester.pumpWidget(buildTest()); // rebuild after the callback removes the entry
|
||||
}
|
||||
|
||||
class Test1215DismissableWidget extends StatelessWidget {
|
||||
@ -133,7 +140,7 @@ void main() {
|
||||
scrollDirection = Axis.vertical;
|
||||
dismissDirection = DismissDirection.horizontal;
|
||||
|
||||
await tester.pumpWidget(widgetBuilder());
|
||||
await tester.pumpWidget(buildTest());
|
||||
expect(dismissedItems, isEmpty);
|
||||
|
||||
await dismissItem(tester, 0, gestureDirection: DismissDirection.startToEnd);
|
||||
@ -151,7 +158,7 @@ void main() {
|
||||
scrollDirection = Axis.horizontal;
|
||||
dismissDirection = DismissDirection.vertical;
|
||||
|
||||
await tester.pumpWidget(widgetBuilder());
|
||||
await tester.pumpWidget(buildTest());
|
||||
expect(dismissedItems, isEmpty);
|
||||
|
||||
await dismissItem(tester, 0, gestureDirection: DismissDirection.up);
|
||||
@ -169,7 +176,7 @@ void main() {
|
||||
scrollDirection = Axis.vertical;
|
||||
dismissDirection = DismissDirection.endToStart;
|
||||
|
||||
await tester.pumpWidget(widgetBuilder());
|
||||
await tester.pumpWidget(buildTest());
|
||||
expect(dismissedItems, isEmpty);
|
||||
|
||||
await dismissItem(tester, 0, gestureDirection: DismissDirection.startToEnd);
|
||||
@ -187,7 +194,7 @@ void main() {
|
||||
scrollDirection = Axis.vertical;
|
||||
dismissDirection = DismissDirection.startToEnd;
|
||||
|
||||
await tester.pumpWidget(widgetBuilder());
|
||||
await tester.pumpWidget(buildTest());
|
||||
expect(dismissedItems, isEmpty);
|
||||
|
||||
await dismissItem(tester, 0, gestureDirection: DismissDirection.endToStart);
|
||||
@ -203,7 +210,7 @@ void main() {
|
||||
scrollDirection = Axis.horizontal;
|
||||
dismissDirection = DismissDirection.up;
|
||||
|
||||
await tester.pumpWidget(widgetBuilder());
|
||||
await tester.pumpWidget(buildTest());
|
||||
expect(dismissedItems, isEmpty);
|
||||
|
||||
await dismissItem(tester, 0, gestureDirection: DismissDirection.down);
|
||||
@ -219,7 +226,7 @@ void main() {
|
||||
scrollDirection = Axis.horizontal;
|
||||
dismissDirection = DismissDirection.down;
|
||||
|
||||
await tester.pumpWidget(widgetBuilder());
|
||||
await tester.pumpWidget(buildTest());
|
||||
expect(dismissedItems, isEmpty);
|
||||
|
||||
await dismissItem(tester, 0, gestureDirection: DismissDirection.up);
|
||||
@ -231,6 +238,22 @@ void main() {
|
||||
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
|
||||
// assert "'!_disqualifiedFromEverAppearingAgain' is not true". The old URL
|
||||
// was https://github.com/domokit/sky_engine/issues/1068 but that issue is 404
|
||||
@ -241,22 +264,22 @@ void main() {
|
||||
scrollDirection = Axis.horizontal;
|
||||
dismissDirection = DismissDirection.down;
|
||||
|
||||
await tester.pumpWidget(widgetBuilder());
|
||||
await tester.pumpWidget(buildTest());
|
||||
Point location = tester.getTopLeft(find.text('0'));
|
||||
Offset offset = const Offset(0.0, 5.0);
|
||||
TestGesture gesture = await tester.startGesture(location, pointer: 5);
|
||||
await gesture.moveBy(offset);
|
||||
await tester.pumpWidget(widgetBuilder());
|
||||
await tester.pumpWidget(buildTest());
|
||||
await gesture.moveBy(offset);
|
||||
await tester.pumpWidget(widgetBuilder());
|
||||
await tester.pumpWidget(buildTest());
|
||||
await gesture.moveBy(offset);
|
||||
await tester.pumpWidget(widgetBuilder());
|
||||
await tester.pumpWidget(buildTest());
|
||||
await gesture.moveBy(offset);
|
||||
await tester.pumpWidget(widgetBuilder());
|
||||
await tester.pumpWidget(buildTest());
|
||||
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
|
||||
// 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
|
||||
@ -294,7 +317,7 @@ void main() {
|
||||
dismissDirection = DismissDirection.horizontal;
|
||||
background = new Text('background');
|
||||
|
||||
await tester.pumpWidget(widgetBuilder());
|
||||
await tester.pumpWidget(buildTest());
|
||||
expect(dismissedItems, isEmpty);
|
||||
|
||||
Finder itemFinder = find.text('0');
|
||||
|
Loading…
x
Reference in New Issue
Block a user