Skip to content

Instantly share code, notes, and snippets.

@rena2019
Created October 10, 2025 12:56
Show Gist options
  • Select an option

  • Save rena2019/29f10688074bcc2bd431bb0ba45b1afe to your computer and use it in GitHub Desktop.

Select an option

Save rena2019/29f10688074bcc2bd431bb0ba45b1afe to your computer and use it in GitHub Desktop.
Flutter OverlayEntry with ValueNotifier
//Flutter OverlayEntry with ValueNotifier
import 'dart:async';
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Progress Overlay Demo',
theme: ThemeData(primarySwatch: Colors.blue),
home: const OverlayDemoPage(),
);
}
}
class OverlayDemoPage extends StatefulWidget {
const OverlayDemoPage({super.key});
@override
State<OverlayDemoPage> createState() => _OverlayDemoPageState();
}
class _OverlayDemoPageState extends State<OverlayDemoPage> {
ProgressOverlay? _overlay;
StreamSubscription<int>? _subscription;
void _startOverlay() {
_subscription?.cancel();
_overlay?.remove();
_overlay = ProgressOverlay(
context,
titleText: 'Ladevorgang gestartet...',
statusText: 'Bereite Daten vor...',
onClose: () {
_subscription?.cancel();
},
);
_overlay!.show();
Future.microtask(() {
final stream =
Stream<int>.periodic(const Duration(milliseconds: 50), (x) => x).take(101);
_subscription = stream.listen((value) {
_overlay?.updateProgress(value);
if (value == 20) _overlay?.updateStatus('Lade Module...');
if (value == 60) _overlay?.updateTitle('Fast fertig...');
if (value == 80) _overlay?.updateStatus('Abschlussphase...');
if (value >= 100) {
Future.delayed(const Duration(seconds: 1), () {
_overlay?.remove();
});
}
});
});
}
@override
void dispose() {
_subscription?.cancel();
_overlay?.remove();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Progress Overlay Demo')),
body: Center(
child: ElevatedButton(
onPressed: _startOverlay,
child: const Text('Overlay starten'),
),
),
);
}
}
/// --------------------------------------------
/// Wiederverwendbares Overlay mit Close-Button
/// --------------------------------------------
class ProgressOverlay {
final BuildContext context;
final VoidCallback? onClose;
OverlayEntry? _entry;
bool _inserted = false;
final ValueNotifier<int> _progressNotifier = ValueNotifier(0);
final ValueNotifier<String> _titleNotifier = ValueNotifier('');
final ValueNotifier<String> _statusNotifier = ValueNotifier('');
ProgressOverlay(
this.context, {
String titleText = 'Bitte warten…',
String statusText = '',
int initialProgress = 0,
this.onClose,
}) {
_titleNotifier.value = titleText;
_statusNotifier.value = statusText;
_progressNotifier.value = initialProgress;
}
void show() {
remove();
_entry = OverlayEntry(builder: (_) {
return Stack(
children: [
// halbtransparenter Hintergrund
Positioned.fill(
child: GestureDetector(
onTap: () {}, // absorbiert Klicks auf Hintergrund
child: Container(color: Colors.black.withOpacity(0.5)),
),
),
// zentrierter Card-Container
Center(
child: ValueListenableBuilder<String>(
valueListenable: _titleNotifier,
builder: (_, title, __) {
return ValueListenableBuilder<String>(
valueListenable: _statusNotifier,
builder: (_, status, __) {
return ValueListenableBuilder<int>(
valueListenable: _progressNotifier,
builder: (_, progress, __) {
return Container(
width: 300,
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: const [
BoxShadow(blurRadius: 12, color: Colors.black26)
],
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// Titelzeile mit Close-Button
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
title,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
textAlign: TextAlign.center,
),
),
IconButton(
icon: const Icon(Icons.close),
onPressed: () {
onClose?.call();
remove();
},
),
],
),
const SizedBox(height: 12),
const CircularProgressIndicator(),
const SizedBox(height: 16),
Text('Progress: $progress%',
style: const TextStyle(fontSize: 16)),
if (status.isNotEmpty) ...[
const SizedBox(height: 8),
Text(
status,
style: const TextStyle(
fontSize: 14,
color: Colors.grey,
),
textAlign: TextAlign.center,
),
]
],
),
);
},
);
},
);
},
),
),
],
);
});
// 📍 Einfügen nach aktuellem Frame (sofort sichtbar)
Future.microtask(() {
final overlay = Overlay.of(context, rootOverlay: true);
if (overlay != null && _entry != null) {
overlay.insert(_entry!);
_inserted = true;
}
});
}
void updateProgress(int value) => _progressNotifier.value = value;
void updateTitle(String title) => _titleNotifier.value = title;
void updateStatus(String status) => _statusNotifier.value = status;
void remove() {
if (_inserted && _entry != null) {
_entry!.remove();
_inserted = false;
}
_entry = null;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment