This tutorial is about how to use Dismissible
widget in Flutter.
In Flutter, if you need to create a widget that can be dismissed, you can wrap the widget as the child
of Dismissible
. Dismissible
is usually used to wrap each list item so that it can be dismissed, either horizontally or vertically. I'm going to give you some examples of how to use the widget, including how to show confirmation modal, set backgrounds that will be shown when the child is being dismissed, and set dismiss direction.
Creating Dismissible
Below is the constructor of Dismissible
const Dismissible({
@required Key key,
@required this.child,
this.background,
this.secondaryBackground,
this.confirmDismiss,
this.onResize,
this.onDismissed,
this.direction = DismissDirection.horizontal,
this.resizeDuration = const Duration(milliseconds: 300),
this.dismissThresholds = const <DismissDirection, double>{},
this.movementDuration = const Duration(milliseconds: 200),
this.crossAxisEndOffset = 0.0,
this.dragStartBehavior = DragStartBehavior.start,
})
You are required to pass key
(Key
) and child
(Widget
). key
becomes very important since the widget can be removed from the widget list. If there are multiple dismissible widgets, make sure each has a unique key. Be careful not to use index as key as dismissing a widget can change the index of other widgets. The second required property is child
where you need to pass the widget that can be dismissed.
Another important property is onDismissed
. It's a callback function accepting one parameter of type DismissDirection
. Inside, you can define what to do after the widget has been dismissed. For example, you can remove the widget from the list.
For other properties, you can read the Properties section.
For example, we are going to create a simple ListView
where the item can be dismissed. The ListView
is created using the following values.
List<String> _values = ['One', 'Two', 'Three', 'Four', 'Five'];
Here's the code for building the ListView
. The itemBuilder
, which is used to build the list items, returns a Dismissible
. In addition of the required arguments (key
and child
), onDismissed
callback is also passed. The below example shows you how to set different action for each direction.
ListView.separated(
itemCount: _values.length,
padding: const EdgeInsets.all(5.0),
separatorBuilder: (context, index) => Divider(
color: Colors.black,
),
itemBuilder: (context, index) {
return Dismissible(
key: Key('item ${_values[index]}'),
onDismissed: (DismissDirection direction) {
if (direction == DismissDirection.startToEnd) {
print("Add to favorite");
} else {
print('Remove item');
}
setState(() {
_values.removeAt(index);
});
},
child: ListTile(
leading: Icon(Icons.local_activity, size: 50),
title: Text(_values[index]),
subtitle: Text('Description here'),
),
);
}
),
Output:
Showing Confirmation
Dismissible
is often used for delete action. If you think the performed action is crucial and cannot be undone, it's better to show confirmation before the action defined inside onDismissed
is performed. You can do it by passing confirmDismissCallback
to the constructor. It's a callback that accepts one parameter of type DismissDirection
and returns Future<bool>
. The below example shows an AlertDialog
where the user can confirm to delete the item or cancel the action.
confirmDismiss: (DismissDirection direction) async {
return await showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text("Delete Confirmation"),
content: const Text("Are you sure you want to delete this item?"),
actions: <Widget>[
FlatButton(
onPressed: () => Navigator.of(context).pop(true),
child: const Text("Delete")
),
FlatButton(
onPressed: () => Navigator.of(context).pop(false),
child: const Text("Cancel"),
),
],
);
},
);
}
Output:
Setting Backgrounds
The default dismiss direction is horizontal. You can swipe to the right or left. Swiping to left or right may end up in a different action depending on what you define in onDismissed
callback. Not only different actions, Flutter allows you to set different widgets that will be shown when the child
is being dismissed. Use background
to define the widget to be displayed when the child
is swiped to the right and secondaryBackground
for the widget when the child
is swiped to the left. If you only set background
, it will be used for both directions.
background: Container(
color: Colors.blue,
child: Padding(
padding: const EdgeInsets.all(15),
child: Row(
children: [
Icon(Icons.favorite, color: Colors.white),
Text('Move to favorites', style: TextStyle(color: Colors.white)),
],
),
),
),
secondaryBackground: Container(
color: Colors.red,
child: Padding(
padding: const EdgeInsets.all(15),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Icon(Icons.delete, color: Colors.white),
Text('Move to trash', style: TextStyle(color: Colors.white)),
],
),
),
),
Output:
Below is the full code that you can copy if you want to try the code in this tutorial. This includes the confirmation modal and different backgrounds for each direction.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Welcome to Flutter',
home: _DismissibleApp(),
);
}
}
class _DismissibleApp extends StatefulWidget {
@override
_DismissibleAppState createState() => new _DismissibleAppState();
}
class _DismissibleAppState extends State<_DismissibleApp> {
List<String> _values = ['One', 'Two', 'Three', 'Four', 'Five'];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Woolha.com Flutter Tutorial'),
),
body: Padding(
padding: const EdgeInsets.all(15),
child: ListView.separated(
itemCount: _values.length,
padding: const EdgeInsets.all(5.0),
separatorBuilder: (context, index) => Divider(
color: Colors.black,
),
itemBuilder: (context, index) {
return Dismissible(
key: Key('item ${_values[index]}'),
background: Container(
color: Colors.blue,
child: Padding(
padding: const EdgeInsets.all(15),
child: Row(
children: <Widget>[
Icon(Icons.favorite, color: Colors.white),
Text('Move to favorites', style: TextStyle(color: Colors.white)),
],
),
),
),
secondaryBackground: Container(
color: Colors.red,
child: Padding(
padding: const EdgeInsets.all(15),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
Icon(Icons.delete, color: Colors.white),
Text('Move to trash', style: TextStyle(color: Colors.white)),
],
),
),
),
confirmDismiss: (DismissDirection direction) async {
return await showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text("Delete Confirmation"),
content: const Text("Are you sure you want to delete this item?"),
actions: <Widget>[
FlatButton(
onPressed: () => Navigator.of(context).pop(true),
child: const Text("Delete")
),
FlatButton(
onPressed: () => Navigator.of(context).pop(false),
child: const Text("Cancel"),
),
],
);
},
);
},
onDismissed: (DismissDirection direction) {
if (direction == DismissDirection.startToEnd) {
print("Add to favorite");
} else {
print('Remove item');
}
setState(() {
_values.removeAt(index);
});
},
child: ListTile(
leading: Icon(Icons.local_activity, size: 50),
title: Text(_values[index]),
subtitle: Text('Description here'),
),
);
}
),
),
);
}
}
Output:
Using Dismissible
with Vertical Direction
If the scroll direction of the list is horizontal, the dismiss direction should be vertical (up or down). You need to set the value of direction
to DismissDirection.vertical
. Below is the full code with horizontal list and vertical dismiss direction.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Woolha.com Flutter tutorial',
home: _DismissibleApp(),
);
}
}
class _DismissibleApp extends StatefulWidget {
@override
_DismissibleAppState createState() => new _DismissibleAppState();
}
class _DismissibleAppState extends State {
List _values = ['One', 'Two', 'Three', 'Four', 'Five'];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Woolha.com Flutter Tutorial'),
),
body: Padding(
padding: const EdgeInsets.all(15),
child: Container(
alignment: Alignment.center,
height: 100,
child: ListView.separated(
scrollDirection: Axis.horizontal,
itemCount: _values.length,
padding: const EdgeInsets.all(5.0),
separatorBuilder: (context, index) => VerticalDivider(
color: Colors.black,
),
itemBuilder: (context, index) {
return Dismissible(
direction: DismissDirection.vertical,
key: Key('item ${_values[index]}'),
background: Container(
color: Colors.blue,
child: Padding(
padding: const EdgeInsets.all(15),
child: Icon(Icons.favorite, color: Colors.white),
),
),
secondaryBackground: Container(
color: Colors.red,
child: Padding(
padding: const EdgeInsets.all(15),
child: Icon(Icons.delete, color: Colors.white),
),
),
onDismissed: (DismissDirection direction) {
if (direction == DismissDirection.down) {
print("Add to favorite");
} else {
print('Remove item');
}
setState(() {
_values.removeAt(index);
});
},
child: Container(
width: 100,
child: Center(
child: Column(
children: [
Icon(Icons.local_activity, size: 50),
Text(_values[index]),
],
),
),
),
);
}
),
)
),
);
}
}
Output:
Properties
Here's the list of properties of Dismissible
you can pass to the constructor.
Key key
*: The widget key, used to control if it should be replaced.Widget child
*: The widget below this widget in the tree.Widget background
: A widget stacked behind the child. IfsecondaryBackground
is set, it's only shown when the child is being dragged down or to the right.Widget secondaryBackground
: A widget stacked behind the child. It's only shown when the child is being dragged up or to the left.ConfirmDismissCallback confirmDismiss
: Gives the app an opportunity to confirm or veto a pending dismissal.VoidCallback onResize
: A callback that will be called when the widget changes size.DismissDirectionCallback onDismissed
: A callback that will be called when the widget has been dismissed.DismissDirection direction
: The direction in which the widget can be dismissed. Defaults toDismissDirection.horizontal
.Duration resizeDuration
: The amount of time the widget will spend contracting beforeonDismissed
is called. Defaults toconst Duration(milliseconds: 300)
.Map<DismissDirection, double> dismissThresholds
: The offset threshold the item has to be dragged in order to be considered dismissed. Defaults toconst <DismissDirection, double>
Duration movementDuration
: The duration to dismiss or back to original position if not dismissed. Defaults toconst Duration(milliseconds: 200)
.double crossAxisEndOffset
: The end offset across the main axis after the card is dismissed. Defaults to0.0
.DragStartBehavior dragStartBehavior
: How the drag start behavior is handled. Defaults toDragStartBehavior.start
.
*: required