Last active
March 4, 2020 14:27
-
-
Save guptahitesh121/06e604f1857710f97c46e4df3f44b52b to your computer and use it in GitHub Desktop.
Flutter animated swipe to remove sample code. Cards can be dismissed by swiping left or right with animation.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'package:flutter/material.dart'; | |
void main() => runApp(MyApp()); | |
class MyApp extends StatelessWidget { | |
@override | |
Widget build(BuildContext context) { | |
return MaterialApp( | |
title: 'Swipe Demo', | |
debugShowCheckedModeBanner: false, | |
theme: ThemeData( | |
primarySwatch: Colors.blue, | |
), | |
home: CardDemo(), | |
); | |
} | |
} | |
class CardDemo extends StatefulWidget { | |
@override | |
CardDemoState createState() => CardDemoState(); | |
} | |
class CardDemoState extends State<CardDemo> { | |
List<int> data; | |
List<int> generateData() => List.generate(10, (i) => i + 1); | |
@override | |
void initState() { | |
data = generateData(); | |
super.initState(); | |
} | |
@override | |
Widget build(BuildContext context) { | |
return SafeArea( | |
child: Container( | |
color: Colors.white, | |
child: Column( | |
mainAxisAlignment: MainAxisAlignment.center, | |
children: <Widget>[ | |
Container( | |
width: 300, | |
height: 400, | |
child: SwipeableStack( | |
data: data, | |
frontSize: 0.67, | |
backSize: 0.55, | |
builder: (c, i, t) { | |
return Card( | |
elevation: 8, | |
color: Colors.red, | |
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)), | |
child: Center( | |
child: Text( | |
'$t', | |
style: TextStyle(fontSize: 150, fontWeight: FontWeight.bold, color: Colors.white), | |
), | |
), | |
); | |
}, | |
), | |
), | |
SizedBox(height: 30), | |
RaisedButton( | |
color: Colors.red, | |
child: Text( | |
'RESET', | |
style: TextStyle(fontWeight: FontWeight.bold, color: Colors.white), | |
), | |
onPressed: () { | |
setState(() { | |
data = generateData(); | |
}); | |
}, | |
) | |
], | |
)), | |
); | |
} | |
} | |
typedef SwipeableStackItemBuilder<T>(BuildContext context, int index, T item); | |
class SwipeableStack<T> extends StatefulWidget { | |
final List<T> data; | |
final double frontSize; | |
final double backSize; | |
final SwipeableStackItemBuilder<T> builder; | |
SwipeableStack({this.data = const [], this.frontSize = 1, this.backSize = 0.9, this.builder}); | |
@override | |
SwipeableStackState createState() => SwipeableStackState<T>(); | |
} | |
class SwipeableStackState<T> extends State<SwipeableStack> with SingleTickerProviderStateMixin { | |
List<T> data; | |
List<Size> cardsSize = List(2); | |
AnimationController controller; | |
Animation<Size> sizeAnimF; | |
@override | |
void initState() { | |
data = widget.data; | |
controller = AnimationController(duration: Duration(milliseconds: 200), vsync: this); | |
controller.addListener(() { | |
setState(() {}); | |
}); | |
super.initState(); | |
} | |
@override | |
void didUpdateWidget(SwipeableStack oldWidget) { | |
data = widget.data; | |
super.didUpdateWidget(oldWidget); | |
} | |
@override | |
void dispose() { | |
controller.dispose(); | |
super.dispose(); | |
} | |
Widget defWidget(BuildContext context, int index, T item, bool drag) { | |
return Swiper( | |
draggable: drag, | |
child: widget.builder?.call(context, index, item) ?? Container(), | |
swipedOut: () { | |
animate(); | |
data.removeAt(0); | |
}, | |
); | |
} | |
void animate() { | |
controller.stop(); | |
controller.value = 0.0; | |
controller.forward(); | |
} | |
Size get frontAnim { | |
if (controller.status == AnimationStatus.forward) { | |
if (sizeAnimF == null) { | |
sizeAnimF = SizeTween(begin: cardsSize[1], end: cardsSize[0]).animate( | |
CurvedAnimation(parent: controller, curve: Curves.easeIn), | |
); | |
} | |
return sizeAnimF.value; | |
} else | |
return cardsSize[0]; | |
} | |
Widget back(Widget child) { | |
return Align( | |
alignment: Alignment.center, | |
child: SizedBox.fromSize( | |
size: cardsSize[1], | |
child: child, | |
), | |
); | |
} | |
Widget front(Widget child) { | |
return Align( | |
alignment: Alignment.center, | |
child: SizedBox.fromSize( | |
size: frontAnim, | |
child: child, | |
), | |
); | |
} | |
@override | |
Widget build(BuildContext context) { | |
cardsSize[0] = Size(MediaQuery.of(context).size.width * widget.frontSize, MediaQuery.of(context).size.height); | |
cardsSize[1] = Size(MediaQuery.of(context).size.width, MediaQuery.of(context).size.height * widget.backSize); | |
var fw; | |
var bw; | |
if (data != null && data.isNotEmpty) { | |
fw = front(defWidget(context, 0, data[0], true)); | |
if (data.length > 1) { | |
bw = back(defWidget(context, 1, data[1], false)); | |
} | |
} | |
return Stack( | |
children: <Widget>[if (bw != null) bw, if (fw != null) fw], | |
); | |
} | |
} | |
typedef SwipedOut(); | |
class Swiper extends StatefulWidget { | |
final SwipedOut swipedOut; | |
final Widget child; | |
final bool draggable; | |
Swiper({this.child, this.swipedOut, this.draggable = true}) : super(key: UniqueKey()); | |
@override | |
SwiperState createState() => SwiperState(); | |
} | |
class SwiperState extends State<Swiper> with SingleTickerProviderStateMixin { | |
Offset o = Offset.zero; | |
AnimationController controller; | |
Animation<Offset> anim; | |
Offset end; | |
@override | |
void initState() { | |
controller = AnimationController(duration: Duration(milliseconds: 300), vsync: this); | |
controller.addListener(() => setState(() { | |
if (anim != null) o = anim.value; | |
})); | |
controller.addStatusListener((AnimationStatus s) { | |
if (s == AnimationStatus.completed && end != Offset.zero) widget.swipedOut?.call(); | |
}); | |
super.initState(); | |
} | |
@override | |
void dispose() { | |
controller.dispose(); | |
super.dispose(); | |
} | |
Offset get offset { | |
if (controller.status == AnimationStatus.forward) { | |
if (anim == null) { | |
anim = Tween<Offset>(begin: o, end: end).animate( | |
CurvedAnimation( | |
parent: controller, | |
curve: Curves.easeInCubic, | |
), | |
); | |
} | |
return anim.value; | |
} else { | |
anim = null; | |
return o; | |
} | |
} | |
double displacement(double width, double dx) { | |
return (dx / width * 100); | |
} | |
@override | |
Widget build(BuildContext context) { | |
if (!widget.draggable) { | |
return widget.child ?? Container(); | |
} else { | |
final width = MediaQuery.of(context).size.width; | |
return GestureDetector( | |
onPanUpdate: (details) { | |
setState(() { | |
o += details.delta; | |
}); | |
}, | |
onPanEnd: (_) { | |
final dis = displacement(width, o.dx); | |
if (dis > 30 || dis < -30) { | |
end = o * 5; | |
} else { | |
end = Offset.zero; | |
} | |
controller.stop(); | |
controller.value = 0.0; | |
controller.forward(); | |
}, | |
child: Transform.translate( | |
offset: offset, | |
child: Transform.rotate( | |
angle: (pi / 180.0) * o.dx / 13, | |
child: Transform.scale( | |
scale: 1 - ((o.dx / width) * 0.3).abs(), | |
child: widget.child ?? Container(), | |
), | |
), | |
), | |
); | |
} | |
} | |
} |
Author
guptahitesh121
commented
Feb 20, 2020
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment