circular_theme_reveal 1.0.2
circular_theme_reveal: ^1.0.2 copied to clipboard
A beautiful Telegram-style circular reveal animation for theme transitions in Flutter. Smooth, performant, and zero dependencies.
Circular Theme Reveal π¨ #
A beautiful Telegram-style circular reveal animation for theme transitions in Flutter

β¨ Smooth β’ π Bidirectional β’ β‘ Performant β’ π― Zero Dependencies
Features β¨ #
- π Smooth circular reveal animation - Just like Telegram
- π Bidirectional transition - Expands when going dark, contracts when going light
- π― Center-based animation - Starts from any point (e.g., your theme toggle button)
- β‘ High performance - Uses efficient snapshot + blend mode technique
- π¨ Customizable - Adjust duration, curves, and more
- π¦ Zero dependencies - Only uses Flutter SDK
Installation #
Add this to your pubspec.yaml:
dependencies:
circular_theme_reveal:
Or install it from the command line:
flutter pub add circular_theme_reveal
Usage #
1. Wrap your MaterialApp #
Wrap your MaterialApp (or the root widget) with CircularThemeRevealOverlay:
import 'package:circular_theme_reveal/circular_theme_reveal.dart';
class MyApp extends StatefulWidget {
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
ThemeMode _themeMode = ThemeMode.light;
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Circular Theme Reveal Demo',
theme: ThemeData.light(),
darkTheme: ThemeData.dark(),
themeMode: _themeMode,
themeAnimationDuration: Duration.zero, // Disable default animation
builder: (context, child) {
return CircularThemeRevealOverlay(
child: child ?? SizedBox.shrink(),
);
},
home: HomePage(
onThemeToggle: () {
setState(() {
_themeMode = _themeMode == ThemeMode.light
? ThemeMode.dark
: ThemeMode.light;
});
},
isDark: _themeMode == ThemeMode.dark,
),
);
}
}
2. Trigger the animation #
In your theme toggle button (simplified with helper method):
IconButton(
icon: Icon(isDark ? Icons.light_mode : Icons.dark_mode),
onPressed: () async {
// Get button center position using helper
final center = CircularThemeRevealOverlay.getCenterFromContext(context);
// Get overlay state
final overlay = CircularThemeRevealOverlay.of(context);
if (overlay != null) {
// Start circular reveal animation
await overlay.startTransition(
center: center,
reverse: isDark, // true when going from dark to light
onThemeChange: () {
// Change your theme here
setState(() {
isDark = !isDark;
});
},
);
} else {
// Fallback if overlay not found
setState(() {
isDark = !isDark;
});
}
},
)
Works with Any State Management #
The package is agnostic to your state management solution. Works with:
Provider / Riverpod
onThemeChange: () {
ref.read(themeProvider.notifier).toggle();
}
Bloc
onThemeChange: () {
context.read<ThemeBloc>().add(ToggleTheme());
}
GetX
onThemeChange: () {
Get.find<ThemeController>().toggleTheme();
}
Complete Example #
See the /example folder for a complete working example with:
- State management
- Theme persistence
- Multiple pages
- Custom theme toggle button
How it works π§ #
The animation uses a clever technique inspired by Telegram:
- Captures a snapshot of the current screen using
RepaintBoundary - Overlays the snapshot on top of your app
- Changes the theme underneath via your
onThemeChangecallback - Animates a circular mask using
BlendMode.dstOutthat reveals the new theme - Fades out gracefully at the end with progressive opacity + glow effect
The animation direction adapts automatically:
- π β π Expands from the button (circle grows, revealing dark theme)
- π β π Contracts back to the button (circle shrinks, revealing light theme)
The contraction uses special techniques to eliminate visual glitches:
- Fade based on actual radius (not time) for natural dissipation
- Subtle glow effect in the final 10% for smooth termination
easeInExpocurve for cinematic acceleration- Precise timing to remove overlay only when fully invisible
Customization #
Current defaults: #
- Duration: 600ms
- Expansion curve:
Curves.easeInOut - Contraction curve:
Curves.easeInExpo(more cinematic) - Fade threshold: Last 15% of radius
- Glow effect: Last 10% of radius
Custom themes support: #
The package works with any theme colors because it captures actual rendered pixels:
MaterialApp(
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.purple, // β
Your custom colors
brightness: Brightness.light,
),
),
darkTheme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.orange, // β
Your custom dark colors
brightness: Brightness.dark,
),
),
// ... rest of setup
)
Performance Tips #
- β
Uses
RepaintBoundaryfor efficient snapshot capture - β Disposes images properly to prevent memory leaks
- β
Uses
BlendMode.dstOutfor hardware-accelerated masking - β Minimal rebuilds during animation
Platform Support #
| Platform | Support |
|---|---|
| Android | β |
| iOS | β |
| Web | β |
| macOS | β |
| Windows | β |
| Linux | β |
Contributing #
Contributions are welcome! Please feel free to submit a Pull Request.
License #
MIT License - see LICENSE file for details
Credits #
Inspired by Telegram's beautiful theme transition animation.
Author #
Created with β€οΈ by marcospereira