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.3
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.0.0
๐ License
MIT ยฉ Hirdaya Shrestha โ see LICENSE for details.
Made with โค๏ธ by Hirdaya Shrestha


