Created
October 10, 2025 12:56
-
-
Save rena2019/29f10688074bcc2bd431bb0ba45b1afe to your computer and use it in GitHub Desktop.
Flutter OverlayEntry with ValueNotifier
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| //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