Skip to content

Commit

Permalink
Merge pull request #8 from letsar/feature/4_close_on_tap
Browse files Browse the repository at this point in the history
Close on slide action tap and on scroll
  • Loading branch information
letsar committed Jul 23, 2018
2 parents 041bb88 + 517ecf3 commit 071a798
Show file tree
Hide file tree
Showing 6 changed files with 222 additions and 75 deletions.
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
## 0.3.0
## Added
* The `closeOnTap` argument on slide actions to close when a action has been tapped.
* The `closeOnScroll` argument on `Slidable` to close when the nearest `Scrollable` starts to scroll.
* The static `Slidable.of` function.

## Changed
* The `dragExtent` field in `SlidableDelegateContext` has been changed to `dragSign`.

## 0.2.0
### Added
* `Slidable.builder` constructor.
Expand Down
15 changes: 13 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ A Flutter implementation of slidable list item with left and right slide actions
* 2 built-in slide action widget.
* You can easily create you custom layouts and animations.
* You can use a builder to create your slide actions if you want special effects during animation.
* Close when a slide action has been tapped (overridable).
* Close when the nearest `Scrollable` starts to scroll (overridable).

## Getting started

Expand All @@ -22,7 +24,7 @@ In the `pubspec.yaml` of your flutter project, add the following dependency:
```yaml
dependencies:
...
flutter_slidable: "^0.2.0"
flutter_slidable: "^0.3.0"
```

In your library add the following import:
Expand Down Expand Up @@ -52,7 +54,6 @@ A `direction` parameter let you choose if you want actions to appear when you sl

