diff --git a/CHANGELOG.md b/CHANGELOG.md index ecab682b..9f10e683 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.5.4 +### Added +* Ripple effect when tapping on the IconSlideAction (https://github.com/letsar/flutter_slidable/pull/89) +* Option to make the widget non-dismissible by dragging (https://github.com/letsar/flutter_slidable/pull/101) + ## 0.5.3 ### Fixed * Fix SlidableDrawerActionPane when different than 2 actions (https://github.com/letsar/flutter_slidable/pull/74). diff --git a/README.md b/README.md index f5c16124..2676e234 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ In the `pubspec.yaml` of your flutter project, add the following dependency: ```yaml dependencies: ... - flutter_slidable: "^0.5.3" + flutter_slidable: "^0.5.4" ``` In your library add the following import: diff --git a/example/lib/main.dart b/example/lib/main.dart index d4862a5a..88ed8198 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -31,11 +31,11 @@ class _MyHomePageState extends State { final List<_HomeItem> items = List.generate( 20, (i) => _HomeItem( - i, - 'Tile n°$i', - _getSubtitle(i), - _getAvatarColor(i), - ), + i, + 'Tile n°$i', + _getSubtitle(i), + _getAvatarColor(i), + ), ); @protected @@ -148,6 +148,11 @@ class _MyHomePageState extends State { ), ], secondaryActions: [ + Container( + height: 800, + color: Colors.green, + child: Text('a'), + ), IconSlideAction( caption: 'More', color: Colors.grey.shade200, diff --git a/lib/src/widgets/fractionnally_aligned_sized_box.dart b/lib/src/widgets/fractionnally_aligned_sized_box.dart index 8bb01981..f3765a72 100644 --- a/lib/src/widgets/fractionnally_aligned_sized_box.dart +++ b/lib/src/widgets/fractionnally_aligned_sized_box.dart @@ -1,6 +1,16 @@ import 'package:flutter/widgets.dart'; +/// A widget that positions its child to a fraction of the total available space. class FractionallyAlignedSizedBox extends StatelessWidget { + /// Creates a widget that positions its child to a fraction of the total available space. + /// + /// Only two out of the three horizontal values ([leftFactor], [rightFactor], + /// [widthFactor]), and only two out of the three vertical values ([topFactor], + /// [bottomFactor], [heightFactor]), can be set. In each case, at least one of + /// the three must be null. + /// + /// If non-null, the [widthFactor] and [heightFactor] arguments must be + /// non-negative. FractionallyAlignedSizedBox({ Key key, @required this.child, @@ -18,12 +28,25 @@ class FractionallyAlignedSizedBox extends StatelessWidget { assert(heightFactor == null || heightFactor >= 0.0), super(key: key); + /// The relative distance that the child's left edge is inset from the left of the parent. final double leftFactor; + + /// The relative distance that the child's top edge is inset from the top of the parent. final double topFactor; + + /// The relative distance that the child's right edge is inset from the right of the parent. final double rightFactor; + + /// The relative distance that the child's bottom edge is inset from the bottom of the parent. final double bottomFactor; + + /// The child's width relative to its parent's width. final double widthFactor; + + /// The child's height relative to its parent's height. final double heightFactor; + + /// The widget below this widget in the tree. final Widget child; @override diff --git a/lib/src/widgets/slidable.dart b/lib/src/widgets/slidable.dart index d4294ed6..77615ca5 100644 --- a/lib/src/widgets/slidable.dart +++ b/lib/src/widgets/slidable.dart @@ -134,6 +134,7 @@ class SlideActionListDelegate extends SlideActionDelegate { /// The slide actions. final List actions; + /// The number of actions. @override int get actionCount => actions?.length ?? 0; @@ -156,6 +157,7 @@ class _SlidableScope extends InheritedWidget { bool updateShouldNotify(_SlidableScope oldWidget) => oldWidget.state != state; } +/// The data used by a [Slidable]. class SlidableData extends InheritedWidget { SlidableData({ Key key, @@ -174,17 +176,35 @@ class SlidableData extends InheritedWidget { @required Widget child, }) : super(key: key, child: child); + /// The type of slide action that is currently been showed by the [Slidable]. final SlideActionType actionType; + + /// The rendering mode in which the [Slidable] is. final SlidableRenderingMode renderingMode; + + /// The total extent of all the actions final double totalActionsExtent; + + /// The offset threshold the item has to be dragged in order to be considered + /// dismissed. final double dismissThreshold; + + /// Indicates whether the [Slidable] can be dismissed. final bool dismissible; /// The current actions that have to be shown. final SlideActionDelegate actionDelegate; + + /// Animation for the whole movement. final Animation overallMoveAnimation; + + /// Animation for the actions. final Animation actionsMoveAnimation; + + /// Dismiss animation. final Animation dismissAnimation; + + /// The slidable. final Slidable slidable; /// Relative ratio between one slide action and the extent of the child. @@ -193,16 +213,31 @@ class SlidableData extends InheritedWidget { /// The direction in which this widget can be slid. final Axis direction; + /// Indicates whether the primary actions are currently shown. bool get showActions => actionType == SlideActionType.primary; + + /// The number of actions. int get actionCount => actionDelegate?.actionCount ?? 0; + + /// If the [actionType] is [SlideActionType.primary] returns 1, -1 otherwise. double get actionSign => actionType == SlideActionType.primary ? 1.0 : -1.0; + + /// Indicates wheter the direction is horizontal. bool get directionIsXAxis => direction == Axis.horizontal; + + /// The alignment of the actions. Alignment get alignment => Alignment( directionIsXAxis ? -actionSign : 0.0, directionIsXAxis ? 0.0 : -actionSign, ); + + /// If the [direction] is horizontal, returns the [totalActionsExtent] + /// otherwise null. double get actionPaneWidthFactor => directionIsXAxis ? totalActionsExtent : null; + + /// If the [direction] is vertical, returns the [totalActionsExtent] + /// otherwise null. double get actionPaneHeightFactor => directionIsXAxis ? null : totalActionsExtent; @@ -238,6 +273,7 @@ class SlidableData extends InheritedWidget { ); } + /// Creates a [FractionallyAlignedSizedBox] related to the current direction and showed actions. FractionallyAlignedSizedBox createFractionallyAlignedSizedBox({ Widget child, double extentFactor, @@ -262,14 +298,16 @@ class SlidableData extends InheritedWidget { return List.generate( actionCount, (int index) => actionDelegate.build( - context, - index, - actionsMoveAnimation, - SlidableRenderingMode.slide, - ), + context, + index, + actionsMoveAnimation, + SlidableRenderingMode.slide, + ), ); } + /// Whether the framework should notify widgets that inherit from this widget. + @override bool updateShouldNotify(SlidableData oldWidget) => (oldWidget.actionType != actionType) || (oldWidget.renderingMode != renderingMode) || @@ -288,19 +326,29 @@ class SlidableData extends InheritedWidget { /// A controller that keep tracks of the active [SlidableState] and close /// the previous one. class SlidableController { + /// Creates a controller that keep tracks of the active [SlidableState] and close + /// the previous one. SlidableController({ this.onSlideAnimationChanged, this.onSlideIsOpenChanged, }); + /// Function called when the animation changed. final ValueChanged> onSlideAnimationChanged; + + /// Function called when the [Slidable] open status changed. final ValueChanged onSlideIsOpenChanged; + bool _isSlideOpen; Animation _slideAnimation; SlidableState _activeState; + + /// The state of the active [Slidable]. SlidableState get activeState => _activeState; + + /// Changes the state of the active [Slidable]. set activeState(SlidableState value) { _activeState?._flingAnimationController(); @@ -559,8 +607,11 @@ class SlidableState extends State double get _totalActionsExtent => widget.actionExtentRatio * (_actionCount); double get _dismissThreshold { - if (widget.dismissal == null) return _kDismissThreshold; - else return widget.dismissal.dismissThresholds[actionType] ?? _kDismissThreshold; + if (widget.dismissal == null) + return _kDismissThreshold; + else + return widget.dismissal.dismissThresholds[actionType] ?? + _kDismissThreshold; } bool get _dismissible => widget.dismissal != null && _dismissThreshold < 1.0; @@ -716,8 +767,9 @@ class SlidableState extends State if (_dismissible && !widget.dismissal.dragDismissible) { // If the widget is not dismissible by dragging, clamp drag result // so the widget doesn't slide past [_totalActionsExtent]. - _overallMoveController.value = (_dragExtent.abs() / _overallDragAxisExtent) - .clamp(0.0, _totalActionsExtent); + _overallMoveController.value = + (_dragExtent.abs() / _overallDragAxisExtent) + .clamp(0.0, _totalActionsExtent); } else { _overallMoveController.value = _dragExtent.abs() / _overallDragAxisExtent; diff --git a/lib/src/widgets/slidable_action_pane.dart b/lib/src/widgets/slidable_action_pane.dart index 6fab4738..b7900a5a 100644 --- a/lib/src/widgets/slidable_action_pane.dart +++ b/lib/src/widgets/slidable_action_pane.dart @@ -102,6 +102,7 @@ class SlidableBehindActionPane extends StatelessWidget { /// An action pane that creates actions which follow the item while it's sliding. class SlidableScrollActionPane extends StatelessWidget { + /// Creates an action pane that creates actions which follow the item while it's sliding. const SlidableScrollActionPane({Key key}) : super(key: key); @override @@ -139,6 +140,7 @@ class SlidableScrollActionPane extends StatelessWidget { /// An action pane that creates actions which animate like drawers while the item is sliding. class SlidableDrawerActionPane extends StatelessWidget { + /// Creates an action pane that creates actions which animate like drawers while the item is sliding. const SlidableDrawerActionPane({Key key}) : super(key: key); @override diff --git a/lib/src/widgets/slidable_dismissal.dart b/lib/src/widgets/slidable_dismissal.dart index 8ab58267..a05862b3 100644 --- a/lib/src/widgets/slidable_dismissal.dart +++ b/lib/src/widgets/slidable_dismissal.dart @@ -1,10 +1,9 @@ import 'package:flutter/widgets.dart'; -import 'package:flutter_slidable/src/widgets/fractionnally_aligned_sized_box.dart'; import 'package:flutter_slidable/src/widgets/slidable.dart'; const Duration _kResizeDuration = const Duration(milliseconds: 300); -/// A wiget that controls how the [Slidable] is dismissed. +/// A widget that controls how the [Slidable] is dismissed. /// /// The [Slidable] widget calls the [onDismissed] callback either after its size has /// collapsed to zero (if [resizeDuration] is non-null) or immediately after @@ -17,6 +16,7 @@ const Duration _kResizeDuration = const Duration(milliseconds: 300); /// * [SlidableDrawerDismissal], which creates slide actions that are displayed like drawers /// while the item is dismissing. class SlidableDismissal extends StatelessWidget { + /// Creates a widget that controls how the [Slidable] is dismissed. const SlidableDismissal({ @required this.child, this.dismissThresholds = const {}, @@ -84,6 +84,7 @@ class SlidableDismissal extends StatelessWidget { /// The widget to show when the [Slidable] enters dismiss mode. final Widget child; + @override Widget build(BuildContext context) { final SlidableData data = SlidableData.of(context); @@ -105,8 +106,11 @@ class SlidableDismissal extends StatelessWidget { /// while the item is dismissing. /// The further slide action will grow faster than the other ones. class SlidableDrawerDismissal extends StatelessWidget { + /// Creates a specific dismissal that creates slide actions that are displayed like drawers + /// while the item is dismissing. const SlidableDrawerDismissal({Key key}) : super(key: key); + @override Widget build(BuildContext context) { final SlidableData data = SlidableData.of(context); @@ -151,61 +155,7 @@ class SlidableDrawerDismissal extends StatelessWidget { }), ), ); - // return Stack( - // children: List.generate(data.actionCount, (index) { - // // For the main actions we have to reverse the order if we want the last item at the bottom of the stack. - // int displayIndex = - // data.showActions ? data.actionCount - index - 1 : index; - - // return data.createFractionallyAlignedSizedBox( - // positionFactor: - // data.actionExtentRatio * (data.actionCount - index - 1), - // extentFactor: extentAnimations[index].value, - // child: data.actionDelegate.build(context, displayIndex, - // data.actionsMoveAnimation, data.renderingMode), - // ); - // }), - // ); }), - // Positioned.fill( - // child: LayoutBuilder(builder: (context, constraints) { - // final count = data.actionCount; - // final double totalExtent = data.getMaxExtent(constraints); - // final double actionExtent = totalExtent * data.actionExtentRatio; - - // final extentAnimations = Iterable.generate(count).map((index) { - // return Tween( - // begin: actionExtent, - // end: totalExtent - - // (actionExtent * (data.actionCount - index - 1)), - // ).animate( - // CurvedAnimation( - // parent: data.overallMoveAnimation, - // curve: Interval(data.totalActionsExtent, 1.0), - // ), - // ); - // }).toList(); - - // return AnimatedBuilder( - // animation: data.overallMoveAnimation, - // builder: (context, child) { - // return Stack( - // children: List.generate(data.actionCount, (index) { - // // For the main actions we have to reverse the order if we want the last item at the bottom of the stack. - // int displayIndex = data.showActions - // ? data.actionCount - index - 1 - // : index; - // return data.createPositioned( - // position: actionExtent * (data.actionCount - index - 1), - // extent: extentAnimations[index].value, - // child: data.actionDelegate.build(context, displayIndex, - // data.actionsMoveAnimation, data.renderingMode), - // ); - // }), - // ); - // }); - // }), - // ), SlideTransition( position: animation, child: data.slidable.child, diff --git a/lib/src/widgets/slide_action.dart b/lib/src/widgets/slide_action.dart index 06360f30..bacceb78 100644 --- a/lib/src/widgets/slide_action.dart +++ b/lib/src/widgets/slide_action.dart @@ -18,7 +18,9 @@ abstract class ClosableSlideAction extends StatelessWidget { }) : assert(closeOnTap != null), super(key: key); + /// The background color of this action. final Color color; + /// A tap has occurred. final VoidCallback onTap; @@ -34,6 +36,7 @@ abstract class ClosableSlideAction extends StatelessWidget { Slidable.of(context)?.close(); } + @override Widget build(BuildContext context) { return GestureDetector( child: Material( @@ -46,6 +49,8 @@ abstract class ClosableSlideAction extends StatelessWidget { ); } + /// Builds the action. + @protected Widget buildAction(BuildContext context); } @@ -123,7 +128,7 @@ class IconSlideAction extends ClosableSlideAction { 'Either set icon or iconWidget.'), super( key: key, - color: color, + color: color, onTap: onTap, closeOnTap: closeOnTap, ); @@ -141,7 +146,7 @@ class IconSlideAction extends ClosableSlideAction { /// The background color. /// - /// Defaults to true. + /// Defaults to [Colors.white]. final Color color; /// The color used for [icon] and [caption]. diff --git a/pubspec.yaml b/pubspec.yaml index 17131ccc..1b3a0ce0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_slidable description: A Flutter implementation of slidable list item with directional slide actions that can be dismissed. -version: 0.5.3 +version: 0.5.4 author: Romain Rastel homepage: https://github.com/letsar/flutter_slidable