flutter_slidable 0.4.9

  • README.md
  • CHANGELOG.md
  • Example
  • Installing
  • Versions
  • 98

flutter_slidable

A Flutter implementation of slidable list item with directional slide actions that can be dismissed.

Pub Donate

Overview

Features

  • Accepts primary (left/top) and secondary (right/bottom) widget lists as slide actions.
  • Can be dismissed.
  • 4 built-in layouts.
  • 2 built-in slide action widgets.
  • 1 built-in dismiss animation.
  • 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).
  • Option to disable the slide effect easily.

Getting started

In the pubspec.yaml of your flutter project, add the following dependency:

dependencies:
  ...
  flutter_slidable: "^0.4.9"

In your library add the following import:

import 'package:flutter_slidable/flutter_slidable.dart';

For help getting started with Flutter, view the online documentation.

Constructors

You can create a Slidable in two different ways:

  • By calling the Slidable constructor and passing a list of slide actions.
  • By calling the Slidable.builder constructor and passing slide action builders, if you want special effects during the animation.

A Slidable needs multiple things:

  • Slide actions (see below for details). They can be any widget. For convenience, this package has 2 built-in side action widgets.
  • A delegate. This is what controls the layout and the animation of the slide menu.
  • An extent ratio between a slide action extent and the item extent.
  • A child.

The actions contains the slide actions that appear when the child has been dragged down or to the right. The secondaryActions contains the slide actions that appear when the child has been dragged up or to the left.

A direction parameter lets you choose if you want actions to appear when you slide horizontally (default) or vertically.

new Slidable(
  delegate: new SlidableDrawerDelegate(),
  actionExtentRatio: 0.25,
  child: new Container(
    color: Colors.white,
    child: new ListTile(
      leading: new CircleAvatar(
        backgroundColor: Colors.indigoAccent,
        child: new Text('$3'),
        foregroundColor: Colors.white,
      ),
      title: new Text('Tile n°$3'),
      subtitle: new Text('SlidableDrawerDelegate'),
    ),
  ),
  actions: <Widget>[
    new IconSlideAction(
      caption: 'Archive',
      color: Colors.blue,
      icon: Icons.archive,
      onTap: () => _showSnackBar('Archive'),
    ),
    new IconSlideAction(
      caption: 'Share',
      color: Colors.indigo,
      icon: Icons.share,
      onTap: () => _showSnackBar('Share'),
    ),
  ],
  secondaryActions: <Widget>[
    new IconSlideAction(
      caption: 'More',
      color: Colors.black45,
      icon: Icons.more_horiz,
      onTap: () => _showSnackBar('More'),
    ),
    new IconSlideAction(
      caption: 'Delete',
      color: Colors.red,
      icon: Icons.delete,
      onTap: () => _showSnackBar('Delete'),
    ),
  ],
);

Built-in slide actions

This package comes with 2 kinds of slide actions:

  • SlideAction, which is the most flexible. You can choose a background color, or any decoration, and it takes any widget as a child.
  • IconSlideAction, which requires an icon. It can have a background color and a caption below the icon.

Built-in delegates

This package comes with 4 kinds of delegates:

SlidableBehindDelegate

The slide actions stay behind the item while it's sliding:

Overview

SlidableScrollDelegate

The slide actions follow the item while it's sliding:

Overview

SlidableDrawerDelegate

The slide actions animate like drawers while the item is sliding:

Overview

SlidableStrechDelegate

The slide actions stretch while the item is sliding:

Overview

FAQ

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 pass in false to the closeOnTap constructor parameter.

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

By default, a Slidable closes when the nearest Scrollable widget starts to scroll. To prevent this, you can pass in false to the closeOnScroll constructor parameter.

How can I dismiss my Slidable?

In order to make your Slidable dismissible, you have to set the slideToDismissDelegate parameter of the Slidable constructor. You can set any class that inherits SlideToDismissDelegate. For now there is only one built-in: SlideToDismissDrawerDelegate.

The actionType passed to the onDismissed callback let you know which action has been dismissed.

When a Slidable is dismissible, the key parameter must not be null.

Example:

