Instantly share code, notes, and snippets.
Last active
July 31, 2024 08:14
-
Star
(0)
0
You must be signed in to star a gist -
Fork
(0)
0
You must be signed in to fork a gist
-
Save riscait/0faacafc83c8544605eca8e0f95ec659 to your computer and use it in GitHub Desktop.
FavoriteButton with Animations
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(const MyApp()); | |
class MyApp extends StatelessWidget { | |
const MyApp({super.key}); | |
@override | |
Widget build(BuildContext context) { | |
return MaterialApp( | |
title: 'FavoriteButton with Animations', | |
home: const MyHomePage(title: 'FavoriteButton with Animations'), | |
); | |
} | |
} | |
class MyHomePage extends StatefulWidget { | |
final String title; | |
const MyHomePage({ | |
super.key, | |
required this.title, | |
}); | |
@override | |
State<MyHomePage> createState() => _MyHomePageState(); | |
} | |
class _MyHomePageState extends State<MyHomePage> { | |
@override | |
Widget build(BuildContext context) { | |
return Scaffold( | |
appBar: AppBar( | |
title: Text(widget.title), | |
), | |
body: Center( | |
child: FavoriteButton( | |
onPressed: (){}, | |
), | |
), | |
); | |
} | |
} | |
class FavoriteButton extends StatefulWidget { | |
const FavoriteButton({ | |
required this.onPressed, | |
this.backgroundSize = 80, | |
this.iconSize = 26, | |
this.backgroundColor = const Color(0xFF303030), | |
this.splashColor = const Color(0xFFE24459), | |
this.iconColor = const Color(0xFFFFFFFF), | |
this.bounceDuration = const Duration(milliseconds: 600), | |
this.fillDuration = const Duration(milliseconds: 400), | |
this.isLarge = false, | |
super.key, | |
}); | |
final VoidCallback? onPressed; | |
final Color backgroundColor; | |
final Color splashColor; | |
final Color iconColor; | |
final double backgroundSize; | |
final double iconSize; | |
final bool isLarge; | |
final Duration bounceDuration; | |
final Duration fillDuration; | |
@override | |
State<FavoriteButton> createState() => _FavoriteButtonState(); | |
} | |
class _FavoriteButtonState extends State<FavoriteButton> | |
with SingleTickerProviderStateMixin { | |
bool _isFavorite = false; | |
late AnimationController _iconController; | |
late Animation<double> _iconWidthAnimation; | |
late Animation<double> _iconHeightAnimation; | |
@override | |
void initState() { | |
super.initState(); | |
_iconController = AnimationController( | |
duration: widget.bounceDuration, | |
vsync: this, | |
); | |
_iconWidthAnimation = TweenSequence<double>([ | |
TweenSequenceItem(tween: Tween(begin: 1, end: 0.85), weight: 1), | |
TweenSequenceItem(tween: Tween(begin: 0.85, end: 1.15), weight: 1), | |
TweenSequenceItem(tween: Tween(begin: 1.15, end: 0.85), weight: 1), | |
TweenSequenceItem(tween: Tween(begin: 0.85, end: 1), weight: 1), | |
]).animate( | |
CurvedAnimation( | |
parent: _iconController, | |
curve: Curves.ease, | |
), | |
); | |
_iconHeightAnimation = TweenSequence<double>([ | |
TweenSequenceItem(tween: Tween(begin: 1, end: 1.15), weight: 1), | |
TweenSequenceItem(tween: Tween(begin: 1.15, end: 0.85), weight: 1), | |
TweenSequenceItem(tween: Tween(begin: 0.85, end: 1.15), weight: 1), | |
TweenSequenceItem(tween: Tween(begin: 1.15, end: 1), weight: 1), | |
]).animate( | |
CurvedAnimation( | |
parent: _iconController, | |
curve: Curves.ease, | |
), | |
); | |
} | |
@override | |
void dispose() { | |
_iconController.dispose(); | |
super.dispose(); | |
} | |
void _onTap() { | |
widget.onPressed?.call(); | |
_toggleFavorite(); | |
} | |
void _toggleFavorite() { | |
setState(() { | |
_isFavorite = !_isFavorite; | |
if (_isFavorite) { | |
_iconController.forward(from: 0); | |
} else { | |
_iconController.reverse(); | |
} | |
}); | |
} | |
@override | |
Widget build(BuildContext context) { | |
return GestureDetector( | |
onTap: _onTap, | |
child: AnimatedScale( | |
duration: const Duration(milliseconds: 300), | |
scale: widget.isLarge ? 1.5 : 1.0, | |
child: Stack( | |
alignment: Alignment.center, | |
children: [ | |
// Grey background. | |
Container( | |
width: widget.backgroundSize, | |
height: widget.backgroundSize, | |
decoration: ShapeDecoration( | |
shape: const OvalBorder(), | |
color: widget.backgroundColor, | |
), | |
), | |
// Pink background. | |
AnimatedScale( | |
scale: _isFavorite ? 1.0 : 0, | |
duration: widget.fillDuration, | |
curve: Curves.ease, | |
child: Container( | |
width: widget.backgroundSize, | |
height: widget.backgroundSize, | |
decoration: ShapeDecoration( | |
shape: const OvalBorder(), | |
color: widget.splashColor, | |
), | |
), | |
), | |
// Heart icon. | |
AnimatedBuilder( | |
animation: _iconController, | |
child: Icon( | |
_isFavorite ? Icons.favorite : Icons.favorite_border, | |
color: Colors.white, | |
size: widget.iconSize, | |
), | |
builder: (context, child) { | |
return Transform( | |
transform: Matrix4.identity() | |
..scale( | |
_iconWidthAnimation.value, | |
_iconHeightAnimation.value, | |
), | |
alignment: Alignment.center, | |
child: child, | |
); | |
}, | |
), | |
], | |
), | |
), | |
); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment