Flutter Swipe List Item Tutorial
We all have seen the WhatsApp swipe to reply feature, it looks very cool when swipe and a reply text field appears. We can also make the same animation, same left, right swipe (WhatsApp has only right swipe though) events in our flutter project. Here is a very nice library that does this work for us and we can make flutter swipe list items very easy.
Flutter swipe list items is useful when we want to perform any action on a particular list item, of course, there are other ways too. but it looks nice when you have some cool animated stuff in your layout. Flutter is very good in customization of widgets and this is my most favorite task in flutter development to play with custom widgets. To make things useful with less code and effort and share with other developers to make their life easy is such a wonderful feeling.
In this tutorial, we have a simple example to demonstrate the working of the “swipeable” library. I tried to produce the same WhatsApp-like feeling in this tutorial. On swipe, we will have the same result as WhatsApp gave.
Flutter is an awesome mobile application development platform that provides a lot of good stuff with few lines of code
Expected output
Required Dependencies
swipeable:
fluttertoast:
Installing it
flutter pub get
We are using the flutter “swipeable” package to make a swipeable list item and the second one is “fluttertoast” to show the toast on an event.
Let’s make whats app-like appbar
There is no special thing is done in this section, we have put a profile image in using CircleAvatar. Then added a title which is our contact name. Appbar provides us the power to add an action button so we have an array of activities that are represented by icons.
AppBar(
leading: Row(
children: [
Padding(
padding: const EdgeInsets.only(left: 10),
child: CircleAvatar(
backgroundImage:
NetworkImage('https://picsum.photos/id/237/200/300'),
),
),
],
),
title: Material(
color: Colors.white.withOpacity(0.0),
child: InkWell(
onTap: () {},
child: Row(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Padding(
padding: const EdgeInsets.symmetric(horizontal: 2.0),
child: Text(
'Flutter Fumes',
style: TextStyle(
color: Colors.white,
fontSize: 18.0,
),
),
),
],
),
],
),
),
),
actions: <Widget>[
Builder(
builder: (BuildContext context) {
return IconButton(
icon: Icon(Icons.videocam),
onPressed: () {
},
);
},
),
Builder(
builder: (BuildContext context) {
return IconButton(
icon: Icon(Icons.call),
onPressed: () {
},
);
},
),
],
)
List of dummy messages
We created a dummy list of messages to make a list of items.
Flexible(
flex: 1,
child: ListView.builder(
itemCount: dummyMessages.length,
itemBuilder: (context, index) {
return Container(
child: Swipeable(
threshold: 100.0,
onSwipeLeft: () {
setState(() {
rightSelected = true;
leftSelected = false;
});
},
onSwipeRight: () {
setState(() {
rightSelected = false;
leftSelected = true;
});
},
child: Card(
key: UniqueKey(),
child: Padding(
padding: EdgeInsets.all(20),
child: Text(dummyMessages[index]),
),
),
background: Icon(
Icons.reply,
color: Colors.black26,
),
),
);
}),
),
Message text field with attachment, smeily, and send button
Padding(
padding: const EdgeInsets.fromLTRB(10, 0, 10, 10),
child: Column(
children: [
leftSelected
? _replyWidget()
: AnimatedContainer(
duration: Duration(seconds:1),
),
Row(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Flexible(
flex: 1,
child: Container(
decoration: BoxDecoration(
borderRadius:
BorderRadius.all(const Radius.circular(30.0)),
color: Colors.white,
),
child: Row(
children: <Widget>[
IconButton(
padding: const EdgeInsets.all(0.0),
disabledColor: Colors.grey,
color: Colors.redAccent,
icon: Icon(Icons.insert_emoticon),
onPressed: () {},
),
Flexible(
child: TextField(
decoration: InputDecoration(
border: InputBorder.none,
contentPadding: const EdgeInsets.all(0.0),
hintText: 'Type a message',
hintStyle: TextStyle(
fontSize: 16.0,
),
counterText: '',
),
onSubmitted: (String text) {},
keyboardType: TextInputType.multiline,
maxLines: null,
maxLength: 100,
),
),
IconButton(
color: Colors.redAccent,
icon: Icon(Icons.attach_file),
onPressed: () {},
),
IconButton(
color: Colors.redAccent,
icon: Icon(Icons.camera_alt),
onPressed: () {},
)
],
),
),
),
Padding(
padding: const EdgeInsets.only(left: 4.0),
child: FloatingActionButton(
elevation: 2.0,
foregroundColor: Colors.white,
child: Icon(Icons.send),
onPressed: () {
Fluttertoast.showToast(msg: 'Sending Message');
}),
)
],
),
],
),
)
Reply widget design
Widget _replyWidget() {
return AnimatedContainer(
alignment: Alignment.centerLeft,
duration: Duration(seconds: 1),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(10.0)),
color: Colors.white,
),
child: Padding(
padding: EdgeInsets.all(5),
child: Stack(
children: [
Container(
alignment: Alignment.centerLeft,
padding: EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.grey.shade100,
border: Border(
left: BorderSide(color: Colors.purple, width: 3),
),
),
child: Column(
children: [
Text('Reply',
style: TextStyle(
color: Colors.purple,
fontSize: 15,
fontWeight: FontWeight.w500)),
SizedBox(height: 3),
Text('This is text message sample',
style: TextStyle(
color: Colors.black54,
fontSize: 12,
)),
],
),
),
Positioned(
right: 10,
top: 10,
child: GestureDetector(
onTap: () {
setState(() {
leftSelected = false;
rightSelected = false;
});
},
child: Icon(
Icons.clear,
color: Colors.black87,
size: 18,
),
)),
],
),
),
);
}
Complete Source Code
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:swipeable/swipeable.dart';
class ChatDetailScreen extends StatefulWidget {
_ChatDetailScreen createState() => _ChatDetailScreen();
}
class _ChatDetailScreen extends State<ChatDetailScreen> {
final dummyMessages = List<String>.generate(1000, (i) => 'Message $i');
late bool leftSelected;
late bool rightSelected;
final Color scaffoldBgColor = const Color(0xfff0eee4);
@override
void initState() {
leftSelected = false;
rightSelected = false;
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: scaffoldBgColor,
appBar: AppBar(
leading: Row(
children: [
Padding(
padding: const EdgeInsets.only(left: 10),
child: CircleAvatar(
backgroundImage:
NetworkImage('https://picsum.photos/id/237/200/300'),
),
),
],
),
title: Material(
color: Colors.white.withOpacity(0.0),
child: InkWell(
onTap: () {},
child: Row(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Padding(
padding: const EdgeInsets.symmetric(horizontal: 2.0),
child: Text(
'Flutter Fumes',
style: TextStyle(
color: Colors.white,
fontSize: 18.0,
),
),
),
],
),
],
),
),
),
actions: <Widget>[
Builder(
builder: (BuildContext context) {
return IconButton(
icon: Icon(Icons.videocam),
onPressed: () {
},
);
},
),
Builder(
builder: (BuildContext context) {
return IconButton(
icon: Icon(Icons.call),
onPressed: () {
},
);
},
),
],
),
body: Column(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Flexible(
flex: 1,
child: ListView.builder(
itemCount: dummyMessages.length,
itemBuilder: (context, index) {
return Container(
child: Swipeable(
threshold: 100.0,
onSwipeLeft: () {
setState(() {
rightSelected = true;
leftSelected = false;
});
},
onSwipeRight: () {
setState(() {
rightSelected = false;
leftSelected = true;
});
},
child: Card(
key: UniqueKey(),
child: Padding(
padding: EdgeInsets.all(20),
child: Text(dummyMessages[index]),
),
),
background: Icon(
Icons.reply,
color: Colors.black26,
),
),
);
}),
),
Padding(
padding: const EdgeInsets.fromLTRB(10, 0, 10, 10),
child: Column(
children: [
leftSelected
? _replyWidget()
: AnimatedContainer(
duration: Duration(seconds:1),
),
Row(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Flexible(
flex: 1,
child: Container(
decoration: BoxDecoration(
borderRadius:
BorderRadius.all(const Radius.circular(30.0)),
color: Colors.white,
),
child: Row(
children: <Widget>[
IconButton(
padding: const EdgeInsets.all(0.0),
disabledColor: Colors.grey,
color: Colors.redAccent,
icon: Icon(Icons.insert_emoticon),
onPressed: () {},
),
Flexible(
child: TextField(
decoration: InputDecoration(
border: InputBorder.none,
contentPadding: const EdgeInsets.all(0.0),
hintText: 'Type a message',
hintStyle: TextStyle(
fontSize: 16.0,
),
counterText: '',
),
onSubmitted: (String text) {},
keyboardType: TextInputType.multiline,
maxLines: null,
maxLength: 100,
),
),
IconButton(
color: Colors.redAccent,
icon: Icon(Icons.attach_file),
onPressed: () {},
),
IconButton(
color: Colors.redAccent,
icon: Icon(Icons.camera_alt),
onPressed: () {},
)
],
),
),
),
Padding(
padding: const EdgeInsets.only(left: 4.0),
child: FloatingActionButton(
elevation: 2.0,
foregroundColor: Colors.white,
child: Icon(Icons.send),
onPressed: () {
Fluttertoast.showToast(msg: 'Sending Message');
}),
)
],
),
],
),
)
],
),
);
}
Widget _replyWidget() {
return AnimatedContainer(
alignment: Alignment.centerLeft,
duration: Duration(seconds: 1),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(10.0)),
color: Colors.white,
),
child: Padding(
padding: EdgeInsets.all(5),
child: Stack(
children: [
Container(
alignment: Alignment.centerLeft,
padding: EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.grey.shade100,
border: Border(
left: BorderSide(color: Colors.purple, width: 3),
),
),
child: Column(
children: [
Text('Reply',
style: TextStyle(
color: Colors.purple,
fontSize: 15,
fontWeight: FontWeight.w500)),
SizedBox(height: 3),
Text('This is text message sample',
style: TextStyle(
color: Colors.black54,
fontSize: 12,
)),
],
),
),
Positioned(
right: 10,
top: 10,
child: GestureDetector(
onTap: () {
setState(() {
leftSelected = false;
rightSelected = false;
});
},
child: Icon(
Icons.clear,
color: Colors.black87,
size: 18,
),
)),
],
),
),
);
}
}
If you have any query feel free to ask. Also, check out the bottom navigation series and custom textfield, social button design, and many others.
Thanks for reading.