slideToDismissDelegate: new SlideToDismissDrawerDelegate(
  onDismissed: (actionType) {
    _showSnackBar(
        context,
        actionType == SlideActionType.primary
            ? 'Dismiss Archive'
            : 'Dimiss Delete');
    setState(() {
      items.removeAt(index);
    });
  },
),

How can I prevent to dismiss one side but not the other?

If you only want one side to be dismissible, you can set the associated threshold to 1.0 or more. For example, if you don't want the first primary action to be dismissed, you will set the following thresholds on the slideToDismissDelegate:

dismissThresholds: <SlideActionType, double>{
  SlideActionType.primary: 1.0
},

How to let the user cancel a dismissal?

You can let the user confirm the dismissal by setting the onWillDismiss callback on the slideToDismissDelegate.

Example:

slideToDismissDelegate: new SlideToDismissDrawerDelegate(
  onWillDismiss: (actionType) {
          return showDialog<bool>(
            context: context,
            builder: (context) {
              return new AlertDialog(
                title: new Text('Delete'),
                content: new Text('Item will be deleted'),
                actions: <Widget>[
                  new FlatButton(
                    child: new Text('Cancel'),
                    onPressed: () => Navigator.of(context).pop(false),
                  ),
                  new FlatButton(
                    child: new Text('Ok'),
                    onPressed: () => Navigator.of(context).pop(true),
                  ),
                ],
              );
            },
          );
        },
        ...
        ),

How to let keep only one Slidable open?

You have to set the controller parameter of the Slidable constructors to a SlidableController instance:

final SlidableController slidableController = new SlidableController();
...
new Slidable(
      key: new Key(item.title),
      controller: slidableController,
      ...
      );

How can I animate an external widget at the same time as the active Slidable?

You have to set the callbacks of a SlidableController instance: The onSlideAnimationChanged let you get the animation of the current Slidable. The onSlideIsOpenChanged let you know when the current Slidable opens and closes.

final SlidableController slidableController = new SlidableController(
  onSlideAnimationChanged: handleSlideAnimationChanged,
  onSlideIsOpenChanged: handleSlideIsOpenChanged,
  );
...
  void handleSlideAnimationChanged(Animation<double> slideAnimation) {
    setState(() {
      _rotationAnimation = slideAnimation;
    });
  }

  void handleSlideIsOpenChanged(bool isOpen) {
    setState(() {
      _fabColor = isOpen ? Colors.green : Colors.blue;
    });
  }

Changelog

Please see the Changelog page to know what's recently changed.

Contributions

Feel free to contribute to this project.

If you find a bug or want a feature, but don't know how to fix/implement it, please fill an issue.
If you fixed a bug or implemented a new feature, please send a pull request.

0.4.9

Fixed

0.4.8

Addded

  • onSlideAnimationChanged and onSlideIsOpenChanged on SlidableController.

0.4.7

Fixed

0.4.6

Modified

0.4.5

Added

  • The foregroundColor parameter on IconSlideAction class.

0.4.4

Added

  • The closeOnCanceled parameter on SlideToDismissDelegate classes.

0.4.3

Fixed

0.4.2

Fixed

0.4.1

Added

  • The SlidableController class.
  • The controller parameter on Slidable constructors to enable keeping only one Slidable open.

0.4.0

Added

  • The SlidableRenderingMode enum.
  • The SlideActionType enum.
  • The SlideToDismissDelegate classes.

Modified

  • Added a renderingMode parameter in the SlideActionBuilder signature.

0.3.2

Added

  • The enabled parameter on Slidable constructors to enable or disable the slide effect (enabled by default).

0.3.1

Fixed

0.3.0

Added

  • The closeOnTap parameter on slide actions to close when a action has been tapped.
  • The closeOnScroll parameter 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.
  • Vertical sliding.

Changed

  • The slide actions are now hosted in a SlideActionDelegate instead of List<Widget> inside the Slidable widget.
  • The leftActions have been renamed to actions.
  • The rightActions have been renamed to secondaryActions.

0.1.0

  • Initial Open Source release.

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:flutter_slidable/flutter_slidable.dart';

void main() => runApp(new MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Flutter Slidable Demo',
      theme: new ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: new MyHomePage(title: 'Flutter Slidable Demo'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => new _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  SlidableController slidableController;
  final List<_HomeItem> items = List.generate(
    20,
    (i) => new _HomeItem(
          i,
          'Tile n°$i',
          _getSubtitle(i),
          _getAvatarColor(i),
        ),
  );

  @protected
  void initState() {
    slidableController = new SlidableController(
      onSlideAnimationChanged: handleSlideAnimationChanged,
      onSlideIsOpenChanged: handleSlideIsOpenChanged,
    );
    super.initState();
  }

  Animation<double> _rotationAnimation;
  Color _fabColor = Colors.blue;

  void handleSlideAnimationChanged(Animation<double> slideAnimation) {
    setState(() {
      _rotationAnimation = slideAnimation;
    });
  }

  void handleSlideIsOpenChanged(bool isOpen) {
    setState(() {
      _fabColor = isOpen ? Colors.green : Colors.blue;
    });
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text(widget.title),
      ),
      body: new Center(
        child: new OrientationBuilder(
          builder: (context, orientation) => _buildList(
              context,
              orientation == Orientation.portrait
                  ? Axis.vertical
                  : Axis.horizontal),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        backgroundColor: _fabColor,
        onPressed: null,
        child: _rotationAnimation == null
            ? Icon(Icons.add)
            : RotationTransition(
                turns: _rotationAnimation,
                child: Icon(Icons.add),
              ),
      ),
    );
  }

  Widget _buildList(BuildContext context, Axis direction) {
    return new ListView.builder(
      scrollDirection: direction,
      itemBuilder: (context, index) {
        final Axis slidableDirection =
            direction == Axis.horizontal ? Axis.vertical : Axis.horizontal;
        var item = items[index];
        if (item.index < 8) {
          return _getSlidableWithLists(context, index, slidableDirection);
        } else {
          return _getSlidableWithDelegates(context, index, slidableDirection);
        }
      },
      itemCount: items.length,
    );
  }

  Widget _buildVerticalListItem(BuildContext context, int index) {
    final _HomeItem item = items[index];
    return new Container(
      color: Colors.white,
      child: new ListTile(
        leading: new CircleAvatar(
          backgroundColor: item.color,
          child: new Text('${item.index}'),
          foregroundColor: Colors.white,
        ),
        title: new Text(item.title),
        subtitle: new Text(item.subtitle),
      ),
    );
  }

  Widget _buildhorizontalListItem(BuildContext context, int index) {
    final _HomeItem item = items[index];
    return new Container(
      color: Colors.white,
      width: 160.0,
      child: new Column(
        mainAxisSize: MainAxisSize.max,
        children: <Widget>[
          new Expanded(
            child: new CircleAvatar(
              backgroundColor: item.color,
              child: new Text('${item.index}'),
              foregroundColor: Colors.white,
            ),
          ),
          new Expanded(
            child: Center(
              child: new Text(
                item.subtitle,
              ),
            ),
          ),
        ],
      ),
    );
  }

  Widget _getSlidableWithLists(
      BuildContext context, int index, Axis direction) {
    final _HomeItem item = items[index];
    //final int t = index;
    return new Slidable(
      key: new Key(item.title),
      controller: slidableController,
      direction: direction,
      slideToDismissDelegate: new SlideToDismissDrawerDelegate(
        onDismissed: (actionType) {
          _showSnackBar(
              context,
              actionType == SlideActionType.primary
                  ? 'Dismiss Archive'
                  : 'Dimiss Delete');
          setState(() {
            items.removeAt(index);
          });
        },
      ),
      delegate: _getDelegate(item.index),
      actionExtentRatio: 0.25,
      child: direction == Axis.horizontal
          ? _buildVerticalListItem(context, index)
          : _buildhorizontalListItem(context, index),
      actions: <Widget>[
        new IconSlideAction(
          caption: 'Archive',
          color: Colors.blue,
          icon: Icons.archive,
          onTap: () => _showSnackBar(context, 'Archive'),
        ),
        new IconSlideAction(
          caption: 'Share',
          color: Colors.indigo,
          icon: Icons.share,
          onTap: () => _showSnackBar(context, 'Share'),
        ),
      ],
      secondaryActions: <Widget>[
        new IconSlideAction(
          caption: 'More',
          color: Colors.grey.shade200,
          icon: Icons.more_horiz,
          onTap: () => _showSnackBar(context, 'More'),
          closeOnTap: false,
        ),
        new IconSlideAction(
          caption: 'Delete',
          color: Colors.red,
          icon: Icons.delete,
          onTap: () => _showSnackBar(context, 'Delete'),
        ),
      ],
    );
  }

  Widget _getSlidableWithDelegates(
      BuildContext context, int index, Axis direction) {
    final _HomeItem item = items[index];

    return new Slidable.builder(
      key: new Key(item.title),
      controller: slidableController,
      direction: direction,
      slideToDismissDelegate: new SlideToDismissDrawerDelegate(
        closeOnCanceled: true,
        onWillDismiss: (item.index != 10)
            ? null
            : (actionType) {
                return showDialog<bool>(
                  context: context,
                  builder: (context) {
                    return new AlertDialog(
                      title: new Text('Delete'),
                      content: new Text('Item will be deleted'),
                      actions: <Widget>[
                        new FlatButton(
                          child: new Text('Cancel'),
                          onPressed: () => Navigator.of(context).pop(false),
                        ),
                        new FlatButton(
                          child: new Text('Ok'),
                          onPressed: () => Navigator.of(context).pop(true),
                        ),
                      ],
                    );
                  },
                );
              },
        onDismissed: (actionType) {
          _showSnackBar(
              context,
              actionType == SlideActionType.primary
                  ? 'Dismiss Archive'
                  : 'Dimiss Delete');
          setState(() {
            items.removeAt(index);
          });
        },
      ),
      delegate: _getDelegate(item.index),
      actionExtentRatio: 0.25,
      child: direction == Axis.horizontal
          ? _buildVerticalListItem(context, index)
          : _buildhorizontalListItem(context, index),
      actionDelegate: new SlideActionBuilderDelegate(
          actionCount: 2,
          builder: (context, index, animation, renderingMode) {
            if (index == 0) {
              return new IconSlideAction(
                caption: 'Archive',
                color: renderingMode == SlidableRenderingMode.slide
                    ? Colors.blue.withOpacity(animation.value)
                    : (renderingMode == SlidableRenderingMode.dismiss
                        ? Colors.blue
                        : Colors.green),
                icon: Icons.archive,
                onTap: () async {
                  var state = Slidable.of(context);
                  var dismiss = await showDialog<bool>(
                    context: context,
                    builder: (context) {
                      return new AlertDialog(
                        title: new Text('Delete'),
                        content: new Text('Item will be deleted'),
                        actions: <Widget>[
                          new FlatButton(
                            child: new Text('Cancel'),
                            onPressed: () => Navigator.of(context).pop(false),
                          ),
                          new FlatButton(
                            child: new Text('Ok'),
                            onPressed: () => Navigator.of(context).pop(true),
                          ),
                        ],
                      );
                    },
                  );

                  if (dismiss) {
                    state.dismiss();
                  }
                },
              );
            } else {
              return new IconSlideAction(
                caption: 'Share',
                color: renderingMode == SlidableRenderingMode.slide
                    ? Colors.indigo.withOpacity(animation.value)
                    : Colors.indigo,
                icon: Icons.share,
                onTap: () => _showSnackBar(context, 'Share'),
              );
            }
          }),
      secondaryActionDelegate: new SlideActionBuilderDelegate(
          actionCount: 2,
          builder: (context, index, animation, renderingMode) {
            if (index == 0) {
              return new IconSlideAction(
                caption: 'More',
                color: renderingMode == SlidableRenderingMode.slide
                    ? Colors.grey.shade200.withOpacity(animation.value)
                    : Colors.grey.shade200,
                icon: Icons.more_horiz,
                onTap: () => _showSnackBar(context, 'More'),
                closeOnTap: false,
              );
            } else {
              return new IconSlideAction(
                caption: 'Delete',
                color: renderingMode == SlidableRenderingMode.slide
                    ? Colors.red.withOpacity(animation.value)
                    : Colors.red,
                icon: Icons.delete,
                onTap: () => _showSnackBar(context, 'Delete'),
              );
            }
          }),
    );
  }

  static SlidableDelegate _getDelegate(int index) {
    switch (index % 4) {
      case 0:
        return new SlidableBehindDelegate();
      case 1:
        return new SlidableStrechDelegate();
      case 2:
        return new SlidableScrollDelegate();
      case 3:
        return new SlidableDrawerDelegate();
      default:
        return null;
    }
  }

  static Color _getAvatarColor(int index) {
    switch (index % 4) {
      case 0:
        return Colors.red;
      case 1:
        return Colors.green;
      case 2:
        return Colors.blue;
      case 3:
        return Colors.indigoAccent;
      default:
        return null;
    }
  }

  static String _getSubtitle(int index) {
    switch (index % 4) {
      case 0:
        return 'SlidableBehindDelegate';
      case 1:
        return 'SlidableStrechDelegate';
      case 2:
        return 'SlidableScrollDelegate';
      case 3:
        return 'SlidableDrawerDelegate';
      default:
        return null;
    }
  }

  void _showSnackBar(BuildContext context, String text) {
    Scaffold.of(context).showSnackBar(SnackBar(content: new Text(text)));
  }
}

class _HomeItem {
  const _HomeItem(
    this.index,
    this.title,
    this.subtitle,
    this.color,
  );

  final int index;
  final String title;
  final String subtitle;
  final Color color;
}

Use this package as a library

1. Depend on it

Add this to your package's pubspec.yaml file:


dependencies:
  flutter_slidable: ^0.4.9

2. Install it

You can install packages from the command line:

with Flutter:


$ flutter packages get

Alternatively, your editor might support flutter packages get. Check the docs for your editor to learn more.

3. Import it

Now in your Dart code, you can use:


import 'package:flutter_slidable/flutter_slidable.dart';
  
Version Uploaded Documentation Archive
0.4.9 Oct 15, 2018 Go to the documentation of flutter_slidable 0.4.9 Download flutter_slidable 0.4.9 archive
0.4.8 Oct 9, 2018 Go to the documentation of flutter_slidable 0.4.8 Download flutter_slidable 0.4.8 archive
0.4.7 Sep 17, 2018 Go to the documentation of flutter_slidable 0.4.7 Download flutter_slidable 0.4.7 archive
0.4.6 Sep 8, 2018 Go to the documentation of flutter_slidable 0.4.6 Download flutter_slidable 0.4.6 archive
0.4.5 Sep 5, 2018 Go to the documentation of flutter_slidable 0.4.5 Download flutter_slidable 0.4.5 archive
0.4.4 Aug 31, 2018 Go to the documentation of flutter_slidable 0.4.4 Download flutter_slidable 0.4.4 archive
0.4.3 Aug 22, 2018 Go to the documentation of flutter_slidable 0.4.3 Download flutter_slidable 0.4.3 archive
0.4.2 Aug 22, 2018 Go to the documentation of flutter_slidable 0.4.2 Download flutter_slidable 0.4.2 archive
0.4.1 Aug 8, 2018 Go to the documentation of flutter_slidable 0.4.1 Download flutter_slidable 0.4.1 archive
0.4.0 Aug 2, 2018 Go to the documentation of flutter_slidable 0.4.0 Download flutter_slidable 0.4.0 archive

All 15 versions...

Popularity:
Describes how popular the package is relative to other packages. [more]
97
Health:
Code health derived from static analysis. [more]
100
Maintenance:
Reflects how tidy and up-to-date the package is. [more]
100
Overall:
Weighted score of the above. [more]
98
Learn more about scoring.

We analyzed this package on Dec 5, 2018, and provided a score, details, and suggestions below. Analysis was completed with status completed using:

  • Dart: 2.1.0
  • pana: 0.12.7
  • Flutter: 1.0.0

Platforms

Detected platforms: Flutter

References Flutter, and has no conflicting libraries.

Dependencies

Package Constraint Resolved Available
Direct dependencies
Dart SDK >=1.19.0 <3.0.0
flutter 0.0.0
Transitive dependencies
collection 1.14.11
meta 1.1.6
sky_engine 0.0.99
typed_data 1.1.6
vector_math 2.0.8
Dev dependencies
flutter_test