theme_toggle_animation 1.0.0
theme_toggle_animation: ^1.0.0 copied to clipboard
Smooth, animated theme switching for Flutter. Circle, line, and custom GIF mask animations with configurable directions, blur effects, and full customization. Zero dependencies beyond Flutter SDK.
Theme Toggle Animation #
Smooth, animated theme switching for Flutter.
Circle reveals, scanning lines, and custom GIF mask animations — zero dependencies, fully customizable.
Get Started · Examples · API
Demo #
| Circle | Line | Custom Mask |
![]() |
![]() |
![]() |
| Expanding circle reveal from any corner or tap position. | Scanning line sweep across the screen. | Animated GIF mask reveal effect. |
Note
Demo GIFs looks a bit pixalated due to the conversion from mp4 recording. It looks better originally in the UI when using this theme toggle animation.
✨ Features #
| Feature | Description |
|---|---|
| 🎯 3 animation types | circle, line, customMask |
| 📍 5 circle directions | 4 corners + from tap position |
| 📏 10 line directions | Cardinal, diagonal, and from tap position |
| 🖼️ GIF mask animation | Animated GIFs as reveal masks |
| 🔀 Custom clip paths | Bring your own Path function |
| 🌫️ Blur effects | Configurable blur on animation edges |
| 🎛️ Configurable | Duration, curve, enabled flag |
| 🔔 Lifecycle callbacks | onAnimationStart / onAnimationEnd |
| ⚡ Optimized | RepaintBoundary, content caching, GPU clips |
| 🧩 State management agnostic | Provider, Riverpod, Bloc, GetX — all work |
| 📦 Zero dependencies | Only Flutter SDK |
📦 Installation #
Add to your pubspec.yaml:
dependencies:
theme_toggle_animation: ^1.0.0
Then run:
flutter pub get
🚀 Quick Start #
class MyApp extends StatefulWidget {
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
bool _isDark = false;
@override
Widget build(BuildContext context) {
return ThemeToggleAnimation(
currentTheme: _isDark ? ThemeData.dark() : ThemeData.light(),
onToggle: () => setState(() => _isDark = !_isDark),
builder: (context, toggle) => MaterialApp(
home: Scaffold(
appBar: AppBar(
actions: [
IconButton(
icon: Icon(_isDark ? Icons.light_mode : Icons.dark_mode),
onPressed: toggle,
),
],
),
body: const Center(child: Text('Hello World')),
),
),
child: const SizedBox.expand(),
);
}
}
🎬 Examples #
Circle #
Expanding circle reveal from any corner or the tap position:
ThemeToggleAnimation(
currentTheme: myTheme,
animationType: ThemeAnimationType.circle,
circleDirection: CircleAnimationDirection.fromWidget,
blurAmount: 4.0,
onToggle: () => setState(() => _isDark = !_isDark),
builder: (context, toggle) => MyToggleWidget(onTap: toggle),
child: MyApp(),
)
| Direction | Description |
|---|---|
ftl |
From top-left |
ftr |
From top-right |
fbl |
From bottom-left |
fbr |
From bottom-right |
fromWidget |
From tap position |
Line #
Scanning line sweep in 10 directions:
ThemeToggleAnimation(
currentTheme: myTheme,
animationType: ThemeAnimationType.line,
lineDirection: LineAnimationDirection.ltr,
blurAmount: 4.0,
onToggle: () => setState(() => _isDark = !_isDark),
builder: (context, toggle) => MyToggleWidget(onTap: toggle),
child: MyApp(),
)
| Direction | Description |
|---|---|
ltr |
Left → right |
rtl |
Right → left |
ttb |
Top → bottom |
btt |
Bottom → top |
fromWidgetHorizontal |
From tap, horizontal |
fromWidgetVertical |
From tap, vertical |
ftl |
Diagonal top-left |
ftr |
Diagonal top-right |
fbl |
Diagonal bottom-left |
fbr |
Diagonal bottom-right |
Custom Mask #
Reveal the new theme through an animated GIF:
ThemeToggleAnimation(
currentTheme: myTheme,
animationType: ThemeAnimationType.customMask,
customMaskImage: AssetImage('assets/mask.gif'),
customMaskOffset: Offset(50, 100),
customMaskSize: Size(150, 150),
duration: Duration(milliseconds: 2000),
onToggle: () => setState(() => _isDark = !_isDark),
builder: (context, toggle) => MyToggleWidget(onTap: toggle),
child: MyApp(),
)
💡 GIFs with transparent backgrounds work best — opaque pixels reveal the new theme.
🔌 State Management #
Works with any state management solution — just call onToggle to flip your state.
Provider / Riverpod
ThemeToggleAnimation(
currentTheme: ref.watch(themeProvider),
onToggle: () => ref.read(themeProvider.notifier).toggle(),
builder: (context, toggle) => MyToggleWidget(onTap: toggle),
child: MyApp(),
)
Bloc
ThemeToggleAnimation(
currentTheme: context.read<ThemeBloc>().state.themeData,
onToggle: () => context.read<ThemeBloc>().add(ToggleTheme()),
builder: (context, toggle) => MyToggleWidget(onTap: toggle),
child: MyApp(),
)
GetX
ThemeToggleAnimation(
currentTheme: Get.isDarkMode ? darkTheme : lightTheme,
onToggle: () => Get.isDarkMode
? Get.changeTheme(lightTheme)
: Get.changeTheme(darkTheme),
builder: (context, toggle) => MyToggleWidget(onTap: toggle),
child: MyApp(),
)
setState
ThemeToggleAnimation(
currentTheme: _isDark ? darkTheme : lightTheme,
onToggle: () => setState(() => _isDark = !_isDark),
builder: (context, toggle) => MyToggleWidget(onTap: toggle),
child: MyApp(),
)
📖 API Reference #
ThemeToggleAnimation #
| Parameter | Type | Default | Description |
|---|---|---|---|
currentTheme |
ThemeData |
required | The current theme to apply |
builder |
Widget Function(BuildContext, VoidCallback) |
required | Builder with toggle callback |
child |
Widget |
required | Child widget tree |
onToggle |
VoidCallback? |
null |
Called when toggle is tapped |
animationType |
ThemeAnimationType |
circle |
Animation effect |
duration |
Duration |
750ms |
Animation duration |
curve |
Curve |
easeInOut |
Animation curve |
enabled |
bool |
true |
Enable / disable animation |
onAnimationStart |
VoidCallback? |
null |
Fired when animation starts |
onAnimationEnd |
VoidCallback? |
null |
Fired when animation completes |
circleDirection |
CircleAnimationDirection |
ftl |
Circle origin |
lineDirection |
LineAnimationDirection |
ltr |
Line sweep direction |
blurAmount |
double |
0.0 |
Blur intensity (circle / line only) |
clipper |
Path Function(Size, Offset, double)? |
null |
Custom clip path |
customMaskImage |
ImageProvider? |
null |
GIF / image for mask |
customMaskOffset |
Offset? |
center | Mask position |
customMaskSize |
Size? |
200 × 200 |
Mask size during play phase |
Enums #
ThemeAnimationType — circle · line · customMask
CircleAnimationDirection — ftl · ftr · fbl · fbr · fromWidget
LineAnimationDirection — ltr · rtl · ttb · btt · fromWidgetHorizontal · fromWidgetVertical · ftl · ftr · fbl · fbr
📋 Requirements #
- Flutter
>=3.0.0 - Dart SDK
^3.13.2
📄 License #
MIT © Hirdaya Shrestha — see LICENSE for details.
Made with ❤️ by Hirdaya Shrestha