```dart
new Slidable(
key: Key('$3'),
delegate: new SlidableDrawerDelegate(),
actionExtentRatio: 0.25,
child: new Container(
Expand Down Expand Up @@ -133,6 +134,16 @@ The slide actions stretch while the item is sliding:

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

#### How to prevent my slide action to close after it has been tapped?

By default, `SlideAction` and `IconSlideAction` close on tap.
To prevent this, you can set `false` to the `closeOnTap` constructor argument.

#### How to prevent my Slidable to close after my list scrolled?

By default, a `Slidable` closes when the nearest `Scrollable` widget starts to scroll.
To prevent this, you can set `false` to the `closeOnScroll` constructor argument.

## Changelog

Please see the [Changelog](https://github.com/letsar/flutter_slidable/blob/master/CHANGELOG.md) page to know what's recently changed.
Expand Down
6 changes: 3 additions & 3 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class _MyHomePageState extends State<MyHomePage> {
title: new Text(widget.title),
),
body: new Center(
child: _buildList(context, Axis.horizontal),
child: _buildList(context, Axis.vertical),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
Expand Down Expand Up @@ -99,7 +99,6 @@ class _MyHomePageState extends State<MyHomePage> {
Widget _getSlidableWithLists(
BuildContext context, int index, Axis direction) {
return new Slidable(
key: Key('$index'),
direction: direction,
delegate: _getDelegate(index),
actionExtentRatio: 0.25,
Expand All @@ -126,6 +125,7 @@ class _MyHomePageState extends State<MyHomePage> {
color: Colors.grey.shade200,
icon: Icons.more_horiz,
onTap: () => _showSnackBar(context, 'More'),
closeOnTap: false,
),
new IconSlideAction(
caption: 'Delete',
Expand All @@ -140,7 +140,6 @@ class _MyHomePageState extends State<MyHomePage> {
Widget _getSlidableWithDelegates(
BuildContext context, int index, Axis direction) {
return new Slidable.builder(
key: Key('$index'),
direction: direction,
delegate: _getDelegate(index),
actionExtentRatio: 0.25,
Expand Down Expand Up @@ -175,6 +174,7 @@ class _MyHomePageState extends State<MyHomePage> {
color: Colors.grey.shade200.withOpacity(animation.value),
icon: Icons.more_horiz,
onTap: () => _showSnackBar(context, 'More'),
closeOnTap: false,
);
} else {
return new IconSlideAction(
Expand Down
134 changes: 99 additions & 35 deletions lib/src/widgets/slidable.dart
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ class SlidableDelegateContext {
const SlidableDelegateContext(
this.slidable,
this.showActions,
this.dragExtent,
this.dragSign,
this.controller,
);

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

final double dragExtent;
final double dragSign;

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

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

return new Positioned.fill(
Expand Down Expand Up @@ -395,12 +395,12 @@ class SlidableDrawerDelegate extends SlidableStackDelegate {
///
/// By sliding in one of these direction, slide actions will appear.
class Slidable extends StatefulWidget {
/// Creates a widget that can be dismissed.
/// Creates a widget that can be slid.
///
/// The [actions] contains the slide actions that appears when the child has been dragged down or to the right.
/// The [secondaryActions] contains the slide actions that appears when the child has been dragged up or to the left.
///
/// The [delegate] argument must not be null. The [actionExtentRatio]
/// The [delegate] and [closeOnScroll] arguments must not be null. The [actionExtentRatio]
/// and [showAllActionsThreshold] arguments must be greater or equal than 0 and less or equal than 1.
Slidable({
Key key,
Expand All @@ -412,6 +412,7 @@ class Slidable extends StatefulWidget {
double actionExtentRatio = _kActionsExtentRatio,
Duration movementDuration = const Duration(milliseconds: 200),
Axis direction = Axis.horizontal,
bool closeOnScroll = true,
}) : this.builder(
key: key,
child: child,
Expand All @@ -423,8 +424,16 @@ class Slidable extends StatefulWidget {
actionExtentRatio: actionExtentRatio,
movementDuration: movementDuration,
direction: direction,
closeOnScroll: closeOnScroll,
);

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

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

/// Specifies to close this slidable after the closest [Scrollable]'s position changed.
///
/// Defaults to true.
final bool closeOnScroll;

/// The state from the closest instance of this class that encloses the given context.
static SlidableState of(BuildContext context) {
return context.ancestorStateOfType(const TypeMatcher<SlidableState>());
}

@override
SlidableState createState() => SlidableState();
}
Expand All @@ -493,25 +514,21 @@ class SlidableState extends State<Slidable>
..addStatusListener(_handleShowAllActionsStatusChanged);
}

void _handleShowAllActionsStatusChanged(AnimationStatus status) {
if (status == AnimationStatus.completed && !_dragUnderway && !_opening) {
_dragExtent = .0;
}

if (status == AnimationStatus.completed) {
setState(() {});
}
}

AnimationController _controller;

double _dragExtent = 0.0;
bool _dragUnderway = false;
bool _opening = false;

ScrollPosition _scrollPosition;

bool get _showActions {
return _dragExtent > 0;
}

@override
bool get wantKeepAlive =>
_controller != null &&
(_controller.isAnimating || _controller.isCompleted);

/// The current actions that have to be shown.
SlideActionDelegate get actionDelegate =>
_showActions ? widget.actionDelegate : widget.secondaryActionDelegate;
Expand All @@ -526,14 +543,62 @@ class SlidableState extends State<Slidable>
(actionDelegate?.actionCount ?? 0);
}

@override
void didChangeDependencies() {
super.didChangeDependencies();
_removeScrollingNotifierListener();
_addScrollingNotifierListener();
}

@override
void didUpdateWidget(Slidable oldWidget) {
super.didUpdateWidget(oldWidget);

if (widget.closeOnScroll != oldWidget.closeOnScroll) {
_removeScrollingNotifierListener();
_addScrollingNotifierListener();
}
}

void _addScrollingNotifierListener() {
if (widget.closeOnScroll) {
_scrollPosition = Scrollable.of(context)?.position;
if (_scrollPosition != null)
_scrollPosition.isScrollingNotifier.addListener(_isScrollingListener);
}
}

void _removeScrollingNotifierListener() {
if (_scrollPosition != null) {
_scrollPosition.isScrollingNotifier.removeListener(_isScrollingListener);
}
}

@override
void dispose() {
_controller.dispose();
_removeScrollingNotifierListener();
super.dispose();
}

void open() {
_controller.fling(velocity: 1.0);
}

void close() {
_controller.fling(velocity: -1.0);
}

void _isScrollingListener() {
if (!widget.closeOnScroll || _scrollPosition == null) return;

// When a scroll starts close this.
if (_scrollPosition.isScrollingNotifier.value) {
close();
}
}

void _handleDragStart(DragStartDetails details) {
_dragUnderway = true;
_dragExtent = _controller.value * _overallDragAxisExtent * _dragExtent.sign;
if (_controller.isAnimating) {
_controller.stop();
Expand All @@ -549,21 +614,23 @@ class SlidableState extends State<Slidable>
}

void _handleDragEnd(DragEndDetails details) {
_dragUnderway = false;
final double velocity = details.primaryVelocity;
final bool open = velocity.sign == _dragExtent.sign;
final bool shouldOpen = velocity.sign == _dragExtent.sign;
final bool fast = velocity.abs() > widget.delegate.fastThreshold;
if (!open && fast) {
_opening = false;
_controller.animateTo(0.0);
} else if (_controller.value >= widget.showAllActionsThreshold ||
(open && fast)) {
_opening = true;
_controller.animateTo(1.0);
if (_controller.value >= widget.showAllActionsThreshold ||
(shouldOpen && fast)) {
open();
} else {
_opening = false;
_controller.animateTo(0.0);
close();
}
}

void _handleShowAllActionsStatusChanged(AnimationStatus status) {
if (status == AnimationStatus.completed) {
setState(() {});
}

updateKeepAlive();
}

@override
Expand All @@ -590,7 +657,7 @@ class SlidableState extends State<Slidable>
new SlidableDelegateContext(
widget,
_showActions,
_dragExtent,
_dragExtent.sign,
_controller,
),
);
Expand All @@ -607,7 +674,4 @@ class SlidableState extends State<Slidable>
child: content,
);
}

@override
bool get wantKeepAlive => _opening;
}

0 comments on commit 071a798

Please sign in to comment.