Open In App

Flutter – Slidable

Improve
Improve
Like Article
Like
Save
Share
Report

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.

Adding Dependency:

You can import the flutter_slidable dependency in the pubspec.yaml file as shown below:

dependency

Importing Dependency:

To import the dependency in the main.dart file use the following:

import 'package:flutter_slidable/flutter_slidable.dart';

Creating App Structure:

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'),
    );
  }
}


Creating Homepage:

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),
    ),
  );


Designing the slides:

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 :

  1. Slide Actions
  2. A slide Action panel widget
  3. An extent ratio between a slide action extent and the item extent.
  4. 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'),
    ),
  ],
);


Assigning Actions:

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:
  1. Archive tile
  2. Share tile
  • For the right to left slide:
  1. Delete tile
  2. 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




@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];
  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:



Last Updated : 15 Feb, 2021
Like Article
Save Article
Previous
Next
Share your thoughts in the comments
Similar Reads