mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
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.
This commit is contained in:
parent
7eed12075b
commit
b08b88ce6c
@ -54,5 +54,65 @@ class _TokenDefaultsM3 extends AppBarTheme {
|
||||
|
||||
@override
|
||||
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);
|
||||
}
|
||||
''';
|
||||
}
|
||||
|
53
examples/api/lib/material/app_bar/sliver_app_bar.2.dart
Normal file
53
examples/api/lib/material/app_bar/sliver_app_bar.2.dart
Normal file
@ -0,0 +1,53 @@
|
||||
// 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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
53
examples/api/lib/material/app_bar/sliver_app_bar.3.dart
Normal file
53
examples/api/lib/material/app_bar/sliver_app_bar.3.dart
Normal file
@ -0,0 +1,53 @@
|
||||
// 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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -860,8 +860,11 @@ class _AppBarState extends State<AppBar> {
|
||||
?? MaterialStateProperty.resolveAs<Color>(defaultColor, states);
|
||||
}
|
||||
|
||||
SystemUiOverlayStyle _systemOverlayStyleForBrightness(Brightness brightness) {
|
||||
return brightness == Brightness.dark ? SystemUiOverlayStyle.light : SystemUiOverlayStyle.dark;
|
||||
SystemUiOverlayStyle _systemOverlayStyleForBrightness(Brightness brightness, [Color? backgroundColor]) {
|
||||
final SystemUiOverlayStyle style = brightness == Brightness.dark
|
||||
? SystemUiOverlayStyle.light
|
||||
: SystemUiOverlayStyle.dark;
|
||||
return style.copyWith(statusBarColor: backgroundColor);
|
||||
}
|
||||
|
||||
@override
|
||||
@ -1139,7 +1142,12 @@ class _AppBarState extends State<AppBar> {
|
||||
: widget.systemOverlayStyle
|
||||
?? appBarTheme.systemOverlayStyle
|
||||
?? defaults.systemOverlayStyle
|
||||
?? _systemOverlayStyleForBrightness(ThemeData.estimateBrightnessForColor(backgroundColor));
|
||||
?? _systemOverlayStyleForBrightness(
|
||||
ThemeData.estimateBrightnessForColor(backgroundColor),
|
||||
// Make the status bar transparent for M3 so the elevation overlay
|
||||
// color is picked up by the statusbar.
|
||||
theme.useMaterial3 ? const Color(0x00000000) : null,
|
||||
);
|
||||
|
||||
return Semantics(
|
||||
container: true,
|
||||
@ -1517,6 +1525,208 @@ class SliverAppBar extends StatefulWidget {
|
||||
assert(stretchTriggerOffset > 0.0),
|
||||
assert(collapsedHeight == null || collapsedHeight >= toolbarHeight, 'The "collapsedHeight" argument has to be larger than or equal to [toolbarHeight].');
|
||||
|
||||
/// Creates a Material Design medium top app bar that can be placed
|
||||
/// in a [CustomScrollView].
|
||||
///
|
||||
/// Returns a [SliverAppBar] configured with appropriate defaults
|
||||
/// for a medium top app bar as defined in Material 3. It starts fully
|
||||
/// expanded with the title in an area underneath the main row of icons.
|
||||
/// When the [CustomScrollView] is scrolled, the title will be scrolled
|
||||
/// under the main row. When it is fully collapsed, a smaller version of the
|
||||
/// title will fade in on the main row. The reverse will happen if it is
|
||||
/// expanded again.
|
||||
///
|
||||
/// {@tool dartpad}
|
||||
/// This sample shows how to use [SliverAppBar.medium] in a [CustomScrollView].
|
||||
///
|
||||
/// ** See code in examples/api/lib/material/app_bar/sliver_app_bar.2.dart **
|
||||
/// {@end-tool}
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [AppBar], for a small or center-aligned top app bar.
|
||||
/// * [SliverAppBar.large], for a large top app bar.
|
||||
/// * https://m3.material.io/components/top-app-bar/overview, the Material 3
|
||||
/// app bar specification.
|
||||
factory SliverAppBar.medium({
|
||||
Key? key,
|
||||
Widget? leading,
|
||||
bool automaticallyImplyLeading = true,
|
||||
Widget? title,
|
||||
List<Widget>? actions,
|
||||
Widget? flexibleSpace,
|
||||
PreferredSizeWidget? bottom,
|
||||
double? elevation,
|
||||
double? scrolledUnderElevation,
|
||||
Color? shadowColor,
|
||||
Color? surfaceTintColor,
|
||||
bool forceElevated = false,
|
||||
Color? backgroundColor,
|
||||
Color? foregroundColor,
|
||||
IconThemeData? iconTheme,
|
||||
IconThemeData? actionsIconTheme,
|
||||
bool primary = true,
|
||||
bool? centerTitle,
|
||||
bool excludeHeaderSemantics = false,
|
||||
double? titleSpacing,
|
||||
double? collapsedHeight,
|
||||
double? expandedHeight,
|
||||
bool floating = false,
|
||||
bool pinned = true,
|
||||
bool snap = false,
|
||||
bool stretch = false,
|
||||
double stretchTriggerOffset = 100.0,
|
||||
AsyncCallback? onStretchTrigger,
|
||||
ShapeBorder? shape,
|
||||
double toolbarHeight = _MediumScrollUnderFlexibleConfig.collapsedHeight,
|
||||
double? leadingWidth,
|
||||
TextStyle? toolbarTextStyle,
|
||||
TextStyle? titleTextStyle,
|
||||
SystemUiOverlayStyle? systemOverlayStyle,
|
||||
}) {
|
||||
return SliverAppBar(
|
||||
key: key,
|
||||
leading: leading,
|
||||
automaticallyImplyLeading: automaticallyImplyLeading,
|
||||
actions: actions,
|
||||
flexibleSpace: flexibleSpace ?? _ScrollUnderFlexibleSpace(
|
||||
title: title,
|
||||
variant: _ScrollUnderFlexibleVariant.medium,
|
||||
centerCollapsedTitle: centerTitle,
|
||||
primary: primary,
|
||||
),
|
||||
bottom: bottom,
|
||||
elevation: elevation,
|
||||
scrolledUnderElevation: scrolledUnderElevation,
|
||||
shadowColor: shadowColor,
|
||||
surfaceTintColor: surfaceTintColor,
|
||||
forceElevated: forceElevated,
|
||||
backgroundColor: backgroundColor,
|
||||
foregroundColor: foregroundColor,
|
||||
iconTheme: iconTheme,
|
||||
actionsIconTheme: actionsIconTheme,
|
||||
primary: primary,
|
||||
centerTitle: centerTitle,
|
||||
excludeHeaderSemantics: excludeHeaderSemantics,
|
||||
titleSpacing: titleSpacing,
|
||||
collapsedHeight: collapsedHeight ?? _MediumScrollUnderFlexibleConfig.collapsedHeight,
|
||||
expandedHeight: expandedHeight ?? _MediumScrollUnderFlexibleConfig.expandedHeight,
|
||||
floating: floating,
|
||||
pinned: pinned,
|
||||
snap: snap,
|
||||
stretch: stretch,
|
||||
stretchTriggerOffset: stretchTriggerOffset,
|
||||
onStretchTrigger: onStretchTrigger,
|
||||
shape: shape,
|
||||
toolbarHeight: toolbarHeight,
|
||||
leadingWidth: leadingWidth,
|
||||
toolbarTextStyle: toolbarTextStyle,
|
||||
titleTextStyle: titleTextStyle,
|
||||
systemOverlayStyle: systemOverlayStyle,
|
||||
);
|
||||
}
|
||||
|
||||
/// Creates a Material Design large top app bar that can be placed
|
||||
/// in a [CustomScrollView].
|
||||
///
|
||||
/// Returns a [SliverAppBar] configured with appropriate defaults
|
||||
/// for a large top app bar as defined in Material 3. It starts fully
|
||||
/// expanded with the title in an area underneath the main row of icons.
|
||||
/// When the [CustomScrollView] is scrolled, the title will be scrolled
|
||||
/// under the main row. When it is fully collapsed, a smaller version of the
|
||||
/// title will fade in on the main row. The reverse will happen if it is
|
||||
/// expanded again.
|
||||
///
|
||||
/// {@tool dartpad}
|
||||
/// This sample shows how to use [SliverAppBar.large] in a [CustomScrollView].
|
||||
///
|
||||
/// ** See code in examples/api/lib/material/app_bar/sliver_app_bar.3.dart **
|
||||
/// {@end-tool}
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [AppBar], for a small or center-aligned top app bar.
|
||||
/// * [SliverAppBar.medium], for a medium top app bar.
|
||||
/// * https://m3.material.io/components/top-app-bar/overview, the Material 3
|
||||
/// app bar specification.
|
||||
factory SliverAppBar.large({
|
||||
Key? key,
|
||||
Widget? leading,
|
||||
bool automaticallyImplyLeading = true,
|
||||
Widget? title,
|
||||
List<Widget>? actions,
|
||||
Widget? flexibleSpace,
|
||||
PreferredSizeWidget? bottom,
|
||||
double? elevation,
|
||||
double? scrolledUnderElevation,
|
||||
Color? shadowColor,
|
||||
Color? surfaceTintColor,
|
||||
bool forceElevated = false,
|
||||
Color? backgroundColor,
|
||||
Color? foregroundColor,
|
||||
IconThemeData? iconTheme,
|
||||
IconThemeData? actionsIconTheme,
|
||||
bool primary = true,
|
||||
bool? centerTitle,
|
||||
bool excludeHeaderSemantics = false,
|
||||
double? titleSpacing,
|
||||
double? collapsedHeight,
|
||||
double? expandedHeight,
|
||||
bool floating = false,
|
||||
bool pinned = true,
|
||||
bool snap = false,
|
||||
bool stretch = false,
|
||||
double stretchTriggerOffset = 100.0,
|
||||
AsyncCallback? onStretchTrigger,
|
||||
ShapeBorder? shape,
|
||||
double toolbarHeight = _LargeScrollUnderFlexibleConfig.collapsedHeight,
|
||||
double? leadingWidth,
|
||||
TextStyle? toolbarTextStyle,
|
||||
TextStyle? titleTextStyle,
|
||||
SystemUiOverlayStyle? systemOverlayStyle,
|
||||
}) {
|
||||
return SliverAppBar(
|
||||
key: key,
|
||||
leading: leading,
|
||||
automaticallyImplyLeading: automaticallyImplyLeading,
|
||||
actions: actions,
|
||||
flexibleSpace: flexibleSpace ?? _ScrollUnderFlexibleSpace(
|
||||
title: title,
|
||||
variant: _ScrollUnderFlexibleVariant.large,
|
||||
centerCollapsedTitle: centerTitle,
|
||||
primary: primary,
|
||||
),
|
||||
bottom: bottom,
|
||||
elevation: elevation,
|
||||
scrolledUnderElevation: scrolledUnderElevation,
|
||||
shadowColor: shadowColor,
|
||||
surfaceTintColor: surfaceTintColor,
|
||||
forceElevated: forceElevated,
|
||||
backgroundColor: backgroundColor,
|
||||
foregroundColor: foregroundColor,
|
||||
iconTheme: iconTheme,
|
||||
actionsIconTheme: actionsIconTheme,
|
||||
primary: primary,
|
||||
centerTitle: centerTitle,
|
||||
excludeHeaderSemantics: excludeHeaderSemantics,
|
||||
titleSpacing: titleSpacing,
|
||||
collapsedHeight: collapsedHeight ?? _LargeScrollUnderFlexibleConfig.collapsedHeight,
|
||||
expandedHeight: expandedHeight ?? _LargeScrollUnderFlexibleConfig.expandedHeight,
|
||||
floating: floating,
|
||||
pinned: pinned,
|
||||
snap: snap,
|
||||
stretch: stretch,
|
||||
stretchTriggerOffset: stretchTriggerOffset,
|
||||
onStretchTrigger: onStretchTrigger,
|
||||
shape: shape,
|
||||
toolbarHeight: toolbarHeight,
|
||||
leadingWidth: leadingWidth,
|
||||
toolbarTextStyle: toolbarTextStyle,
|
||||
titleTextStyle: titleTextStyle,
|
||||
systemOverlayStyle: systemOverlayStyle,
|
||||
);
|
||||
}
|
||||
|
||||
/// {@macro flutter.material.appbar.leading}
|
||||
///
|
||||
/// This property is used to configure an [AppBar].
|
||||
@ -1943,6 +2153,128 @@ class _RenderAppBarTitleBox extends RenderAligningShiftedBox {
|
||||
}
|
||||
}
|
||||
|
||||
enum _ScrollUnderFlexibleVariant { medium, large }
|
||||
|
||||
class _ScrollUnderFlexibleSpace extends StatefulWidget {
|
||||
const _ScrollUnderFlexibleSpace({
|
||||
this.title,
|
||||
required this.variant,
|
||||
this.centerCollapsedTitle,
|
||||
this.primary = true,
|
||||
});
|
||||
|
||||
final Widget? title;
|
||||
final _ScrollUnderFlexibleVariant variant;
|
||||
final bool? centerCollapsedTitle;
|
||||
final bool primary;
|
||||
|
||||
@override
|
||||
State<_ScrollUnderFlexibleSpace> createState() => _ScrollUnderFlexibleSpaceState();
|
||||
}
|
||||
|
||||
class _ScrollUnderFlexibleSpaceState extends State<_ScrollUnderFlexibleSpace> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
late final ThemeData theme = Theme.of(context);
|
||||
final FlexibleSpaceBarSettings settings = context.dependOnInheritedWidgetOfExactType<FlexibleSpaceBarSettings>()!;
|
||||
final double topPadding = widget.primary ? MediaQuery.of(context).viewPadding.top : 0;
|
||||
final double collapsedHeight = settings.minExtent - topPadding;
|
||||
final double scrollUnderHeight = settings.maxExtent - settings.minExtent;
|
||||
final _ScrollUnderFlexibleConfig config;
|
||||
switch (widget.variant) {
|
||||
case _ScrollUnderFlexibleVariant.medium:
|
||||
config = _MediumScrollUnderFlexibleConfig(context);
|
||||
break;
|
||||
case _ScrollUnderFlexibleVariant.large:
|
||||
config = _LargeScrollUnderFlexibleConfig(context);
|
||||
break;
|
||||
}
|
||||
|
||||
late final Widget? collapsedTitle;
|
||||
late final Widget? expandedTitle;
|
||||
if (widget.title != null) {
|
||||
collapsedTitle = config.collapsedTextStyle != null
|
||||
? DefaultTextStyle(
|
||||
style: config.collapsedTextStyle!,
|
||||
child: widget.title!,
|
||||
)
|
||||
: widget.title;
|
||||
expandedTitle = config.expandedTextStyle != null
|
||||
? DefaultTextStyle(
|
||||
style: config.expandedTextStyle!,
|
||||
child: widget.title!,
|
||||
)
|
||||
: widget.title;
|
||||
}
|
||||
|
||||
late final bool centerTitle;
|
||||
{
|
||||
bool platformCenter() {
|
||||
assert(theme.platform != null);
|
||||
switch (theme.platform) {
|
||||
case TargetPlatform.android:
|
||||
case TargetPlatform.fuchsia:
|
||||
case TargetPlatform.linux:
|
||||
case TargetPlatform.windows:
|
||||
return false;
|
||||
case TargetPlatform.iOS:
|
||||
case TargetPlatform.macOS:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
centerTitle = widget.centerCollapsedTitle
|
||||
?? theme.appBarTheme.centerTitle
|
||||
?? platformCenter();
|
||||
}
|
||||
|
||||
final bool isCollapsed = settings.isScrolledUnder ?? false;
|
||||
return Column(
|
||||
children: <Widget>[
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: topPadding),
|
||||
child: Container(
|
||||
height: collapsedHeight,
|
||||
padding: centerTitle ? config.collapsedCenteredTitlePadding : config.collapsedTitlePadding,
|
||||
child: AnimatedOpacity(
|
||||
opacity: isCollapsed ? 1 : 0,
|
||||
duration: const Duration(milliseconds: 500),
|
||||
curve: const Cubic(0.2, 0.0, 0.0, 1.0),
|
||||
child: Align(
|
||||
alignment: centerTitle
|
||||
? Alignment.center
|
||||
: AlignmentDirectional.centerStart,
|
||||
child: collapsedTitle
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
child: ClipRect(
|
||||
child: OverflowBox(
|
||||
minHeight: scrollUnderHeight,
|
||||
maxHeight: scrollUnderHeight,
|
||||
alignment: Alignment.bottomLeft,
|
||||
child: Container(
|
||||
alignment: AlignmentDirectional.bottomStart,
|
||||
padding: config.expandedTitlePadding,
|
||||
child: expandedTitle,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
mixin _ScrollUnderFlexibleConfig {
|
||||
TextStyle? get collapsedTextStyle;
|
||||
TextStyle? get expandedTextStyle;
|
||||
EdgeInsetsGeometry? get collapsedTitlePadding;
|
||||
EdgeInsetsGeometry? get collapsedCenteredTitlePadding;
|
||||
EdgeInsetsGeometry? get expandedTitlePadding;
|
||||
}
|
||||
|
||||
class _DefaultsM2 extends AppBarTheme {
|
||||
_DefaultsM2(this.context)
|
||||
: super(
|
||||
@ -2020,4 +2352,64 @@ class _TokenDefaultsM3 extends AppBarTheme {
|
||||
@override
|
||||
TextStyle? get titleTextStyle => _textTheme.titleLarge;
|
||||
}
|
||||
|
||||
// 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 = 64.0;
|
||||
static const double expandedHeight = 112.0;
|
||||
|
||||
@override
|
||||
TextStyle? get collapsedTextStyle =>
|
||||
_textTheme.titleLarge?.apply(color: _colors.onSurface);
|
||||
|
||||
@override
|
||||
TextStyle? get expandedTextStyle =>
|
||||
_textTheme.headlineSmall?.apply(color: _colors.onSurface);
|
||||
|
||||
@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 = 64.0;
|
||||
static const double expandedHeight = 152.0;
|
||||
|
||||
@override
|
||||
TextStyle? get collapsedTextStyle =>
|
||||
_textTheme.titleLarge?.apply(color: _colors.onSurface);
|
||||
|
||||
@override
|
||||
TextStyle? get expandedTextStyle =>
|
||||
_textTheme.headlineMedium?.apply(color: _colors.onSurface);
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
// END GENERATED TOKEN PROPERTIES
|
||||
|
@ -953,6 +953,152 @@ void main() {
|
||||
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 {
|
||||
final bool useMaterial3 = ThemeData().useMaterial3;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user