diff --git a/examples/api/lib/widgets/overlay/overlay.0.dart b/examples/api/lib/widgets/overlay/overlay.0.dart new file mode 100644 index 00000000000..b90d0f0928b --- /dev/null +++ b/examples/api/lib/widgets/overlay/overlay.0.dart @@ -0,0 +1,243 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Flutter code sample for Overlay + +import 'package:flutter/material.dart'; + +void main() => runApp(const OverlayApp()); + +class OverlayApp extends StatelessWidget { + const OverlayApp({super.key}); + + @override + Widget build(BuildContext context) { + return const MaterialApp( + home: OverlayExample(), + ); + } +} + +class OverlayExample extends StatefulWidget { + const OverlayExample({super.key}); + + @override + State createState() => _OverlayExampleState(); +} + +class _OverlayExampleState extends State { + OverlayEntry? overlayEntry; + int currentPageIndex = 0; + + void createHighlightOverlay({ + required AlignmentDirectional alignment, + required Color borderColor, + }) { + // Remove the existing OverlayEntry. + removeHighlightOverlay(); + + assert(overlayEntry == null); + + overlayEntry = OverlayEntry( + // Create a new OverlayEntry. + builder: (BuildContext context) { + // Align is used to position the highlight overlay + // relative to the NavigationBar destination. + return SafeArea( + child: Align( + alignment: alignment, + heightFactor: 1.0, + child: DefaultTextStyle( + style: const TextStyle( + color: Colors.blue, + fontWeight: FontWeight.bold, + fontSize: 14.0, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Text('Tap here for'), + Builder(builder: (BuildContext context) { + switch (currentPageIndex) { + case 0: + return Column( + children: const [ + Text( + 'Explore page', + style: TextStyle( + color: Colors.red, + ), + ), + Icon( + Icons.arrow_downward, + color: Colors.red + ), + ], + ); + case 1: + return Column( + children: const [ + Text( + 'Commute page', + style: TextStyle( + color: Colors.green, + ), + ), + Icon( + Icons.arrow_downward, + color: Colors.green + ), + ], + ); + case 2: + return Column( + children: const [ + Text( + 'Saved page', + style: TextStyle( + color: Colors.orange, + ), + ), + Icon( + Icons.arrow_downward, + color: Colors.orange + ), + ], + ); + default: + return const Text('No page selected.'); + } + }), + SizedBox( + width: MediaQuery.of(context).size.width / 3, + height: 80.0, + child: Center( + child: Container( + decoration: BoxDecoration( + border: Border.all( + color: borderColor, + width: 4.0, + ), + ), + ), + ), + ), + ], + ), + ), + ), + ); + }, + ); + + // Add the OverlayEntry to the Overlay. + Overlay.of(context)!.insert(overlayEntry!); + } + + // Remove the OverlayEntry. + void removeHighlightOverlay() { + overlayEntry?.remove(); + overlayEntry = null; + } + + @override + void dispose() { + // Make sure to remove OverlayEntry when the widget is disposed. + removeHighlightOverlay(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Overlay Sample'), + ), + bottomNavigationBar: NavigationBar( + selectedIndex: currentPageIndex, + destinations: const [ + NavigationDestination( + icon: Icon(Icons.explore), + label: 'Explore', + ), + NavigationDestination( + icon: Icon(Icons.commute), + label: 'Commute', + ), + NavigationDestination( + selectedIcon: Icon(Icons.bookmark), + icon: Icon(Icons.bookmark_border), + label: 'Saved', + ), + ], + ), + body: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'Use Overlay to highlight a NavigationBar destination', + style: Theme.of(context).textTheme.bodyMedium, + ), + const SizedBox(height: 20.0), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + // This creates a highlight Overlay for + // the Explore item. + ElevatedButton( + onPressed: () { + setState(() { + currentPageIndex = 0; + }); + createHighlightOverlay( + alignment: AlignmentDirectional.bottomStart, + borderColor: Colors.red, + ); + }, + child: const Text('Explore'), + ), + const SizedBox(width: 20.0), + // This creates a highlight Overlay for + // the Commute item. + ElevatedButton( + onPressed: () { + setState(() { + currentPageIndex = 1; + }); + createHighlightOverlay( + alignment: AlignmentDirectional.bottomCenter, + borderColor: Colors.green, + ); + }, + child: const Text('Commute'), + ), + const SizedBox(width: 20.0), + // This creates a highlight Overlay for + // the Saved item. + ElevatedButton( + onPressed: () { + setState(() { + currentPageIndex = 2; + }); + createHighlightOverlay( + alignment: AlignmentDirectional.bottomEnd, + borderColor: Colors.orange, + ); + }, + child: const Text('Saved'), + ), + ], + ), + const SizedBox(height: 10.0), + ElevatedButton( + onPressed: () { + removeHighlightOverlay(); + }, + child: const Text('Remove Overlay'), + ), + ], + ), + ); + } +} diff --git a/examples/api/test/widgets/overlay/overlay.0_test.dart b/examples/api/test/widgets/overlay/overlay.0_test.dart new file mode 100644 index 00000000000..c626fe17fee --- /dev/null +++ b/examples/api/test/widgets/overlay/overlay.0_test.dart @@ -0,0 +1,41 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; +import 'package:flutter_api_samples/widgets/overlay/overlay.0.dart' as example; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets('Can use Overlay to highlight NavigationBar destination', (WidgetTester tester) async { + const String explorePage = 'Explore page'; + const String commutePage = 'Commute page'; + const String savedPage = 'Saved page'; + + await tester.pumpWidget( + const example.OverlayApp(), + ); + + expect(find.text(explorePage), findsNothing); + expect(find.text(commutePage), findsNothing); + expect(find.text(savedPage), findsNothing); + + await tester.tap(find.widgetWithText(ElevatedButton, 'Explore')); + await tester.pumpAndSettle(); + expect(find.text(explorePage), findsOneWidget); + + await tester.tap(find.widgetWithText(ElevatedButton, 'Commute')); + await tester.pumpAndSettle(); + expect(find.text(commutePage), findsOneWidget); + + await tester.tap(find.widgetWithText(ElevatedButton, 'Saved')); + await tester.pumpAndSettle(); + expect(find.text(savedPage), findsOneWidget); + + await tester.tap(find.widgetWithText(ElevatedButton, 'Remove Overlay')); + await tester.pumpAndSettle(); + expect(find.text(explorePage), findsNothing); + expect(find.text(commutePage), findsNothing); + expect(find.text(savedPage), findsNothing); + }); +} diff --git a/packages/flutter/lib/src/widgets/overlay.dart b/packages/flutter/lib/src/widgets/overlay.dart index f0013872a90..0b55e8d63fb 100644 --- a/packages/flutter/lib/src/widgets/overlay.dart +++ b/packages/flutter/lib/src/widgets/overlay.dart @@ -278,6 +278,13 @@ class _OverlayEntryWidgetState extends State<_OverlayEntryWidget> { /// navigation and being able to insert widgets on top of the pages in an app. /// To simply display a stack of widgets, consider using [Stack] instead. /// +/// {@tool dartpad} +/// This example shows how to use the [Overlay] to highlight the [NavigationBar] +/// destination. +/// +/// ** See code in examples/api/lib/widgets/overlay/overlay.0.dart ** +/// {@end-tool} +/// /// See also: /// /// * [OverlayEntry], the class that is used for describing the overlay entries.