Custom Tutorial Overlay
A highly customizable tutorial overlay package for Flutter that provides step-by-step walkthroughs with spotlight effects, blur/dim backgrounds, and comprehensive navigation controls.
Features
✅ Fully Customizable UI: Customize tooltip content, shapes, colors, padding, and animations
✅ Spotlight Effects: Circle, rounded rectangle, and rectangle shapes with customizable borders
✅ Background Effects: Choose between dim overlay or blur effects
✅ Step Navigation: "Next", "Previous", and "Skip" buttons with full customization
✅ Async Step Progression: Wait for user actions before allowing progression
✅ Widget Targeting: Use GlobalKeys to highlight specific widgets
✅ Dual API: Both declarative and imperative approaches
✅ Theme Adaptation: Automatic light/dark mode support
✅ Animated Transitions: Smooth animations between steps
✅ Step Indicators: Dots, progress bar, or number indicators
✅ Cross-Platform: Supports mobile and web
✅ Null Safety: Full null safety support
Installation
Add this to your pubspec.yaml:
dependencies:
custom_tutorial_overlay: ^1.0.0
Then run:
flutter pub get
Quick Start
Imperative Usage
import 'package:flutter/material.dart';
import 'package:custom_tutorial_overlay/custom_tutorial_overlay.dart';
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
late TutorialController _tutorialController;
final GlobalKey _buttonKey = GlobalKey();
@override
void initState() {
super.initState();
// Create tutorial controller
_tutorialController = TutorialController(
config: const TutorialConfig(
showStepIndicator: true,
stepIndicatorStyle: StepIndicatorStyle.dots,
),
onComplete: () => print('Tutorial completed!'),
);
// Add tutorial steps
_tutorialController.addSteps([
TutorialStep(
targetKey: _buttonKey,
content: const Column(
mainAxisSize: MainAxisSize.min,
children: [
Text('Welcome!', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
SizedBox(height: 8),
Text('This button does something important.'),
],
),
shape: SpotlightShape.circle,
borderColor: Colors.blue,
),
]);
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: TutorialOverlay(
controller: _tutorialController,
child: Scaffold(
appBar: AppBar(
title: const Text('Tutorial Demo'),
actions: [
IconButton(
icon: const Icon(Icons.help),
onPressed: () => _tutorialController.start(),
),
],
),
body: Center(
child: ElevatedButton(
key: _buttonKey,
onPressed: () => print('Button pressed!'),
child: const Text('Important Button'),
),
),
),
),
);
}
}
Declarative Usage
import 'package:flutter/material.dart';
import 'package:custom_tutorial_overlay/custom_tutorial_overlay.dart';
class DeclarativeExample extends StatelessWidget {
final GlobalKey _targetKey = GlobalKey();
@override
Widget build(BuildContext context) {
return TutorialBuilder(
config: const TutorialConfig(
showStepIndicator: true,
stepIndicatorStyle: StepIndicatorStyle.progress,
),
autoStart: false,
steps: [
TutorialStep(
targetKey: _targetKey,
content: const Text('This is a declarative tutorial step!'),
shape: SpotlightShape.roundedRectangle,
borderColor: Colors.green,
),
],
onComplete: () => print('Declarative tutorial completed!'),
child: Scaffold(
appBar: AppBar(
title: const Text('Declarative Tutorial'),
actions: [
IconButton(
onPressed: () => context.startTutorial(),
icon: const Icon(Icons.play_arrow),
),
],
),
body: Center(
child: Container(
key: _targetKey,
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.green.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
),
child: const Text('Target Widget'),
),
),
),
);
}
}
API Reference
TutorialStep
The TutorialStep class defines individual steps in your tutorial:
TutorialStep(
// Widget targeting
targetKey: GlobalKey(), // Target widget using GlobalKey
customPosition: Offset(100, 200), // Custom position if no targetKey
customSize: Size(150, 100), // Custom size if no targetKey
// Content
content: Widget, // Content to display in tooltip
// Spotlight appearance
shape: SpotlightShape.circle, // circle, roundedRectangle, rectangle
padding: EdgeInsets.all(8), // Padding around target widget
borderRadius: 8.0, // Corner radius for rounded rectangle
borderColor: Colors.blue, // Border color
borderWidth: 2.0, // Border width
// Background
overlayColor: Colors.black54, // Overlay color
useBlurEffect: false, // Use blur instead of dim
blurIntensity: 5.0, // Blur intensity
// Tooltip appearance
tooltipBackgroundColor: Colors.white,
tooltipTextColor: Colors.black,
tooltipPosition: TooltipPosition.auto,
tooltipOffset: Offset.zero,
tooltipMaxWidth: 300.0,
// Navigation
showNavigationButtons: true,
customNextButton: Widget,
customPreviousButton: Widget,
customSkipButton: Widget,
// Behavior
waitForUserAction: false, // Wait for user interaction
canProceed: () => bool, // Custom condition for progression
animationDuration: Duration(milliseconds: 300),
// Callbacks
onShow: () => print('Step shown'),
onHide: () => print('Step hidden'),
)
TutorialConfig
Configure global tutorial settings:
TutorialConfig(
// Default appearance
defaultOverlayColor: Colors.black54,
defaultTooltipBackgroundColor: Colors.white,
defaultTooltipTextColor: Colors.black,
defaultBorderColor: Colors.blue,
defaultBorderWidth: 2.0,
defaultAnimationDuration: Duration(milliseconds: 300),
defaultShape: SpotlightShape.circle,
defaultPadding: EdgeInsets.all(8),
defaultBorderRadius: 8.0,
// Step indicator
showStepIndicator: true,
stepIndicatorPosition: StepIndicatorPosition.bottom,
stepIndicatorStyle: StepIndicatorStyle.dots,
stepIndicatorBuilder: (currentStep, totalSteps) => Widget,
// Interaction
enableSwipeGestures: false,
allowTapOutsideToClose: false,
barrierDismissible: false,
// Buttons
nextButtonColor: Colors.blue,
previousButtonColor: Colors.grey,
skipButtonColor: Colors.grey,
nextButtonTextStyle: TextStyle(),
previousButtonTextStyle: TextStyle(),
skipButtonTextStyle: TextStyle(),
// Auto-play
autoPlayDelay: Duration(seconds: 3),
)
TutorialController
Control tutorial programmatically:
final controller = TutorialController();
// Step management
controller.addStep(step);
controller.addSteps([step1, step2, step3]);
controller.insertStep(1, step);
controller.removeStep(1);
controller.updateStep(1, newStep);
controller.clearSteps();
// Tutorial control
await controller.start(startIndex: 0);
controller.stop();
controller.pause();
controller.resume();
// Navigation
await controller.next();
await controller.previous();
await controller.goToStep(2);
controller.skip();
controller.complete();
// State
bool isActive = controller.isActive;
bool isPaused = controller.isPaused;
int currentStep = controller.currentStepIndex;
int totalSteps = controller.totalSteps;
bool hasNext = controller.hasNextStep;
bool hasPrevious = controller.hasPreviousStep;
// For waitForUserAction steps
controller.allowProgression();
Context Extensions
When using TutorialBuilder, you get convenient context extensions:
// Start tutorial
context.startTutorial(startIndex: 0);
// Navigation
context.nextTutorialStep();
context.previousTutorialStep();
context.goToTutorialStep(2);
// Control
context.stopTutorial();
context.skipTutorial();
context.allowTutorialProgression();
// Get controller
TutorialController? controller = context.tutorialController;
Advanced Features
Waiting for User Actions
Create interactive tutorials that wait for user input:
TutorialStep(
targetKey: _cardKey,
content: const Text('Tap this card to continue'),
waitForUserAction: true,
canProceed: () => _userHasTappedCard,
)
Custom Step Indicators
Provide your own step indicator widget:
TutorialConfig(
stepIndicatorBuilder: (currentStep, totalSteps) {
return Container(
padding: EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(16),
),
child: Text('$currentStep/$totalSteps'),
);
},
)
Custom Positioning
Position tooltips without targeting specific widgets:
TutorialStep(
customPosition: Offset(100, 200),
customSize: Size(150, 100),
content: Text('Custom positioned tooltip'),
)
Theme Adaptation
The package automatically adapts to your app's theme:
// Light theme
ThemeData.light()
// Dark theme
ThemeData.dark()
// Custom colors will be automatically adjusted
Blur Effects
Use blur effects instead of dim overlay:
TutorialStep(
targetKey: _key,
content: Text('Blurred background'),
useBlurEffect: true,
blurIntensity: 5.0,
)
Testing
The package includes comprehensive test coverage. Run tests with:
flutter test
Contributing
Contributions are welcome! Please read our contributing guidelines and submit pull requests to our repository.
License
This project is licensed under the MIT License - see the LICENSE file for details.
Changelog
1.0.0
- Initial release
- Full tutorial overlay functionality
- Customizable spotlight effects
- Step navigation and indicators
- Async step progression
- Theme adaptation
- Cross-platform support
Libraries
- custom_tutorial_overlay
- A customizable tutorial overlay package for Flutter