Unverified Commit b08b88ce authored by Darren Austin's avatar Darren Austin Committed by GitHub

Add support for Material 3 medium and large top app bars. (#103962)

* Add support for M3 AppBar 'Medium' and 'Large' types.

* Updates from review feedback.

* Updated from review feedback.
parent 7eed1207
...@@ -54,5 +54,65 @@ class _TokenDefaultsM3 extends AppBarTheme { ...@@ -54,5 +54,65 @@ class _TokenDefaultsM3 extends AppBarTheme {
@override @override
TextStyle? get titleTextStyle => ${textStyle('md.comp.top-app-bar.small.headline')}; TextStyle? get titleTextStyle => ${textStyle('md.comp.top-app-bar.small.headline')};
}'''; }
// Variant configuration
class _MediumScrollUnderFlexibleConfig with _ScrollUnderFlexibleConfig {
_MediumScrollUnderFlexibleConfig(this.context);
final BuildContext context;
late final ThemeData _theme = Theme.of(context);
late final ColorScheme _colors = _theme.colorScheme;
late final TextTheme _textTheme = _theme.textTheme;
static const double collapsedHeight = ${tokens['md.comp.top-app-bar.small.container.height']};
static const double expandedHeight = ${tokens['md.comp.top-app-bar.medium.container.height']};
@override
TextStyle? get collapsedTextStyle =>
${textStyle('md.comp.top-app-bar.small.headline')}?.apply(color: ${color('md.comp.top-app-bar.small.headline.color')});
@override
TextStyle? get expandedTextStyle =>
${textStyle('md.comp.top-app-bar.medium.headline')}?.apply(color: ${color('md.comp.top-app-bar.medium.headline.color')});
@override
EdgeInsetsGeometry? get collapsedTitlePadding => const EdgeInsetsDirectional.fromSTEB(48, 0, 16, 0);
@override
EdgeInsetsGeometry? get collapsedCenteredTitlePadding => const EdgeInsets.fromLTRB(16, 0, 16, 0);
@override
EdgeInsetsGeometry? get expandedTitlePadding => const EdgeInsets.fromLTRB(16, 0, 16, 20);
}
class _LargeScrollUnderFlexibleConfig with _ScrollUnderFlexibleConfig {
_LargeScrollUnderFlexibleConfig(this.context);
final BuildContext context;
late final ThemeData _theme = Theme.of(context);
late final ColorScheme _colors = _theme.colorScheme;
late final TextTheme _textTheme = _theme.textTheme;
static const double collapsedHeight = ${tokens['md.comp.top-app-bar.small.container.height']};
static const double expandedHeight = ${tokens['md.comp.top-app-bar.large.container.height']};
@override
TextStyle? get collapsedTextStyle =>
${textStyle('md.comp.top-app-bar.small.headline')}?.apply(color: ${color('md.comp.top-app-bar.small.headline.color')});
@override
TextStyle? get expandedTextStyle =>
${textStyle('md.comp.top-app-bar.large.headline')}?.apply(color: ${color('md.comp.top-app-bar.large.headline.color')});
@override
EdgeInsetsGeometry? get collapsedTitlePadding => const EdgeInsetsDirectional.fromSTEB(48, 0, 16, 0);
@override
EdgeInsetsGeometry? get collapsedCenteredTitlePadding => const EdgeInsets.fromLTRB(16, 0, 16, 0);
@override
EdgeInsetsGeometry? get expandedTitlePadding => const EdgeInsets.fromLTRB(16, 0, 16, 28);
}
''';
} }
// 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 SliverAppBar.medium
import 'package:flutter/material.dart';
void main() {
runApp(const AppBarMediumApp());
}
class AppBarMediumApp extends StatelessWidget {
const AppBarMediumApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
useMaterial3: true,
colorSchemeSeed: const Color(0xff6750A4)
),
home: Material(
child: CustomScrollView(
slivers: <Widget>[
SliverAppBar.medium(
leading: IconButton(icon: const Icon(Icons.menu), onPressed: () {}),
title: const Text('Medium App Bar'),
actions: <Widget>[
IconButton(icon: const Icon(Icons.more_vert), onPressed: () {}),
],
),
// Just some content big enough to have something to scroll.
SliverToBoxAdapter(
child: Card(
child: SizedBox(
height: 1200,
child: Padding(
padding: const EdgeInsets.fromLTRB(8, 100, 8, 100),
child: Text(
'Here be scrolling content...',
style: Theme.of(context).textTheme.headlineSmall,
),
),
),
),
),
],
),
),
);
}
}
// 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 SliverAppBar.large
import 'package:flutter/material.dart';
void main() {
runApp(const AppBarLargeApp());
}
class AppBarLargeApp extends StatelessWidget {
const AppBarLargeApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
useMaterial3: true,
colorSchemeSeed: const Color(0xff6750A4)
),
home: Material(
child: CustomScrollView(
slivers: <Widget>[
SliverAppBar.large(
leading: IconButton(icon: const Icon(Icons.menu), onPressed: () {}),
title: const Text('Large App Bar'),
actions: <Widget>[
IconButton(icon: const Icon(Icons.more_vert), onPressed: () {}),
],
),
// Just some content big enough to have something to scroll.
SliverToBoxAdapter(
child: Card(
child: SizedBox(
height: 1200,
child: Padding(
padding: const EdgeInsets.fromLTRB(8, 100, 8, 100),
child: Text(
'Here be scrolling content...',
style: Theme.of(context).textTheme.headlineSmall,
),
),
),
),
),
],
),
),
);
}
}
...@@ -953,6 +953,152 @@ void main() { ...@@ -953,6 +953,152 @@ void main() {
expect(tabBarHeight(tester), initialTabBarHeight); expect(tabBarHeight(tester), initialTabBarHeight);
}); });
testWidgets('SliverAppBar.medium defaults', (WidgetTester tester) async {
const double collapsedAppBarHeight = 64;
const double expandedAppBarHeight = 112;
await tester.pumpWidget(MaterialApp(
home: Scaffold(
body: CustomScrollView(
primary: true,
slivers: <Widget>[
SliverAppBar.medium(
title: const Text('AppBar Title'),
),
SliverToBoxAdapter(
child: Container(
height: 1200,
color: Colors.orange[400],
),
),
],
),
),
));
final ScrollController controller = primaryScrollController(tester);
// There are two widgets for the title. The first is the title on the main
// row with the icons. It is transparent when the app bar is expanded, and
// opaque when it is collapsed. The second title is a larger version that is
// shown at the bottom when the app bar is expanded. It scrolls under the
// main row until it is completely hidden and then the first title is faded
// in.
final Finder collapsedTitle = find.text('AppBar Title').first;
final Finder collapsedTitleOpacity = find.ancestor(
of: collapsedTitle,
matching: find.byType(AnimatedOpacity),
);
final Finder expandedTitle = find.text('AppBar Title').last;
final Finder expandedTitleClip = find.ancestor(
of: expandedTitle,
matching: find.byType(ClipRect),
);
// Default, fully expanded app bar.
expect(controller.offset, 0);
expect(find.byType(SliverAppBar), findsOneWidget);
expect(appBarHeight(tester), expandedAppBarHeight);
expect(tester.widget<AnimatedOpacity>(collapsedTitleOpacity).opacity, 0);
expect(tester.getSize(expandedTitleClip).height, expandedAppBarHeight - collapsedAppBarHeight);
// Scroll the expanded app bar partially out of view.
controller.jumpTo(45);
await tester.pump();
expect(find.byType(SliverAppBar), findsOneWidget);
expect(appBarHeight(tester), expandedAppBarHeight - 45);
expect(tester.widget<AnimatedOpacity>(collapsedTitleOpacity).opacity, 0);
expect(tester.getSize(expandedTitleClip).height, expandedAppBarHeight - collapsedAppBarHeight - 45);
// Scroll so that it is completely collapsed.
controller.jumpTo(600);
await tester.pump();
expect(find.byType(SliverAppBar), findsOneWidget);
expect(appBarHeight(tester), collapsedAppBarHeight);
expect(tester.widget<AnimatedOpacity>(collapsedTitleOpacity).opacity, 1);
expect(tester.getSize(expandedTitleClip).height, 0);
// Scroll back to fully expanded.
controller.jumpTo(0);
await tester.pumpAndSettle();
expect(find.byType(SliverAppBar), findsOneWidget);
expect(appBarHeight(tester), expandedAppBarHeight);
expect(tester.widget<AnimatedOpacity>(collapsedTitleOpacity).opacity, 0);
expect(tester.getSize(expandedTitleClip).height, expandedAppBarHeight - collapsedAppBarHeight);
});
testWidgets('SliverAppBar.large defaults', (WidgetTester tester) async {
const double collapsedAppBarHeight = 64;
const double expandedAppBarHeight = 152;
await tester.pumpWidget(MaterialApp(
home: Scaffold(
body: CustomScrollView(
primary: true,
slivers: <Widget>[
SliverAppBar.large(
title: const Text('AppBar Title'),
),
SliverToBoxAdapter(
child: Container(
height: 1200,
color: Colors.orange[400],
),
),
],
),
),
));
final ScrollController controller = primaryScrollController(tester);
// There are two widgets for the title. The first is the title on the main
// row with the icons. It is transparent when the app bar is expanded, and
// opaque when it is collapsed. The second title is a larger version that is
// shown at the bottom when the app bar is expanded. It scrolls under the
// main row until it is completely hidden and then the first title is faded
// in.
final Finder collapsedTitle = find.text('AppBar Title').first;
final Finder collapsedTitleOpacity = find.ancestor(
of: collapsedTitle,
matching: find.byType(AnimatedOpacity),
);
final Finder expandedTitle = find.text('AppBar Title').last;
final Finder expandedTitleClip = find.ancestor(
of: expandedTitle,
matching: find.byType(ClipRect),
);
// Default, fully expanded app bar.
expect(controller.offset, 0);
expect(find.byType(SliverAppBar), findsOneWidget);
expect(appBarHeight(tester), expandedAppBarHeight);
expect(tester.widget<AnimatedOpacity>(collapsedTitleOpacity).opacity, 0);
expect(tester.getSize(expandedTitleClip).height, expandedAppBarHeight - collapsedAppBarHeight);
// Scroll the expanded app bar partially out of view.
controller.jumpTo(45);
await tester.pump();
expect(find.byType(SliverAppBar), findsOneWidget);
expect(appBarHeight(tester), expandedAppBarHeight - 45);
expect(tester.widget<AnimatedOpacity>(collapsedTitleOpacity).opacity, 0);
expect(tester.getSize(expandedTitleClip).height, expandedAppBarHeight - collapsedAppBarHeight - 45);
// Scroll so that it is completely collapsed.
controller.jumpTo(600);
await tester.pump();
expect(find.byType(SliverAppBar), findsOneWidget);
expect(appBarHeight(tester), collapsedAppBarHeight);
expect(tester.widget<AnimatedOpacity>(collapsedTitleOpacity).opacity, 1);
expect(tester.getSize(expandedTitleClip).height, 0);
// Scroll back to fully expanded.
controller.jumpTo(0);
await tester.pumpAndSettle();
expect(find.byType(SliverAppBar), findsOneWidget);
expect(appBarHeight(tester), expandedAppBarHeight);
expect(tester.widget<AnimatedOpacity>(collapsedTitleOpacity).opacity, 0);
expect(tester.getSize(expandedTitleClip).height, expandedAppBarHeight - collapsedAppBarHeight);
});
testWidgets('AppBar uses the specified elevation or defaults to 4.0', (WidgetTester tester) async { testWidgets('AppBar uses the specified elevation or defaults to 4.0', (WidgetTester tester) async {
final bool useMaterial3 = ThemeData().useMaterial3; final bool useMaterial3 = ThemeData().useMaterial3;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment