Skip to content

Instantly share code, notes, and snippets.

@guptahitesh121
Last active March 4, 2020 14:27
Show Gist options
  • Save guptahitesh121/06e604f1857710f97c46e4df3f44b52b to your computer and use it in GitHub Desktop.
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.
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(),
),
),
),
);
}
}
}
@guptahitesh121
Copy link
Author

SwipeToDismiss

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment