diff --git a/examples/api/lib/material/app_bar/app_bar.0.dart b/examples/api/lib/material/app_bar/app_bar.0.dart index c58bc4d2be3f..ce01560389f5 100644 --- a/examples/api/lib/material/app_bar/app_bar.0.dart +++ b/examples/api/lib/material/app_bar/app_bar.0.dart @@ -6,24 +6,21 @@ import 'package:flutter/material.dart'; -void main() => runApp(const MyApp()); +void main() => runApp(const AppBarApp()); -class MyApp extends StatelessWidget { - const MyApp({super.key}); - - static const String _title = 'Flutter Code Sample'; +class AppBarApp extends StatelessWidget { + const AppBarApp({super.key}); @override Widget build(BuildContext context) { return const MaterialApp( - title: _title, - home: MyStatelessWidget(), + home: AppBarExample(), ); } } -class MyStatelessWidget extends StatelessWidget { - const MyStatelessWidget({super.key}); +class AppBarExample extends StatelessWidget { + const AppBarExample({super.key}); @override Widget build(BuildContext context) { diff --git a/examples/api/lib/material/app_bar/app_bar.1.dart b/examples/api/lib/material/app_bar/app_bar.1.dart index f94b07875b05..925df09cfb7f 100644 --- a/examples/api/lib/material/app_bar/app_bar.1.dart +++ b/examples/api/lib/material/app_bar/app_bar.1.dart @@ -6,43 +6,120 @@ import 'package:flutter/material.dart'; -void main() => runApp(const MyApp()); +final List _items = List.generate(51, (int index) => index); -class MyApp extends StatelessWidget { - const MyApp({super.key}); +void main() => runApp(const AppBarApp()); - static const String _title = 'Flutter Code Sample'; +class AppBarApp extends StatelessWidget { + const AppBarApp({super.key}); @override Widget build(BuildContext context) { - return const MaterialApp( - title: _title, - home: MyStatelessWidget(), + return MaterialApp( + theme: ThemeData( + colorSchemeSeed: const Color(0xff6750a4), + useMaterial3: true, + ), + home: const AppBarExample(), ); } } -class MyStatelessWidget extends StatelessWidget { - const MyStatelessWidget({super.key}); +class AppBarExample extends StatefulWidget { + const AppBarExample({super.key}); + + @override + State createState() => _AppBarExampleState(); +} + +class _AppBarExampleState extends State { + bool shadowColor = false; + double? scrolledUnderElevation; @override Widget build(BuildContext context) { - final ButtonStyle style = - TextButton.styleFrom(primary: Theme.of(context).colorScheme.onPrimary); + final ColorScheme colorScheme = Theme.of(context).colorScheme; + final Color oddItemColor = colorScheme.primary.withOpacity(0.05); + final Color evenItemColor = colorScheme.primary.withOpacity(0.15); + return Scaffold( appBar: AppBar( - actions: [ - TextButton( - style: style, - onPressed: () {}, - child: const Text('Action 1'), - ), - TextButton( - style: style, - onPressed: () {}, - child: const Text('Action 2'), + title: const Text('AppBar Demo'), + scrolledUnderElevation: scrolledUnderElevation, + shadowColor: shadowColor ? Theme.of(context).colorScheme.shadow : null, + ), + body: GridView.builder( + shrinkWrap: true, + itemCount: _items.length, + padding: const EdgeInsets.all(8.0), + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 3, + childAspectRatio: 2.0, + mainAxisSpacing: 10.0, + crossAxisSpacing: 10.0, + ), + itemBuilder: (BuildContext context, int index) { + if (index == 0) { + return Center( + child: Text( + 'Scroll to see the Appbar in effect.', + style: Theme.of(context).textTheme.labelLarge, + textAlign: TextAlign.center, + ), + ); + } + return Container( + alignment: Alignment.center, + // tileColor: _items[index].isOdd ? oddItemColor : evenItemColor, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20.0), + color: _items[index].isOdd ? oddItemColor : evenItemColor, + ), + child: Text('Item $index'), + ); + }, + ), + bottomNavigationBar: BottomAppBar( + child: Padding( + padding: const EdgeInsets.all(8), + child: OverflowBar( + overflowAlignment: OverflowBarAlignment.center, + alignment: MainAxisAlignment.center, + overflowSpacing: 5.0, + children: [ + ElevatedButton.icon( + onPressed: () { + setState(() { + shadowColor = !shadowColor; + }); + }, + icon: Icon( + shadowColor ? Icons.visibility_off : Icons.visibility, + ), + label: const Text('shadow color'), + ), + const SizedBox(width: 5), + ElevatedButton.icon( + onPressed: () { + if (scrolledUnderElevation == null) { + setState(() { + // Default elevation is 3.0, increment by 1.0. + scrolledUnderElevation = 4.0; + }); + } else { + setState(() { + scrolledUnderElevation = scrolledUnderElevation! + 1.0; + }); + } + }, + icon: const Icon(Icons.add), + label: Text( + 'scrolledUnderElevation: ${scrolledUnderElevation ?? 'default'}', + ), + ), + ], ), - ], + ), ), ); } diff --git a/examples/api/lib/material/app_bar/app_bar.2.dart b/examples/api/lib/material/app_bar/app_bar.2.dart new file mode 100644 index 000000000000..58a4f236cf60 --- /dev/null +++ b/examples/api/lib/material/app_bar/app_bar.2.dart @@ -0,0 +1,47 @@ +// 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 AppBar + +import 'package:flutter/material.dart'; + +void main() => runApp(const AppBarApp()); + +class AppBarApp extends StatelessWidget { + const AppBarApp({super.key}); + + @override + Widget build(BuildContext context) { + return const MaterialApp( + home: AppBarExample(), + ); + } +} + +class AppBarExample extends StatelessWidget { + const AppBarExample({super.key}); + + @override + Widget build(BuildContext context) { + final ButtonStyle style = TextButton.styleFrom( + primary: Theme.of(context).colorScheme.onPrimary, + ); + return Scaffold( + appBar: AppBar( + actions: [ + TextButton( + style: style, + onPressed: () {}, + child: const Text('Action 1'), + ), + TextButton( + style: style, + onPressed: () {}, + child: const Text('Action 2'), + ), + ], + ), + ); + } +} diff --git a/examples/api/lib/material/app_bar/sliver_app_bar.1.dart b/examples/api/lib/material/app_bar/sliver_app_bar.1.dart index 72895d256a21..8859c4512e06 100644 --- a/examples/api/lib/material/app_bar/sliver_app_bar.1.dart +++ b/examples/api/lib/material/app_bar/sliver_app_bar.1.dart @@ -6,30 +6,27 @@ import 'package:flutter/material.dart'; -void main() => runApp(const MyApp()); +void main() => runApp(const AppBarApp()); -class MyApp extends StatelessWidget { - const MyApp({super.key}); - - static const String _title = 'Flutter Code Sample'; +class AppBarApp extends StatelessWidget { + const AppBarApp({super.key}); @override Widget build(BuildContext context) { return const MaterialApp( - title: _title, - home: MyStatefulWidget(), + home: SliverAppBarExample(), ); } } -class MyStatefulWidget extends StatefulWidget { - const MyStatefulWidget({super.key}); +class SliverAppBarExample extends StatefulWidget { + const SliverAppBarExample({super.key}); @override - State createState() => _MyStatefulWidgetState(); + State createState() => _SliverAppBarExampleState(); } -class _MyStatefulWidgetState extends State { +class _SliverAppBarExampleState extends State { bool _pinned = true; bool _snap = false; bool _floating = false; diff --git a/examples/api/test/material/appbar/app_bar.0_test.dart b/examples/api/test/material/appbar/app_bar.0_test.dart new file mode 100644 index 000000000000..b131b297f7cb --- /dev/null +++ b/examples/api/test/material/appbar/app_bar.0_test.dart @@ -0,0 +1,24 @@ +// 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/material/app_bar/app_bar.0.dart' as example; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets('Appbar updates on navigation', (WidgetTester tester) async { + await tester.pumpWidget( + const example.AppBarApp(), + ); + + expect(find.widgetWithText(AppBar, 'AppBar Demo'), findsOneWidget); + expect(find.text('This is the home page'), findsOneWidget); + + await tester.tap(find.byIcon(Icons.navigate_next)); + await tester.pumpAndSettle(); + + expect(find.widgetWithText(AppBar, 'Next page'), findsOneWidget); + expect(find.text('This is the next page'), findsOneWidget); + }); +} diff --git a/examples/api/test/material/appbar/app_bar.1_test.dart b/examples/api/test/material/appbar/app_bar.1_test.dart new file mode 100644 index 000000000000..2713cf41bffb --- /dev/null +++ b/examples/api/test/material/appbar/app_bar.1_test.dart @@ -0,0 +1,55 @@ +// 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/material/app_bar/app_bar.1.dart' as example; +import 'package:flutter_test/flutter_test.dart'; + +const Offset _kOffset = Offset(0.0, -100.0); + +void main() { + testWidgets('Appbar Material 3 test', (WidgetTester tester) async { + await tester.pumpWidget( + const example.AppBarApp() + ); + + expect(find.widgetWithText(AppBar, 'AppBar Demo'), findsOneWidget); + Material appbarMaterial = _getAppBarMaterial(tester); + expect(appbarMaterial.shadowColor, null); + expect(appbarMaterial.elevation, 0); + + await tester.drag(find.text('Item 4'), _kOffset, touchSlopY: 0, warnIfMissed: false); + + await tester.pump(); + await tester.pump(const Duration(milliseconds: 500)); + + await tester.tap(find.text('shadow color')); + await tester.pumpAndSettle(); + appbarMaterial = _getAppBarMaterial(tester); + expect(appbarMaterial.shadowColor, Colors.black); + expect(appbarMaterial.elevation, 3.0); + + await tester.tap(find.text('scrolledUnderElevation: default')); + await tester.pumpAndSettle(); + + appbarMaterial = _getAppBarMaterial(tester); + expect(appbarMaterial.shadowColor, Colors.black); + expect(appbarMaterial.elevation, 4.0); + + await tester.tap(find.text('scrolledUnderElevation: 4.0')); + await tester.pumpAndSettle(); + appbarMaterial = _getAppBarMaterial(tester); + expect(appbarMaterial.shadowColor, Colors.black); + expect(appbarMaterial.elevation, 5.0); + }); +} + +Material _getAppBarMaterial(WidgetTester tester) { + return tester.widget( + find.descendant( + of: find.byType(AppBar), + matching: find.byType(Material), + ), + ); +} diff --git a/examples/api/test/material/appbar/app_bar.2_test.dart b/examples/api/test/material/appbar/app_bar.2_test.dart new file mode 100644 index 000000000000..b064e95435c4 --- /dev/null +++ b/examples/api/test/material/appbar/app_bar.2_test.dart @@ -0,0 +1,19 @@ +// 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/material/app_bar/app_bar.2.dart' as example; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets('Appbar and actions', (WidgetTester tester) async { + await tester.pumpWidget( + const example.AppBarApp(), + ); + + expect(find.byType(AppBar), findsOneWidget); + expect(find.widgetWithText(TextButton, 'Action 1'), findsOneWidget); + expect(find.widgetWithText(TextButton, 'Action 2'), findsOneWidget); + }); +} diff --git a/examples/api/test/material/appbar/sliver_app_bar.1_test.dart b/examples/api/test/material/appbar/sliver_app_bar.1_test.dart new file mode 100644 index 000000000000..5a7cb784a8b7 --- /dev/null +++ b/examples/api/test/material/appbar/sliver_app_bar.1_test.dart @@ -0,0 +1,25 @@ +// 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/material/app_bar/sliver_app_bar.1.dart' as example; +import 'package:flutter_test/flutter_test.dart'; + +const Offset _kOffset = Offset(0.0, -200.0); + +void main() { + testWidgets('SliverAppbar can be pinned', (WidgetTester tester) async { + await tester.pumpWidget( + const example.AppBarApp(), + ); + + expect(find.widgetWithText(SliverAppBar, 'SliverAppBar'), findsOneWidget); + expect(tester.getBottomLeft(find.text('SliverAppBar')).dy, 144.0); + + await tester.drag(find.text('0'), _kOffset, touchSlopY: 0, warnIfMissed: false); + await tester.pump(); + await tester.pump(const Duration(milliseconds: 500)); + expect(tester.getBottomLeft(find.text('SliverAppBar')).dy, 40.0); + }); +} diff --git a/packages/flutter/lib/src/material/app_bar.dart b/packages/flutter/lib/src/material/app_bar.dart index e3114e1206aa..06f97af3255d 100644 --- a/packages/flutter/lib/src/material/app_bar.dart +++ b/packages/flutter/lib/src/material/app_bar.dart @@ -107,6 +107,15 @@ class _PreferredAppBarSize extends Size { /// ** See code in examples/api/lib/material/app_bar/app_bar.0.dart ** /// {@end-tool} /// +/// Material Design 3 introduced new types of app bar. +/// {@tool dartpad} +/// This sample shows the creation of an [AppBar] widget with the [shadowColor] and +/// [scrolledUnderElevation] properties set, as described in: +/// https://m3.material.io/components/top-app-bar/overview +/// +/// ** See code in examples/api/lib/material/app_bar/app_bar.1.dart ** +/// {@end-tool} +/// /// ## Troubleshooting /// /// ### Why don't my TextButton actions appear? @@ -125,9 +134,10 @@ class _PreferredAppBarSize extends Size { /// [TextButton.style]: /// /// {@tool dartpad} +/// This sample shows an [AppBar] with two action buttons with their primary +/// color set to [ColorScheme.onPrimary]. /// -/// -/// ** See code in examples/api/lib/material/app_bar/app_bar.1.dart ** +/// ** See code in examples/api/lib/material/app_bar/app_bar.2.dart ** /// {@end-tool} /// /// See also: