Flutter - Slidable - GeeksforGeeks

15 Feb, 2021
Slidable in an application can be used to perform a wide range of tasks with just a simple swipe to either right or left on the tile. It not only makes the UI very user-friendly but also saves a lot of time in doing trivial tasks which if done in other ways can be hectic and redundant to design. In this article, we will look into the process of designing a slidable for your application.
Here we will build a simple application with tiles that when swiped left to right archives the tile and when swiped right to left deletes the tile. To do so follow the below steps:
Add the flutter_slidable dependency to the pubspec.yaml file.
Import the dependency to the main.dart file
Create a StatelessWidget to give the app a structure
Use a StateFulWidget to add a Homepage to the application
Use SlidableCntroller to set up the slide actions
Use FloatingActionButton to assign actions to the buttons generated while sliding the tile
Use the WidgetBuilder to build the tiles on the homepage
Let’s discuss the steps in detail.
You can import the flutter_slidable dependency in the pubspec.yaml file as shown below:
To import the dependency in the main.dart file use the following:
import 'package:flutter_slidable/flutter_slidable.dart';
Use a StatelessWidget to give the application a simple structure as shown below:
Dart
class MyApp extends StatelessWidget {    @override  Widget build(BuildContext context) {    return MaterialApp(      title: 'Flutter Slidable ',      theme: ThemeData(        primarySwatch: Colors.blue,      ),      home: MyHomePage(title: 'GeeksForGeeks'),    );  }}
Use a StatefulWidget to set up the homepage for the application that would in the future hold the tiles that can be swiped in either direction to perform tasks assigned to them as shown below:
Dart
class MyHomePage extends StatefulWidget {  MyHomePage({Key key, this.title}) : super(key: key);    final String title;    @override  _MyHomePageState createState() => _MyHomePageState();}  class _MyHomePageState extends State<MyHomePage> {  SlidableController slidableController;  final List<_HomeItem> items = List.generate(    20,        (i) => _HomeItem(      i,      'Slide Bar $i',      _getSubtitle(i),      _getAvatarColor(i),    ),  );
Use the SlidableController to setup the slides for the application. It can be done by the use of either Slidable Constructor or Slidable.builder Constructor. The app we are building has 4 essential components :
Slide ActionsA slide Action panel widgetAn extent ratio between a slide action extent and the item extent.A child
Slide Actions
A slide Action panel widget
An extent ratio between a slide action extent and the item extent.
A child
To design the slidable use the following:
Dart
Slidable(  actionPane: SlidableDrawerActionPane(),  actionExtentRatio: 0.25,  child: Container(    color: Colors.white,    child: ListTile(      leading: CircleAvatar(        backgroundColor: Colors.indigoAccent,        child: Text('$3'),        foregroundColor: Colors.white,      ),      title: Text('Tile $3'),      subtitle: Text('SlidableDrawerDelegate'),    ),  ),  actions: <Widget>[    IconSlideAction(      caption: 'Archive',      color: Colors.blue,      icon: Icons.archive,      onTap: () => _showSnackBar('Archive'),    ),    IconSlideAction(      caption: 'Share',      color: Colors.indigo,      icon: Icons.share,      onTap: () => _showSnackBar('Share'),    ),  ],  secondaryActions: <Widget>[    IconSlideAction(      caption: 'More',      color: Colors.black45,      icon: Icons.more_horiz,      onTap: () => _showSnackBar('More'),    ),    IconSlideAction(      caption: 'Delete',      color: Colors.red,      icon: Icons.delete,      onTap: () => _showSnackBar('Delete'),    ),  ],);
As the slides are swiped a FloatingActionButton will appear depending upon the direction of the slide. In both the case two actions will be assigned to each swipe  as following:
For left to right slide:
Archive tileShare tile
Archive tile
Share tile
For the right to left slide:
Delete tileMore
Delete tile
More
For the sake of simplicity, we will only assign actions to the Archive tile button and delete the tile button which will archive and delete the tile respectively. To do so use the following:
Dart
@protectedvoid initState() {  slidableController = 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;  });} @overrideWidget build(BuildContext context) {  return Scaffold(    appBar: AppBar(      title: Text(widget.title),      backgroundColor: Colors.green,    ),    body: Center(      child: 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 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 _getSlidableWithLists(    BuildContext context, int index, Axis direction) {  final _HomeItem item = items[index];  return Slidable(    key: Key(item.title),    controller: slidableController,    direction: direction,    dismissal: SlidableDismissal(      child: SlidableDrawerDismissal(),      onDismissed: (actionType) {        _showSnackBar(            context,            actionType == SlideActionType.primary                ? 'Dismiss Archive'                : 'Dismiss Delete');        setState(() {          items.removeAt(index);        });      },    ),    actionPane: _getActionPane(item.index),    actionExtentRatio: 0.25,    child: direction == Axis.horizontal        ? VerticalListItem(items[index])        : HorizontalListItem(items[index]),    actions: <Widget>[      IconSlideAction(        caption: 'Archive',        color: Colors.blue,        icon: Icons.archive,        onTap: () => _showSnackBar(context, 'Archive'),      ),      IconSlideAction(        caption: 'Share',        color: Colors.indigo,        icon: Icons.share,        onTap: () => _showSnackBar(context, 'Share'),      ),    ],    secondaryActions: <Widget>[      Container(        height: 800,        color: Colors.green,        child: Text('a'),      ),      IconSlideAction(        caption: 'More',        color: Colors.grey.shade200,        icon: Icons.more_horiz,        onTap: () => _showSnackBar(context, 'More'),        closeOnTap: false,      ),      IconSlideAction(        caption: 'Delete',        color: Colors.red,        icon: Icons.delete,        onTap: () => _showSnackBar(context, 'Delete'),      ),    ],  );}
Complete Source Code:
Dart
import 'package:flutter/material.dart';import 'package:flutter_slidable/flutter_slidable.dart';  void main() => runApp(MyApp());  class MyApp extends StatelessWidget {    @override  Widget build(BuildContext context) {    return MaterialApp(      title: 'Flutter Slidable ',      theme: ThemeData(        primarySwatch: Colors.blue,      ),      home: MyHomePage(title: 'GeeksForGeeks'),    );  }}  class MyHomePage extends StatefulWidget {  MyHomePage({Key key, this.title}) : super(key: key);    final String title;    @override  _MyHomePageState createState() => _MyHomePageState();}  class _MyHomePageState extends State<MyHomePage> {  SlidableController slidableController;  final List<_HomeItem> items = List.generate(    20,        (i) => _HomeItem(      i,      'Slide Bar $i',      _getSubtitle(i),      _getAvatarColor(i),    ),  );    @protected  void initState() {    slidableController = 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 Scaffold(      appBar: AppBar(        title: Text(widget.title),        backgroundColor: Colors.green,      ),      body: Center(        child: 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 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 _getSlidableWithLists(      BuildContext context, int index, Axis direction) {    final _HomeItem item = items[index];    //final int t = index;    return Slidable(      key: Key(item.title),      controller: slidableController,      direction: direction,      dismissal: SlidableDismissal(        child: SlidableDrawerDismissal(),        onDismissed: (actionType) {          _showSnackBar(              context,              actionType == SlideActionType.primary                  ? 'Dismiss Archive'                  : 'Dismiss Delete');          setState(() {            items.removeAt(index);          });        },      ),      actionPane: _getActionPane(item.index),      actionExtentRatio: 0.25,      child: direction == Axis.horizontal          ? VerticalListItem(items[index])          : HorizontalListItem(items[index]),      actions: <Widget>[        IconSlideAction(          caption: 'Archive',          color: Colors.blue,          icon: Icons.archive,          onTap: () => _showSnackBar(context, 'Archive'),        ),        IconSlideAction(          caption: 'Share',          color: Colors.indigo,          icon: Icons.share,          onTap: () => _showSnackBar(context, 'Share'),        ),      ],      secondaryActions: <Widget>[        Container(          height: 800,          color: Colors.green,          child: Text('a'),        ),        IconSlideAction(          caption: 'More',          color: Colors.grey.shade200,          icon: Icons.more_horiz,          onTap: () => _showSnackBar(context, 'More'),          closeOnTap: false,        ),        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 Slidable.builder(      key: Key(item.title),      controller: slidableController,      direction: direction,      dismissal: SlidableDismissal(        child: SlidableDrawerDismissal(),        closeOnCanceled: true,        onWillDismiss: (item.index != 10)            ? null            : (actionType) {          return showDialog<bool>(            context: context,            builder: (context) {              return AlertDialog(                title: Text('Delete'),                content: Text('Item will be deleted'),                actions: <Widget>[                  FlatButton(                    child: Text('Cancel'),                    onPressed: () => Navigator.of(context).pop(false),                  ),                  FlatButton(                    child: Text('Ok'),                    onPressed: () => Navigator.of(context).pop(true),                  ),                ],              );            },          );        },        onDismissed: (actionType) {          _showSnackBar(              context,              actionType == SlideActionType.primary                  ? 'Dismiss Archive'                  : 'Dismiss Delete');          setState(() {            items.removeAt(index);          });        },      ),      actionPane: _getActionPane(item.index),      actionExtentRatio: 0.25,      child: direction == Axis.horizontal          ? VerticalListItem(items[index])          : HorizontalListItem(items[index]),      actionDelegate: SlideActionBuilderDelegate(          actionCount: 2,          builder: (context, index, animation, renderingMode) {            if (index == 0) {              return 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 AlertDialog(                        title: Text('Delete'),                        content: Text('Item will be deleted'),                        actions: <Widget>[                          FlatButton(                            child: Text('Cancel'),                            onPressed: () => Navigator.of(context).pop(false),                          ),                          FlatButton(                            child: Text('Ok'),                            onPressed: () => Navigator.of(context).pop(true),                          ),                        ],                      );                    },                  );                    if (dismiss) {                    state.dismiss();                  }                },              );            } else {              return IconSlideAction(                caption: 'Share',                color: renderingMode == SlidableRenderingMode.slide                    ? Colors.indigo.withOpacity(animation.value)                    : Colors.indigo,                icon: Icons.share,                onTap: () => _showSnackBar(context, 'Share'),              );            }          }),      secondaryActionDelegate: SlideActionBuilderDelegate(          actionCount: 2,          builder: (context, index, animation, renderingMode) {            if (index == 0) {              return 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 IconSlideAction(                caption: 'Delete',                color: renderingMode == SlidableRenderingMode.slide                    ? Colors.red.withOpacity(animation.value)                    : Colors.red,                icon: Icons.delete,                onTap: () => _showSnackBar(context, 'Delete'),              );            }          }),    );  }    static Widget _getActionPane(int index) {    switch (index % 4) {      case 0:        return SlidableBehindActionPane();      case 1:        return SlidableStrechActionPane();      case 2:        return SlidableScrollActionPane();      case 3:        return SlidableDrawerActionPane();      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 ' ';      case 1:        return ' ';      case 2:        return ' ';      case 3:        return ' ';      default:        return null;    }  }    void _showSnackBar(BuildContext context, String text) {    Scaffold.of(context).showSnackBar(SnackBar(content: Text(text)));  }}  class HorizontalListItem extends StatelessWidget {  HorizontalListItem(this.item);  final _HomeItem item;  @override  Widget build(BuildContext context) {    return Container(      color: Colors.white,      width: 160.0,      child: Column(        mainAxisSize: MainAxisSize.max,        children: <Widget>[          Expanded(            child: CircleAvatar(              backgroundColor: item.color,              child: Text('${item.index}'),              foregroundColor: Colors.white,            ),          ),          Expanded(            child: Center(              child: Text(                item.subtitle,              ),            ),          ),        ],      ),    );  }}  class VerticalListItem extends StatelessWidget {  VerticalListItem(this.item);  final _HomeItem item;    @override  Widget build(BuildContext context) {    return GestureDetector(      onTap: () =>      Slidable.of(context)?.renderingMode == SlidableRenderingMode.none          ? Slidable.of(context)?.open()          : Slidable.of(context)?.close(),      child: Container(        color: Colors.white,        child: ListTile(          leading: CircleAvatar(            backgroundColor: item.color,            child: Text('${item.index}'),            foregroundColor: Colors.white,          ),          title: Text(item.title),          subtitle: Text(item.subtitle),        ),      ),    );  }}  class _HomeItem {  const _HomeItem(      this.index,      this.title,      this.subtitle,      this.color,      );    final int index;  final String title;  final String subtitle;  final Color color;}
Output:
android
Flutter
Flutter UI-components
Dart
Flutter
Writing code in comment?
Please use ide.geeksforgeeks.org,
generate link and share the link here.
Flutter - Custom Bottom Navigation Bar
ListView Class in Flutter
Flutter - Flexible Widget
Flutter - Stack Widget
Flutter - Positioned Widget
Flutter - Custom Bottom Navigation Bar
Flutter Tutorial
Flutter - Flexible Widget
Flutter - Stack Widget
Flutter - Positioned Widget