Skip to content

Instantly share code, notes, and snippets.

@maheshmnj
Last active March 29, 2024 13:55
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save maheshmnj/815642f5576ebef0a0747db6854c2a74 to your computer and use it in GitHub Desktop.
Save maheshmnj/815642f5576ebef0a0747db6854c2a74 to your computer and use it in GitHub Desktop.
Sample code showing dark mode transition similar to Telegram in flutter with circular transition. Blog Post: https://maheshmnj.medium.com/leveraging-clippaths-in-flutter-a5f34c795ae5
class DarkTransition extends StatefulWidget {
const DarkTransition(
{required this.childBuilder,
Key? key,
this.offset = Offset.zero,
this.themeController,
this.radius,
this.duration = const Duration(milliseconds: 400),
this.isDark = false})
: super(key: key);
/// Deinfe the widget that will be transitioned
/// int index is either 1 or 2 to identify widgets, 2 is the top widget
final Widget Function(BuildContext, int) childBuilder;
/// the current state of the theme
final bool isDark;
/// optional animation controller to controll the animation
final AnimationController? themeController;
/// centeral point of the circular transition
final Offset offset;
/// optional radius of the circle defaults to [max(height,width)*1.5])
final double? radius;
/// duration of animation defaults to 400ms
final Duration? duration;
@override
_DarkTransitionState createState() => _DarkTransitionState();
}
class _DarkTransitionState extends State<DarkTransition>
with SingleTickerProviderStateMixin {
@override
void dispose() {
_darkNotifier.dispose();
super.dispose();
}
final _darkNotifier = ValueNotifier<bool>(false);
@override
void initState() {
super.initState();
if (widget.themeController == null) {
_animationController =
AnimationController(vsync: this, duration: widget.duration);
} else {
_animationController = widget.themeController!;
}
}
double _radius(Size size) {
final maxVal = max(size.width, size.height);
return maxVal * 1.5;
}
late AnimationController _animationController;
double x = 0;
double y = 0;
bool isDark = false;
// bool isBottomThemeDark = true;
bool isDarkVisible = false;
late double radius;
Offset position = Offset.zero;
ThemeData getTheme(bool dark) {
if (dark)
return ThemeData.dark();
else
return ThemeData.light();
}
@override
void didUpdateWidget(DarkTransition oldWidget) {
super.didUpdateWidget(oldWidget);
_darkNotifier.value = widget.isDark;
if (widget.isDark != oldWidget.isDark) {
if (isDark) {
_animationController.reverse();
_darkNotifier.value = false;
} else {
_animationController.reset();
_animationController.forward();
_darkNotifier.value = true;
}
position = widget.offset;
}
if (widget.radius != oldWidget.radius) {
_updateRadius();
}
if (widget.duration != oldWidget.duration) {
_animationController.duration = widget.duration;
}
}
@override
void didChangeDependencies() {
// TODO: implement didChangeDependencies
super.didChangeDependencies();
_updateRadius();
}
void _updateRadius() {
final size = MediaQuery.of(context).size;
if (widget.radius == null)
radius = _radius(size);
else
radius = widget.radius!;
}
@override
Widget build(BuildContext context) {
isDark = _darkNotifier.value;
Widget _body(int index) {
return ValueListenableBuilder<bool>(
valueListenable: _darkNotifier,
builder: (BuildContext context, bool isDark, Widget? child) {
return Theme(
data: index == 2
? getTheme(!isDarkVisible)
: getTheme(isDarkVisible),
child: widget.childBuilder(context, index));
});
}
return AnimatedBuilder(
animation: _animationController,
builder: (BuildContext context, Widget? child) {
return Stack(
children: [
_body(1),
ClipPath(
clipper: CircularClipper(
_animationController.value * radius, position),
child: _body(2)),
],
);
});
}
}
class CircularClipper extends CustomClipper<Path> {
const CircularClipper(this.radius, this.center);
final double radius;
final Offset center;
@override
Path getClip(Size size) {
final Path path = Path();
path.addOval(Rect.fromCircle(radius: radius, center: center));
return path;
}
@override
bool shouldReclip(covariant CustomClipper<Path> oldClipper) {
return true;
}
}
@maheshmnj
Copy link
Author

@bahaa599599 You can try using this class https://stackoverflow.com/a/71135052/8253662 to save and retrieve theme from your shared preferences

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