Skip to content

Commit 071a798

Browse files
authored
Merge pull request #8 from letsar/feature/4_close_on_tap
Close on slide action tap and on scroll
2 parents 041bb88 + 517ecf3 commit 071a798

File tree

6 files changed

+222
-75
lines changed

6 files changed

+222
-75
lines changed

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
## 0.3.0
2+
## Added
3+
* The `closeOnTap` argument on slide actions to close when a action has been tapped.
4+
* The `closeOnScroll` argument on `Slidable` to close when the nearest `Scrollable` starts to scroll.
5+
* The static `Slidable.of` function.
6+
7+
## Changed
8+
* The `dragExtent` field in `SlidableDelegateContext` has been changed to `dragSign`.
9+
110
## 0.2.0
211
### Added
312
* `Slidable.builder` constructor.

README.md

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ A Flutter implementation of slidable list item with left and right slide actions
1414
* 2 built-in slide action widget.
1515
* You can easily create you custom layouts and animations.
1616
* You can use a builder to create your slide actions if you want special effects during animation.
17+
* Close when a slide action has been tapped (overridable).
18+
* Close when the nearest `Scrollable` starts to scroll (overridable).
1719

1820
## Getting started
1921

@@ -22,7 +24,7 @@ In the `pubspec.yaml` of your flutter project, add the following dependency:
2224
```yaml
2325
dependencies:
2426
...
25-
flutter_slidable: "^0.2.0"
27+
flutter_slidable: "^0.3.0"
2628
```
2729
2830
In your library add the following import:
@@ -52,7 +54,6 @@ A `direction` parameter let you choose if you want actions to appear when you sl
5254

5355
```dart
5456
new Slidable(
55-
key: Key('$3'),
5657
delegate: new SlidableDrawerDelegate(),
5758
actionExtentRatio: 0.25,
5859
child: new Container(
@@ -133,6 +134,16 @@ The slide actions stretch while the item is sliding:
133134

134135
![Overview](https://raw.githubusercontent.com/letsar/flutter_slidable/master/doc/images/slidable_stretch.gif)
135136

137+
#### How to prevent my slide action to close after it has been tapped?
138+
139+
By default, `SlideAction` and `IconSlideAction` close on tap.
140+
To prevent this, you can set `false` to the `closeOnTap` constructor argument.
141+
142+
#### How to prevent my Slidable to close after my list scrolled?
143+
144+
By default, a `Slidable` closes when the nearest `Scrollable` widget starts to scroll.
145+
To prevent this, you can set `false` to the `closeOnScroll` constructor argument.
146+
136147
## Changelog
137148

138149
Please see the [Changelog](https://github.com/letsar/flutter_slidable/blob/master/CHANGELOG.md) page to know what's recently changed.

example/lib/main.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ class _MyHomePageState extends State<MyHomePage> {
3434
title: new Text(widget.title),
3535
),
3636
body: new Center(
37-
child: _buildList(context, Axis.horizontal),
37+
child: _buildList(context, Axis.vertical),
3838
), // This trailing comma makes auto-formatting nicer for build methods.
3939
);
4040
}
@@ -99,7 +99,6 @@ class _MyHomePageState extends State<MyHomePage> {
9999
Widget _getSlidableWithLists(
100100
BuildContext context, int index, Axis direction) {
101101
return new Slidable(
102-
key: Key('$index'),
103102
direction: direction,
104103
delegate: _getDelegate(index),
105104
actionExtentRatio: 0.25,
@@ -126,6 +125,7 @@ class _MyHomePageState extends State<MyHomePage> {
126125
color: Colors.grey.shade200,
127126
icon: Icons.more_horiz,
128127
onTap: () => _showSnackBar(context, 'More'),
128+
closeOnTap: false,
129129
),
130130
new IconSlideAction(
131131
caption: 'Delete',
@@ -140,7 +140,6 @@ class _MyHomePageState extends State<MyHomePage> {
140140
Widget _getSlidableWithDelegates(
141141
BuildContext context, int index, Axis direction) {
142142
return new Slidable.builder(
143-
key: Key('$index'),
144143
direction: direction,
145144
delegate: _getDelegate(index),
146145
actionExtentRatio: 0.25,
@@ -175,6 +174,7 @@ class _MyHomePageState extends State<MyHomePage> {
175174
color: Colors.grey.shade200.withOpacity(animation.value),
176175
icon: Icons.more_horiz,
177176
onTap: () => _showSnackBar(context, 'More'),
177+
closeOnTap: false,
178178
);
179179
} else {
180180
return new IconSlideAction(

lib/src/widgets/slidable.dart

Lines changed: 99 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ class SlidableDelegateContext {
107107
const SlidableDelegateContext(
108108
this.slidable,
109109
this.showActions,
110-
this.dragExtent,
110+
this.dragSign,
111111
this.controller,
112112
);
113113

@@ -126,7 +126,7 @@ class SlidableDelegateContext {
126126
/// Whether the actions have to be shown.
127127
final bool showActions;
128128

129-
final double dragExtent;
129+
final double dragSign;
130130

131131
/// The animation controller which value depends on `dragExtent`.
132132
final AnimationController controller;
@@ -199,10 +199,10 @@ abstract class SlidableStackDelegate extends SlidableDelegate {
199199
Widget buildActions(BuildContext context, SlidableDelegateContext ctx) {
200200
final animation = new Tween(
201201
begin: Offset.zero,
202-
end: ctx.createOffset(ctx.totalActionsExtent * ctx.dragExtent.sign),
202+
end: ctx.createOffset(ctx.totalActionsExtent * ctx.dragSign),
203203
).animate(ctx.controller);
204204

205-
if (ctx.controller.value != .0 && ctx.dragExtent != .0) {
205+
if (ctx.controller.value != .0) {
206206
return new Container(
207207
child: new Stack(
208208
children: <Widget>[
@@ -237,7 +237,7 @@ class SlidableStrechDelegate extends SlidableStackDelegate {
237237
Widget buildStackActions(BuildContext context, SlidableDelegateContext ctx) {
238238
final animation = new Tween(
239239
begin: Offset.zero,
240-
end: ctx.createOffset(ctx.totalActionsExtent * ctx.dragExtent.sign),
240+
end: ctx.createOffset(ctx.totalActionsExtent * ctx.dragSign),
241241
).animate(ctx.controller);
242242

243243
return new Positioned.fill(
@@ -395,12 +395,12 @@ class SlidableDrawerDelegate extends SlidableStackDelegate {
395395
///
396396
/// By sliding in one of these direction, slide actions will appear.
397397
class Slidable extends StatefulWidget {
398-
/// Creates a widget that can be dismissed.
398+
/// Creates a widget that can be slid.
399399
///
400400
/// The [actions] contains the slide actions that appears when the child has been dragged down or to the right.
401401
/// The [secondaryActions] contains the slide actions that appears when the child has been dragged up or to the left.
402402
///
403-
/// The [delegate] argument must not be null. The [actionExtentRatio]
403+
/// The [delegate] and [closeOnScroll] arguments must not be null. The [actionExtentRatio]
404404
/// and [showAllActionsThreshold] arguments must be greater or equal than 0 and less or equal than 1.
405405
Slidable({
406406
Key key,
@@ -412,6 +412,7 @@ class Slidable extends StatefulWidget {
412412
double actionExtentRatio = _kActionsExtentRatio,
413413
Duration movementDuration = const Duration(milliseconds: 200),
414414
Axis direction = Axis.horizontal,
415+
bool closeOnScroll = true,
415416
}) : this.builder(
416417
key: key,
417418
child: child,
@@ -423,8 +424,16 @@ class Slidable extends StatefulWidget {
423424
actionExtentRatio: actionExtentRatio,
424425
movementDuration: movementDuration,
425426
direction: direction,
427+
closeOnScroll: closeOnScroll,
426428
);
427429

430+
/// Creates a widget that can be slid.
431+
///
432+
/// The [actionDelegate] is a delegate that builds the slide actions that appears when the child has been dragged down or to the right.
433+
/// The [secondaryActionDelegate] is a delegate that builds the slide actions that appears when the child has been dragged up or to the left.
434+
///
435+
/// The [delegate] and [closeOnScroll] arguments must not be null. The [actionExtentRatio]
436+
/// and [showAllActionsThreshold] arguments must be greater or equal than 0 and less or equal than 1.
428437
Slidable.builder({
429438
Key key,
430439
@required this.child,
@@ -435,6 +444,7 @@ class Slidable extends StatefulWidget {
435444
this.actionExtentRatio = _kActionsExtentRatio,
436445
this.movementDuration = const Duration(milliseconds: 200),
437446
this.direction = Axis.horizontal,
447+
this.closeOnScroll = true,
438448
}) : assert(delegate != null),
439449
assert(direction != null),
440450
assert(
@@ -447,6 +457,7 @@ class Slidable extends StatefulWidget {
447457
actionExtentRatio >= .0 &&
448458
actionExtentRatio <= 1.0,
449459
'actionExtentRatio must be between 0.0 and 1.0'),
460+
assert(closeOnScroll != null),
450461
super(key: key);
451462

452463
/// The widget below this widget in the tree.
@@ -479,6 +490,16 @@ class Slidable extends StatefulWidget {
479490
/// Defines the duration for card to go to final position or to come back to original position if threshold not reached.
480491
final Duration movementDuration;
481492

493+
/// Specifies to close this slidable after the closest [Scrollable]'s position changed.
494+
///
495+
/// Defaults to true.
496+
final bool closeOnScroll;
497+
498+
/// The state from the closest instance of this class that encloses the given context.
499+
static SlidableState of(BuildContext context) {
500+
return context.ancestorStateOfType(const TypeMatcher<SlidableState>());
501+
}
502+
482503
@override
483504
SlidableState createState() => SlidableState();
484505
}
@@ -493,25 +514,21 @@ class SlidableState extends State<Slidable>
493514
..addStatusListener(_handleShowAllActionsStatusChanged);
494515
}
495516

496-
void _handleShowAllActionsStatusChanged(AnimationStatus status) {
497-
if (status == AnimationStatus.completed && !_dragUnderway && !_opening) {
498-
_dragExtent = .0;
499-
}
500-
501-
if (status == AnimationStatus.completed) {
502-
setState(() {});
503-
}
504-
}
505-
506517
AnimationController _controller;
518+
507519
double _dragExtent = 0.0;
508-
bool _dragUnderway = false;
509-
bool _opening = false;
520+
521+
ScrollPosition _scrollPosition;
510522

511523
bool get _showActions {
512524
return _dragExtent > 0;
513525
}
514526

527+
@override
528+
bool get wantKeepAlive =>
529+
_controller != null &&
530+
(_controller.isAnimating || _controller.isCompleted);
531+
515532
/// The current actions that have to be shown.
516533
SlideActionDelegate get actionDelegate =>
517534
_showActions ? widget.actionDelegate : widget.secondaryActionDelegate;
@@ -526,14 +543,62 @@ class SlidableState extends State<Slidable>
526543
(actionDelegate?.actionCount ?? 0);
527544
}
528545

546+
@override
547+
void didChangeDependencies() {
548+
super.didChangeDependencies();
549+
_removeScrollingNotifierListener();
550+
_addScrollingNotifierListener();
551+
}
552+
553+
@override
554+
void didUpdateWidget(Slidable oldWidget) {
555+
super.didUpdateWidget(oldWidget);
556+
557+
if (widget.closeOnScroll != oldWidget.closeOnScroll) {
558+
_removeScrollingNotifierListener();
559+
_addScrollingNotifierListener();
560+
}
561+
}
562+
563+
void _addScrollingNotifierListener() {
564+
if (widget.closeOnScroll) {
565+
_scrollPosition = Scrollable.of(context)?.position;
566+
if (_scrollPosition != null)
567+
_scrollPosition.isScrollingNotifier.addListener(_isScrollingListener);
568+
}
569+
}
570+
571+
void _removeScrollingNotifierListener() {
572+
if (_scrollPosition != null) {
573+
_scrollPosition.isScrollingNotifier.removeListener(_isScrollingListener);
574+
}
575+
}
576+
529577
@override
530578
void dispose() {
531579
_controller.dispose();
580+
_removeScrollingNotifierListener();
532581
super.dispose();
533582
}
534583

584+
void open() {
585+
_controller.fling(velocity: 1.0);
586+
}
587+
588+
void close() {
589+
_controller.fling(velocity: -1.0);
590+
}
591+
592+
void _isScrollingListener() {
593+
if (!widget.closeOnScroll || _scrollPosition == null) return;
594+
595+
// When a scroll starts close this.
596+
if (_scrollPosition.isScrollingNotifier.value) {
597+
close();
598+
}
599+
}
600+
535601
void _handleDragStart(DragStartDetails details) {
536-
_dragUnderway = true;
537602
_dragExtent = _controller.value * _overallDragAxisExtent * _dragExtent.sign;
538603
if (_controller.isAnimating) {
539604
_controller.stop();
@@ -549,21 +614,23 @@ class SlidableState extends State<Slidable>
549614
}
550615

551616
void _handleDragEnd(DragEndDetails details) {
552-
_dragUnderway = false;
553617
final double velocity = details.primaryVelocity;
554-
final bool open = velocity.sign == _dragExtent.sign;
618+
final bool shouldOpen = velocity.sign == _dragExtent.sign;
555619
final bool fast = velocity.abs() > widget.delegate.fastThreshold;
556-
if (!open && fast) {
557-
_opening = false;
558-
_controller.animateTo(0.0);
559-
} else if (_controller.value >= widget.showAllActionsThreshold ||
560-
(open && fast)) {
561-
_opening = true;
562-
_controller.animateTo(1.0);
620+
if (_controller.value >= widget.showAllActionsThreshold ||
621+
(shouldOpen && fast)) {
622+
open();
563623
} else {
564-
_opening = false;
565-
_controller.animateTo(0.0);
624+
close();
625+
}
626+
}
627+
628+
void _handleShowAllActionsStatusChanged(AnimationStatus status) {
629+
if (status == AnimationStatus.completed) {
630+
setState(() {});
566631
}
632+
633+
updateKeepAlive();
567634
}
568635

569636
@override
@@ -590,7 +657,7 @@ class SlidableState extends State<Slidable>
590657
new SlidableDelegateContext(
591658
widget,
592659
_showActions,
593-
_dragExtent,
660+
_dragExtent.sign,
594661
_controller,
595662
),
596663
);
@@ -607,7 +674,4 @@ class SlidableState extends State<Slidable>
607674
child: content,
608675
);
609676
}
610-
611-
@override
612-
bool get wantKeepAlive => _opening;
613677
}

0 commit comments

Comments
 (0)