Created
December 9, 2018 01:02
-
-
Save slightfoot/d2a98cadaaeb7f2df63b6a3996e4dc5e to your computer and use it in GitHub Desktop.
Flutter Swipe Button Demo
You should add license, or made into a package.
Good widget, it solves my problem, i add your name as �the author when i use your widget, you should make it into a package and add the license for this. Thanks again!
I have changed the code to support reverse swipe content !
enum SwipePosition {
SwipeLeft,
SwipeRight,
}
class SwipeButton extends StatefulWidget {
const SwipeButton({
Key key,
this.thumb,
this.content,
BorderRadius borderRadius,
this.initialPosition = SwipePosition.SwipeLeft,
@required this.onChanged,
this.height = 56.0,
this.thumbColor,
this.rectColor,
}) : assert(initialPosition != null && onChanged != null && height != null),
this.borderRadius = borderRadius ?? BorderRadius.zero,
super(key: key);
final Widget thumb;
final Widget content;
final BorderRadius borderRadius;
final double height;
final SwipePosition initialPosition;
final Color thumbColor;
final Color rectColor;
final ValueChanged<SwipePosition> onChanged;
@override
SwipeButtonState createState() => SwipeButtonState();
}
class SwipeButtonState extends State<SwipeButton>
with SingleTickerProviderStateMixin {
final GlobalKey _containerKey = GlobalKey();
final GlobalKey _positionedKey = GlobalKey();
AnimationController _controller;
Animation<double> _contentAnimation;
Offset _start = Offset.zero;
RenderBox get _positioned => _positionedKey.currentContext.findRenderObject();
RenderBox get _container => _containerKey.currentContext.findRenderObject();
@override
void initState() {
super.initState();
_controller = AnimationController.unbounded(vsync: this);
_contentAnimation = Tween<double>(begin: 1.0, end: 0.0).animate(
CurvedAnimation(parent: _controller, curve: Curves.easeOut),
);
if (widget.initialPosition == SwipePosition.SwipeRight) {
_controller.value = 1.0;
}
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return SizedBox(
width: double.infinity,
height: widget.height,
child: Stack(
key: _containerKey,
children: <Widget>[
DecoratedBox(
decoration: BoxDecoration(
color: widget.rectColor,
borderRadius: widget.borderRadius,
),
child: ClipRRect(
clipper: _SwipeButtonClipper(
animation: _controller,
borderRadius: widget.borderRadius,
position: widget.initialPosition),
borderRadius: widget.borderRadius,
child: SizedBox.expand(
child: Padding(
padding: EdgeInsets.only(left: widget.height),
child: widget.content,
),
),
),
),
AnimatedBuilder(
animation: _controller,
builder: (BuildContext context, Widget child) {
return Align(
alignment: Alignment((_controller.value * 2.0) - 1.0, 0.0),
child: child,
);
},
child: GestureDetector(
onHorizontalDragStart: _onDragStart,
onHorizontalDragUpdate: _onDragUpdate,
onHorizontalDragEnd: _onDragEnd,
child: Container(
key: _positionedKey,
width: widget.height,
height: widget.height,
decoration: BoxDecoration(
color: widget.thumbColor,
borderRadius: widget.borderRadius,
),
child: widget.thumb,
),
),
),
],
),
);
}
void _onDragStart(DragStartDetails details) {
final pos = _positioned.globalToLocal(details.globalPosition);
_start = Offset(pos.dx, 0.0);
_controller.stop(canceled: true);
}
void _onDragUpdate(DragUpdateDetails details) {
final pos = _container.globalToLocal(details.globalPosition) - _start;
final extent = _container.size.width - _positioned.size.width;
_controller.value = (pos.dx.clamp(0.0, extent) / extent);
}
void _onDragEnd(DragEndDetails details) {
final extent = _container.size.width - _positioned.size.width;
var fractionalVelocity = (details.primaryVelocity / extent).abs();
if (fractionalVelocity < 0.5) {
fractionalVelocity = 0.5;
}
SwipePosition result;
double acceleration, velocity;
if (_controller.value > 0.5) {
acceleration = 0.5;
velocity = fractionalVelocity;
result = SwipePosition.SwipeRight;
} else {
acceleration = -0.5;
velocity = -fractionalVelocity;
result = SwipePosition.SwipeLeft;
}
final simulation = _SwipeSimulation(
acceleration,
_controller.value,
1.0,
velocity,
);
_controller.animateWith(simulation).then((_) {
if (widget.onChanged != null) {
widget.onChanged(result);
}
});
}
}
class _SwipeSimulation extends GravitySimulation {
_SwipeSimulation(
double acceleration, double distance, double endDistance, double velocity)
: super(acceleration, distance, endDistance, velocity);
@override
double x(double time) => super.x(time).clamp(0.0, 1.0);
@override
bool isDone(double time) {
final _x = x(time).abs();
return _x <= 0.0 || _x >= 1.0;
}
}
class _SwipeButtonClipper extends CustomClipper<RRect> {
const _SwipeButtonClipper({
@required this.animation,
@required this.borderRadius,
this.position,
}) : assert(animation != null && borderRadius != null),
super(reclip: animation);
final Animation<double> animation;
final BorderRadius borderRadius;
final SwipePosition position;
@override
RRect getClip(Size size) {
return borderRadius.toRRect(
Rect.fromLTRB(
position == SwipePosition.SwipeRight
? size.width * animation.value
: size.width,
0.0,
position == SwipePosition.SwipeLeft
? size.width * animation.value
: 0.0,
size.height,
),
);
}
@override
bool shouldReclip(_SwipeButtonClipper oldClipper) => true;
}
How to change the state of the icons and text when SwipePosition.SwipeRight action is done?
I have changed the code to support reverse swipe content !
enum SwipePosition { SwipeLeft, SwipeRight, } class SwipeButton extends StatefulWidget { const SwipeButton({ Key key, this.thumb, this.content, BorderRadius borderRadius, this.initialPosition = SwipePosition.SwipeLeft, @required this.onChanged, this.height = 56.0, this.thumbColor, this.rectColor, }) : assert(initialPosition != null && onChanged != null && height != null), this.borderRadius = borderRadius ?? BorderRadius.zero, super(key: key); final Widget thumb; final Widget content; final BorderRadius borderRadius; final double height; final SwipePosition initialPosition; final Color thumbColor; final Color rectColor; final ValueChanged<SwipePosition> onChanged; @override SwipeButtonState createState() => SwipeButtonState(); } class SwipeButtonState extends State<SwipeButton> with SingleTickerProviderStateMixin { final GlobalKey _containerKey = GlobalKey(); final GlobalKey _positionedKey = GlobalKey(); AnimationController _controller; Animation<double> _contentAnimation; Offset _start = Offset.zero; RenderBox get _positioned => _positionedKey.currentContext.findRenderObject(); RenderBox get _container => _containerKey.currentContext.findRenderObject(); @override void initState() { super.initState(); _controller = AnimationController.unbounded(vsync: this); _contentAnimation = Tween<double>(begin: 1.0, end: 0.0).animate( CurvedAnimation(parent: _controller, curve: Curves.easeOut), ); if (widget.initialPosition == SwipePosition.SwipeRight) { _controller.value = 1.0; } } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final theme = Theme.of(context); return SizedBox( width: double.infinity, height: widget.height, child: Stack( key: _containerKey, children: <Widget>[ DecoratedBox( decoration: BoxDecoration( color: widget.rectColor, borderRadius: widget.borderRadius, ), child: ClipRRect( clipper: _SwipeButtonClipper( animation: _controller, borderRadius: widget.borderRadius, position: widget.initialPosition), borderRadius: widget.borderRadius, child: SizedBox.expand( child: Padding( padding: EdgeInsets.only(left: widget.height), child: widget.content, ), ), ), ), AnimatedBuilder( animation: _controller, builder: (BuildContext context, Widget child) { return Align( alignment: Alignment((_controller.value * 2.0) - 1.0, 0.0), child: child, ); }, child: GestureDetector( onHorizontalDragStart: _onDragStart, onHorizontalDragUpdate: _onDragUpdate, onHorizontalDragEnd: _onDragEnd, child: Container( key: _positionedKey, width: widget.height, height: widget.height, decoration: BoxDecoration( color: widget.thumbColor, borderRadius: widget.borderRadius, ), child: widget.thumb, ), ), ), ], ), ); } void _onDragStart(DragStartDetails details) { final pos = _positioned.globalToLocal(details.globalPosition); _start = Offset(pos.dx, 0.0); _controller.stop(canceled: true); } void _onDragUpdate(DragUpdateDetails details) { final pos = _container.globalToLocal(details.globalPosition) - _start; final extent = _container.size.width - _positioned.size.width; _controller.value = (pos.dx.clamp(0.0, extent) / extent); } void _onDragEnd(DragEndDetails details) { final extent = _container.size.width - _positioned.size.width; var fractionalVelocity = (details.primaryVelocity / extent).abs(); if (fractionalVelocity < 0.5) { fractionalVelocity = 0.5; } SwipePosition result; double acceleration, velocity; if (_controller.value > 0.5) { acceleration = 0.5; velocity = fractionalVelocity; result = SwipePosition.SwipeRight; } else { acceleration = -0.5; velocity = -fractionalVelocity; result = SwipePosition.SwipeLeft; } final simulation = _SwipeSimulation( acceleration, _controller.value, 1.0, velocity, ); _controller.animateWith(simulation).then((_) { if (widget.onChanged != null) { widget.onChanged(result); } }); } } class _SwipeSimulation extends GravitySimulation { _SwipeSimulation( double acceleration, double distance, double endDistance, double velocity) : super(acceleration, distance, endDistance, velocity); @override double x(double time) => super.x(time).clamp(0.0, 1.0); @override bool isDone(double time) { final _x = x(time).abs(); return _x <= 0.0 || _x >= 1.0; } } class _SwipeButtonClipper extends CustomClipper<RRect> { const _SwipeButtonClipper({ @required this.animation, @required this.borderRadius, this.position, }) : assert(animation != null && borderRadius != null), super(reclip: animation); final Animation<double> animation; final BorderRadius borderRadius; final SwipePosition position; @override RRect getClip(Size size) { return borderRadius.toRRect( Rect.fromLTRB( position == SwipePosition.SwipeRight ? size.width * animation.value : size.width, 0.0, position == SwipePosition.SwipeLeft ? size.width * animation.value : 0.0, size.height, ), ); } @override bool shouldReclip(_SwipeButtonClipper oldClipper) => true; }
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Perfect!