Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swipe List Item for more options (Flutter)

Somedays ago I decided to choose an Ui for an app from Pinterest to practice building apps with Flutter but I'm stuck with the Slider which shows an "more" and "delete" button on horizontal drag. Picture on the right.

I don't have enough knowledge to use Gestures combined with Animations to create something like this in flutter. Thats why I hope that someone of you can make an example for everyone like me that we can understand how to implement something like this in a ListView.builder.

enter image description here (Source)

An gif example from the macOS mail App:

enter image description here

like image 749
Lukas Kirner Avatar asked Oct 09 '17 17:10

Lukas Kirner


People also ask

How do you slide list in flutter?

Use Slidable Flutter Package To Swipe To Either Right Or Left On The Tile In Your Flutter Apps. The flutter_slidable plugin adds a component rich slider widget to your project. Sliders like this are frequently observed in scrollable records.

How do I display list dynamic flutter?

We can use ListView. builder(..) in Stateless Widgets if we want to display dynamic (different) contents every time the widget is loaded within an application. With Stateful widgets, it can change the contents of the screen dynamically.


2 Answers

I created a package for doing this kind of layout: flutter_slidable (Thanks Rémi Rousselet for the based idea)

With this package it's easier to create contextual actions for a list item. For example if you want to create the kind of animation you described:

Drawer (iOS) animation

You will use this code:

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'),     ),   ], ); 
like image 65
Romain Rastel Avatar answered Sep 29 '22 21:09

Romain Rastel


There's already a widget for this kind of gesture. It's called Dismissible.

You can find it here. https://docs.flutter.io/flutter/widgets/Dismissible-class.html

EDIT

If you need the exact same transtion, you'd probably have to implement if yourself. I made a basic example. You'd probably want to tweak the animation a bit, but it's working at least.

enter image description here

class Test extends StatefulWidget {   @override   _TestState createState() => new _TestState(); }  class _TestState extends State<Test> {   double rating = 3.5;    @override   Widget build(BuildContext context) {     return new Scaffold(       body: new ListView(         children: ListTile             .divideTiles(               context: context,               tiles: new List.generate(42, (index) {                 return new SlideMenu(                   child: new ListTile(                     title: new Container(child: new Text("Drag me")),                   ),                   menuItems: <Widget>[                     new Container(                       child: new IconButton(                         icon: new Icon(Icons.delete),                       ),                     ),                     new Container(                       child: new IconButton(                         icon: new Icon(Icons.info),                       ),                     ),                   ],                 );               }),             )             .toList(),       ),     );   } }  class SlideMenu extends StatefulWidget {   final Widget child;   final List<Widget> menuItems;    SlideMenu({this.child, this.menuItems});    @override   _SlideMenuState createState() => new _SlideMenuState(); }  class _SlideMenuState extends State<SlideMenu> with SingleTickerProviderStateMixin {   AnimationController _controller;    @override   initState() {     super.initState();     _controller = new AnimationController(vsync: this, duration: const Duration(milliseconds: 200));   }    @override   dispose() {     _controller.dispose();     super.dispose();   }    @override   Widget build(BuildContext context) {     final animation = new Tween(       begin: const Offset(0.0, 0.0),       end: const Offset(-0.2, 0.0)     ).animate(new CurveTween(curve: Curves.decelerate).animate(_controller));      return new GestureDetector(       onHorizontalDragUpdate: (data) {         // we can access context.size here         setState(() {           _controller.value -= data.primaryDelta / context.size.width;         });       },       onHorizontalDragEnd: (data) {         if (data.primaryVelocity > 2500)           _controller.animateTo(.0); //close menu on fast swipe in the right direction         else if (_controller.value >= .5 || data.primaryVelocity < -2500) // fully open if dragged a lot to left or on fast swipe to left           _controller.animateTo(1.0);         else // close if none of above           _controller.animateTo(.0);       },       child: new Stack(         children: <Widget>[           new SlideTransition(position: animation, child: widget.child),           new Positioned.fill(             child: new LayoutBuilder(               builder: (context, constraint) {                 return new AnimatedBuilder(                   animation: _controller,                   builder: (context, child) {                     return new Stack(                       children: <Widget>[                         new Positioned(                           right: .0,                           top: .0,                           bottom: .0,                           width: constraint.maxWidth * animation.value.dx * -1,                           child: new Container(                             color: Colors.black26,                             child: new Row(                               children: widget.menuItems.map((child) {                                 return new Expanded(                                   child: child,                                 );                               }).toList(),                             ),                           ),                         ),                       ],                     );                   },                 );               },             ),           )         ],       ),     );   } } 

EDIT

Flutter no longer allows type Animation<FractionalOffset> in SlideTransition animation property. According to this post https://groups.google.com/forum/#!topic/flutter-dev/fmr-C9xK5t4 it should be replaced with AlignmentTween but this also doesn't work. Instead, according to this issue: https://github.com/flutter/flutter/issues/13812 replacing it instead with a raw Tween and directly creating Offset object works instead. Unfortunately, the code is much less clear.

like image 22
Rémi Rousselet Avatar answered Sep 29 '22 19:09

Rémi Rousselet