mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Refactor SliverAppBar.medium & SliverAppBar.large to fix several issues (#122542)
Refactor `SliverAppBar.medium` & `SliverAppBar.large` to fix several issues
This commit is contained in:
parent
62cb61d3f3
commit
55dc9f93ea
@ -34,6 +34,8 @@ import 'theme.dart';
|
||||
const double _kLeadingWidth = kToolbarHeight; // So the leading button is square.
|
||||
const double _kMaxTitleTextScaleFactor = 1.34; // TODO(perc): Add link to Material spec when available, https://github.com/flutter/flutter/issues/58769.
|
||||
|
||||
enum _SliverAppVariant { small, medium, large }
|
||||
|
||||
// Bottom justify the toolbarHeight child which may overflow the top.
|
||||
class _ToolbarContainerLayout extends SingleChildLayoutDelegate {
|
||||
const _ToolbarContainerLayout(this.toolbarHeight);
|
||||
@ -1191,7 +1193,8 @@ class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
|
||||
required this.titleTextStyle,
|
||||
required this.systemOverlayStyle,
|
||||
required this.forceMaterialTransparency,
|
||||
required this.clipBehavior
|
||||
required this.clipBehavior,
|
||||
required this.variant,
|
||||
}) : assert(primary || topPadding == 0.0),
|
||||
_bottomHeight = bottom?.preferredSize.height ?? 0.0;
|
||||
|
||||
@ -1228,6 +1231,7 @@ class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
|
||||
final double _bottomHeight;
|
||||
final bool forceMaterialTransparency;
|
||||
final Clip? clipBehavior;
|
||||
final _SliverAppVariant variant;
|
||||
|
||||
@override
|
||||
double get minExtent => collapsedHeight;
|
||||
@ -1258,6 +1262,17 @@ class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
|
||||
final double toolbarOpacity = !pinned || isPinnedWithOpacityFade
|
||||
? clampDouble(visibleToolbarHeight / (toolbarHeight ?? kToolbarHeight), 0.0, 1.0)
|
||||
: 1.0;
|
||||
final Widget? effectiveTitle;
|
||||
if (variant == _SliverAppVariant.small) {
|
||||
effectiveTitle = title;
|
||||
} else {
|
||||
effectiveTitle = AnimatedOpacity(
|
||||
opacity: isScrolledUnder ? 1 : 0,
|
||||
duration: const Duration(milliseconds: 500),
|
||||
curve: const Cubic(0.2, 0.0, 0.0, 1.0),
|
||||
child: title,
|
||||
);
|
||||
}
|
||||
|
||||
final Widget appBar = FlexibleSpaceBar.createSettings(
|
||||
minExtent: minExtent,
|
||||
@ -1269,7 +1284,7 @@ class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
|
||||
clipBehavior: clipBehavior,
|
||||
leading: leading,
|
||||
automaticallyImplyLeading: automaticallyImplyLeading,
|
||||
title: title,
|
||||
title: effectiveTitle,
|
||||
actions: actions,
|
||||
flexibleSpace: (title == null && flexibleSpace != null && !excludeHeaderSemantics)
|
||||
? Semantics(
|
||||
@ -1474,7 +1489,11 @@ class SliverAppBar extends StatefulWidget {
|
||||
this.clipBehavior,
|
||||
}) : assert(floating || !snap, 'The "snap" argument only makes sense for floating app bars.'),
|
||||
assert(stretchTriggerOffset > 0.0),
|
||||
assert(collapsedHeight == null || collapsedHeight >= toolbarHeight, 'The "collapsedHeight" argument has to be larger than or equal to [toolbarHeight].');
|
||||
assert(
|
||||
collapsedHeight == null || collapsedHeight >= toolbarHeight,
|
||||
'The "collapsedHeight" argument has to be larger than or equal to [toolbarHeight].',
|
||||
),
|
||||
_variant = _SliverAppVariant.small;
|
||||
|
||||
/// Creates a Material Design medium top app bar that can be placed
|
||||
/// in a [CustomScrollView].
|
||||
@ -1499,87 +1518,50 @@ class SliverAppBar extends StatefulWidget {
|
||||
/// * [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,
|
||||
flexibleSpace: flexibleSpace ?? _ScrollUnderFlexibleSpace(
|
||||
hasLeading: leading != null,
|
||||
title: title,
|
||||
actions: actions,
|
||||
foregroundColor: foregroundColor,
|
||||
variant: _ScrollUnderFlexibleVariant.medium,
|
||||
centerCollapsedTitle: centerTitle,
|
||||
primary: primary,
|
||||
leadingWidth: leadingWidth,
|
||||
titleSpacing: titleSpacing,
|
||||
),
|
||||
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,
|
||||
);
|
||||
}
|
||||
const SliverAppBar.medium({
|
||||
super.key,
|
||||
this.leading,
|
||||
this.automaticallyImplyLeading = true,
|
||||
this.title,
|
||||
this.actions,
|
||||
this.flexibleSpace,
|
||||
this.bottom,
|
||||
this.elevation,
|
||||
this.scrolledUnderElevation,
|
||||
this.shadowColor,
|
||||
this.surfaceTintColor,
|
||||
this.forceElevated = false,
|
||||
this.backgroundColor,
|
||||
this.foregroundColor,
|
||||
this.iconTheme,
|
||||
this.actionsIconTheme,
|
||||
this.primary = true,
|
||||
this.centerTitle,
|
||||
this.excludeHeaderSemantics = false,
|
||||
this.titleSpacing,
|
||||
this.collapsedHeight,
|
||||
this.expandedHeight,
|
||||
this.floating = false,
|
||||
this.pinned = true,
|
||||
this.snap = false,
|
||||
this.stretch = false,
|
||||
this.stretchTriggerOffset = 100.0,
|
||||
this.onStretchTrigger,
|
||||
this.shape,
|
||||
this.toolbarHeight = _MediumScrollUnderFlexibleConfig.collapsedHeight,
|
||||
this.leadingWidth,
|
||||
this.toolbarTextStyle,
|
||||
this.titleTextStyle,
|
||||
this.systemOverlayStyle,
|
||||
this.forceMaterialTransparency = false,
|
||||
this.clipBehavior,
|
||||
}) : assert(floating || !snap, 'The "snap" argument only makes sense for floating app bars.'),
|
||||
assert(stretchTriggerOffset > 0.0),
|
||||
assert(
|
||||
collapsedHeight == null || collapsedHeight >= toolbarHeight,
|
||||
'The "collapsedHeight" argument has to be larger than or equal to [toolbarHeight].',
|
||||
),
|
||||
_variant = _SliverAppVariant.medium;
|
||||
|
||||
/// Creates a Material Design large top app bar that can be placed
|
||||
/// in a [CustomScrollView].
|
||||
@ -1604,87 +1586,50 @@ class SliverAppBar extends StatefulWidget {
|
||||
/// * [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,
|
||||
flexibleSpace: flexibleSpace ?? _ScrollUnderFlexibleSpace(
|
||||
hasLeading: leading != null,
|
||||
title: title,
|
||||
actions: actions,
|
||||
foregroundColor: foregroundColor,
|
||||
variant: _ScrollUnderFlexibleVariant.large,
|
||||
centerCollapsedTitle: centerTitle,
|
||||
primary: primary,
|
||||
leadingWidth: leadingWidth,
|
||||
titleSpacing: titleSpacing,
|
||||
),
|
||||
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,
|
||||
);
|
||||
}
|
||||
const SliverAppBar.large({
|
||||
super.key,
|
||||
this.leading,
|
||||
this.automaticallyImplyLeading = true,
|
||||
this.title,
|
||||
this.actions,
|
||||
this.flexibleSpace,
|
||||
this.bottom,
|
||||
this.elevation,
|
||||
this.scrolledUnderElevation,
|
||||
this.shadowColor,
|
||||
this.surfaceTintColor,
|
||||
this.forceElevated = false,
|
||||
this.backgroundColor,
|
||||
this.foregroundColor,
|
||||
this.iconTheme,
|
||||
this.actionsIconTheme,
|
||||
this.primary = true,
|
||||
this.centerTitle,
|
||||
this.excludeHeaderSemantics = false,
|
||||
this.titleSpacing,
|
||||
this.collapsedHeight,
|
||||
this.expandedHeight,
|
||||
this.floating = false,
|
||||
this.pinned = true,
|
||||
this.snap = false,
|
||||
this.stretch = false,
|
||||
this.stretchTriggerOffset = 100.0,
|
||||
this.onStretchTrigger,
|
||||
this.shape,
|
||||
this.toolbarHeight = _LargeScrollUnderFlexibleConfig.collapsedHeight,
|
||||
this.leadingWidth,
|
||||
this.toolbarTextStyle,
|
||||
this.titleTextStyle,
|
||||
this.systemOverlayStyle,
|
||||
this.forceMaterialTransparency = false,
|
||||
this.clipBehavior,
|
||||
}) : assert(floating || !snap, 'The "snap" argument only makes sense for floating app bars.'),
|
||||
assert(stretchTriggerOffset > 0.0),
|
||||
assert(
|
||||
collapsedHeight == null || collapsedHeight >= toolbarHeight,
|
||||
'The "collapsedHeight" argument has to be larger than or equal to [toolbarHeight].',
|
||||
),
|
||||
_variant = _SliverAppVariant.large;
|
||||
|
||||
/// {@macro flutter.material.appbar.leading}
|
||||
///
|
||||
@ -1941,6 +1886,8 @@ class SliverAppBar extends StatefulWidget {
|
||||
/// {@macro flutter.material.Material.clipBehavior}
|
||||
final Clip? clipBehavior;
|
||||
|
||||
final _SliverAppVariant _variant;
|
||||
|
||||
@override
|
||||
State<SliverAppBar> createState() => _SliverAppBarState();
|
||||
}
|
||||
@ -2004,6 +1951,41 @@ class _SliverAppBarState extends State<SliverAppBar> with TickerProviderStateMix
|
||||
final double collapsedHeight = (widget.pinned && widget.floating && widget.bottom != null)
|
||||
? (widget.collapsedHeight ?? 0.0) + bottomHeight + topPadding
|
||||
: (widget.collapsedHeight ?? widget.toolbarHeight) + bottomHeight + topPadding;
|
||||
final double? effectiveExpandedHeight;
|
||||
final double effectiveCollapsedHeight;
|
||||
final Widget? effectiveFlexibleSpace;
|
||||
switch (widget._variant) {
|
||||
case _SliverAppVariant.small:
|
||||
effectiveExpandedHeight = widget.expandedHeight;
|
||||
effectiveCollapsedHeight = collapsedHeight;
|
||||
effectiveFlexibleSpace = widget.flexibleSpace;
|
||||
case _SliverAppVariant.medium:
|
||||
effectiveExpandedHeight = widget.expandedHeight
|
||||
?? _MediumScrollUnderFlexibleConfig.expandedHeight + bottomHeight;
|
||||
effectiveCollapsedHeight = widget.collapsedHeight
|
||||
?? topPadding + _MediumScrollUnderFlexibleConfig.collapsedHeight + bottomHeight;
|
||||
effectiveFlexibleSpace = widget.flexibleSpace ?? _ScrollUnderFlexibleSpace(
|
||||
title: widget.title,
|
||||
foregroundColor: widget.foregroundColor,
|
||||
variant: _ScrollUnderFlexibleVariant.medium,
|
||||
primary: widget.primary,
|
||||
titleTextStyle: widget.titleTextStyle,
|
||||
bottomHeight: bottomHeight,
|
||||
);
|
||||
case _SliverAppVariant.large:
|
||||
effectiveExpandedHeight = widget.expandedHeight
|
||||
?? _LargeScrollUnderFlexibleConfig.expandedHeight + bottomHeight;
|
||||
effectiveCollapsedHeight = widget.collapsedHeight
|
||||
?? topPadding + _LargeScrollUnderFlexibleConfig.collapsedHeight + bottomHeight;
|
||||
effectiveFlexibleSpace = widget.flexibleSpace ?? _ScrollUnderFlexibleSpace(
|
||||
title: widget.title,
|
||||
foregroundColor: widget.foregroundColor,
|
||||
variant: _ScrollUnderFlexibleVariant.large,
|
||||
primary: widget.primary,
|
||||
titleTextStyle: widget.titleTextStyle,
|
||||
bottomHeight: bottomHeight,
|
||||
);
|
||||
}
|
||||
|
||||
return MediaQuery.removePadding(
|
||||
context: context,
|
||||
@ -2017,7 +1999,7 @@ class _SliverAppBarState extends State<SliverAppBar> with TickerProviderStateMix
|
||||
automaticallyImplyLeading: widget.automaticallyImplyLeading,
|
||||
title: widget.title,
|
||||
actions: widget.actions,
|
||||
flexibleSpace: widget.flexibleSpace,
|
||||
flexibleSpace: effectiveFlexibleSpace,
|
||||
bottom: widget.bottom,
|
||||
elevation: widget.elevation,
|
||||
scrolledUnderElevation: widget.scrolledUnderElevation,
|
||||
@ -2032,8 +2014,8 @@ class _SliverAppBarState extends State<SliverAppBar> with TickerProviderStateMix
|
||||
centerTitle: widget.centerTitle,
|
||||
excludeHeaderSemantics: widget.excludeHeaderSemantics,
|
||||
titleSpacing: widget.titleSpacing,
|
||||
expandedHeight: widget.expandedHeight,
|
||||
collapsedHeight: collapsedHeight,
|
||||
expandedHeight: effectiveExpandedHeight,
|
||||
collapsedHeight: effectiveCollapsedHeight,
|
||||
topPadding: topPadding,
|
||||
floating: widget.floating,
|
||||
pinned: widget.pinned,
|
||||
@ -2048,6 +2030,7 @@ class _SliverAppBarState extends State<SliverAppBar> with TickerProviderStateMix
|
||||
systemOverlayStyle: widget.systemOverlayStyle,
|
||||
forceMaterialTransparency: widget.forceMaterialTransparency,
|
||||
clipBehavior: widget.clipBehavior,
|
||||
variant: widget._variant,
|
||||
),
|
||||
),
|
||||
);
|
||||
@ -2098,35 +2081,30 @@ enum _ScrollUnderFlexibleVariant { medium, large }
|
||||
|
||||
class _ScrollUnderFlexibleSpace extends StatelessWidget {
|
||||
const _ScrollUnderFlexibleSpace({
|
||||
required this.hasLeading,
|
||||
this.title,
|
||||
this.actions,
|
||||
this.foregroundColor,
|
||||
required this.variant,
|
||||
this.centerCollapsedTitle,
|
||||
this.primary = true,
|
||||
this.leadingWidth,
|
||||
this.titleSpacing,
|
||||
this.titleTextStyle,
|
||||
required this.bottomHeight,
|
||||
});
|
||||
|
||||
final bool hasLeading;
|
||||
final Widget? title;
|
||||
final List<Widget>? actions;
|
||||
final Color? foregroundColor;
|
||||
final _ScrollUnderFlexibleVariant variant;
|
||||
final bool? centerCollapsedTitle;
|
||||
final bool primary;
|
||||
final double? leadingWidth;
|
||||
final double? titleSpacing;
|
||||
final TextStyle? titleTextStyle;
|
||||
final double bottomHeight;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
late final ThemeData theme = Theme.of(context);
|
||||
late final AppBarTheme appBarTheme = AppBarTheme.of(context);
|
||||
final AppBarTheme defaults = theme.useMaterial3 ? _AppBarDefaultsM3(context) : _AppBarDefaultsM2(context);
|
||||
final FlexibleSpaceBarSettings settings = context.dependOnInheritedWidgetOfExactType<FlexibleSpaceBarSettings>()!;
|
||||
final double topPadding = primary ? MediaQuery.viewPaddingOf(context).top : 0;
|
||||
final double collapsedHeight = settings.minExtent - topPadding;
|
||||
final double scrollUnderHeight = settings.maxExtent - settings.minExtent;
|
||||
final double collapsedHeight = settings.minExtent - topPadding - bottomHeight;
|
||||
final double scrollUnderHeight = settings.maxExtent - settings.minExtent + bottomHeight;
|
||||
final _ScrollUnderFlexibleConfig config;
|
||||
switch (variant) {
|
||||
case _ScrollUnderFlexibleVariant.medium:
|
||||
@ -2135,71 +2113,46 @@ class _ScrollUnderFlexibleSpace extends StatelessWidget {
|
||||
config = _LargeScrollUnderFlexibleConfig(context);
|
||||
}
|
||||
|
||||
late final Widget? collapsedTitle;
|
||||
late final Widget? expandedTitle;
|
||||
if (title != null) {
|
||||
collapsedTitle = config.collapsedTextStyle != null
|
||||
? DefaultTextStyle(
|
||||
style: config.collapsedTextStyle!.copyWith(color: foregroundColor ?? appBarTheme.foregroundColor),
|
||||
child: title!,
|
||||
)
|
||||
: title;
|
||||
final TextStyle? expandedTextStyle = titleTextStyle
|
||||
?? appBarTheme.titleTextStyle
|
||||
?? config.expandedTextStyle?.copyWith(
|
||||
color: foregroundColor ?? appBarTheme.foregroundColor ?? defaults.foregroundColor,
|
||||
);
|
||||
expandedTitle = config.expandedTextStyle != null
|
||||
? DefaultTextStyle(
|
||||
style: config.expandedTextStyle!.copyWith(color: foregroundColor ?? appBarTheme.foregroundColor),
|
||||
style: expandedTextStyle!,
|
||||
child: title!,
|
||||
)
|
||||
: title;
|
||||
}
|
||||
|
||||
late final bool centerTitle;
|
||||
{
|
||||
bool platformCenter() {
|
||||
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;
|
||||
}
|
||||
EdgeInsetsGeometry expandedTitlePadding() {
|
||||
final EdgeInsets padding = config.expandedTitlePadding!.resolve(Directionality.of(context));
|
||||
if (bottomHeight > 0) {
|
||||
return EdgeInsets.fromLTRB(padding.left, 0, padding.right, bottomHeight);
|
||||
}
|
||||
centerTitle = centerCollapsedTitle ?? appBarTheme.centerTitle ?? platformCenter();
|
||||
if (theme.useMaterial3) {
|
||||
final TextStyle textStyle = config.expandedTextStyle!;
|
||||
// Substract the bottom line height from the bottom padding.
|
||||
// TODO(tahatesser): Figure out why this is done.
|
||||
// https://github.com/flutter/flutter/issues/120672
|
||||
final double adjustBottomPadding = padding.bottom
|
||||
- (textStyle.fontSize! * textStyle.height! - textStyle.fontSize!) / 2;
|
||||
return EdgeInsets.fromLTRB(
|
||||
padding.left,
|
||||
0,
|
||||
padding.right,
|
||||
adjustBottomPadding,
|
||||
);
|
||||
}
|
||||
return padding;
|
||||
}
|
||||
|
||||
EdgeInsetsGeometry effectiveCollapsedTitlePadding = EdgeInsets.zero;
|
||||
if (hasLeading && leadingWidth == null) {
|
||||
effectiveCollapsedTitlePadding = centerTitle
|
||||
? config.collapsedCenteredTitlePadding!
|
||||
: config.collapsedTitlePadding!;
|
||||
} else if (hasLeading && leadingWidth != null) {
|
||||
effectiveCollapsedTitlePadding = EdgeInsetsDirectional.only(start: leadingWidth!);
|
||||
}
|
||||
final bool isCollapsed = settings.isScrolledUnder ?? false;
|
||||
return Column(
|
||||
children: <Widget>[
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: topPadding),
|
||||
child: Container(
|
||||
height: collapsedHeight,
|
||||
padding: effectiveCollapsedTitlePadding,
|
||||
child: NavigationToolbar(
|
||||
centerMiddle: centerTitle,
|
||||
middleSpacing: titleSpacing ?? appBarTheme.titleSpacing ?? NavigationToolbar.kMiddleSpacing,
|
||||
middle: AnimatedOpacity(
|
||||
opacity: isCollapsed ? 1 : 0,
|
||||
duration: const Duration(milliseconds: 500),
|
||||
curve: const Cubic(0.2, 0.0, 0.0, 1.0),
|
||||
child: collapsedTitle,
|
||||
),
|
||||
trailing: actions != null ? Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: actions!,
|
||||
) : null,
|
||||
),
|
||||
),
|
||||
padding: EdgeInsets.only(top: collapsedHeight + topPadding),
|
||||
),
|
||||
Flexible(
|
||||
child: ClipRect(
|
||||
@ -2209,7 +2162,7 @@ class _ScrollUnderFlexibleSpace extends StatelessWidget {
|
||||
alignment: Alignment.bottomLeft,
|
||||
child: Container(
|
||||
alignment: AlignmentDirectional.bottomStart,
|
||||
padding: config.expandedTitlePadding,
|
||||
padding: expandedTitlePadding(),
|
||||
child: expandedTitle,
|
||||
),
|
||||
),
|
||||
@ -2223,8 +2176,6 @@ class _ScrollUnderFlexibleSpace extends StatelessWidget {
|
||||
mixin _ScrollUnderFlexibleConfig {
|
||||
TextStyle? get collapsedTextStyle;
|
||||
TextStyle? get expandedTextStyle;
|
||||
EdgeInsetsGeometry? get collapsedTitlePadding;
|
||||
EdgeInsetsGeometry? get collapsedCenteredTitlePadding;
|
||||
EdgeInsetsGeometry? get expandedTitlePadding;
|
||||
}
|
||||
|
||||
@ -2332,12 +2283,6 @@ class _MediumScrollUnderFlexibleConfig with _ScrollUnderFlexibleConfig {
|
||||
TextStyle? get expandedTextStyle =>
|
||||
_textTheme.headlineSmall?.apply(color: _colors.onSurface);
|
||||
|
||||
@override
|
||||
EdgeInsetsGeometry? get collapsedTitlePadding => const EdgeInsetsDirectional.only(start: 40);
|
||||
|
||||
@override
|
||||
EdgeInsetsGeometry? get collapsedCenteredTitlePadding => const EdgeInsetsDirectional.only(start: 40);
|
||||
|
||||
@override
|
||||
EdgeInsetsGeometry? get expandedTitlePadding => const EdgeInsets.fromLTRB(16, 0, 16, 20);
|
||||
}
|
||||
@ -2361,12 +2306,6 @@ class _LargeScrollUnderFlexibleConfig with _ScrollUnderFlexibleConfig {
|
||||
TextStyle? get expandedTextStyle =>
|
||||
_textTheme.headlineMedium?.apply(color: _colors.onSurface);
|
||||
|
||||
@override
|
||||
EdgeInsetsGeometry? get collapsedTitlePadding => const EdgeInsetsDirectional.only(start: 40);
|
||||
|
||||
@override
|
||||
EdgeInsetsGeometry? get collapsedCenteredTitlePadding => const EdgeInsetsDirectional.only(start: 40);
|
||||
|
||||
@override
|
||||
EdgeInsetsGeometry? get expandedTitlePadding => const EdgeInsets.fromLTRB(16, 0, 16, 28);
|
||||
}
|
||||
|
||||
@ -1086,16 +1086,18 @@ void main() {
|
||||
});
|
||||
|
||||
testWidgets('SliverAppBar.medium defaults', (WidgetTester tester) async {
|
||||
final ThemeData theme = ThemeData(useMaterial3: true);
|
||||
const double collapsedAppBarHeight = 64;
|
||||
const double expandedAppBarHeight = 112;
|
||||
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
theme: theme,
|
||||
home: Scaffold(
|
||||
body: CustomScrollView(
|
||||
primary: true,
|
||||
slivers: <Widget>[
|
||||
SliverAppBar.medium(
|
||||
title: const Text('AppBar Title'),
|
||||
const SliverAppBar.medium(
|
||||
title: Text('AppBar Title'),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: Container(
|
||||
@ -1109,21 +1111,20 @@ void main() {
|
||||
));
|
||||
|
||||
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;
|
||||
// There are two widgets for the title. The first 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. The last is the title on the mainrow with the icons. It is
|
||||
// transparent when the app bar is expanded, and opaque when it is collapsed.
|
||||
final Finder expandedTitle = find.text('AppBar Title').first;
|
||||
final Finder expandedTitleClip = find.ancestor(
|
||||
of: expandedTitle,
|
||||
matching: find.byType(ClipRect),
|
||||
).first;
|
||||
final Finder collapsedTitle = find.text('AppBar Title').last;
|
||||
final Finder collapsedTitleOpacity = find.ancestor(
|
||||
of: collapsedTitle,
|
||||
matching: find.byType(AnimatedOpacity),
|
||||
);
|
||||
|
||||
// Default, fully expanded app bar.
|
||||
@ -1133,6 +1134,17 @@ void main() {
|
||||
expect(tester.widget<AnimatedOpacity>(collapsedTitleOpacity).opacity, 0);
|
||||
expect(tester.getSize(expandedTitleClip).height, expandedAppBarHeight - collapsedAppBarHeight);
|
||||
|
||||
// Test the expanded title is positioned correctly.
|
||||
final Offset titleOffset = tester.getBottomLeft(expandedTitle);
|
||||
expect(titleOffset.dx, 16.0);
|
||||
expect(titleOffset.dy, closeTo(96.0, 0.1));
|
||||
|
||||
// Test the expanded title default color.
|
||||
expect(
|
||||
tester.renderObject<RenderParagraph>(expandedTitle).text.style!.color,
|
||||
theme.colorScheme.onSurface,
|
||||
);
|
||||
|
||||
// Scroll the expanded app bar partially out of view.
|
||||
controller.jumpTo(45);
|
||||
await tester.pump();
|
||||
@ -1159,16 +1171,18 @@ void main() {
|
||||
});
|
||||
|
||||
testWidgets('SliverAppBar.large defaults', (WidgetTester tester) async {
|
||||
final ThemeData theme = ThemeData(useMaterial3: true);
|
||||
const double collapsedAppBarHeight = 64;
|
||||
const double expandedAppBarHeight = 152;
|
||||
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
theme: theme,
|
||||
home: Scaffold(
|
||||
body: CustomScrollView(
|
||||
primary: true,
|
||||
slivers: <Widget>[
|
||||
SliverAppBar.large(
|
||||
title: const Text('AppBar Title'),
|
||||
const SliverAppBar.large(
|
||||
title: Text('AppBar Title'),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: Container(
|
||||
@ -1182,21 +1196,20 @@ void main() {
|
||||
));
|
||||
|
||||
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;
|
||||
// There are two widgets for the title. The first 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. The last is the title on the mainrow with the icons. It is
|
||||
// transparent when the app bar is expanded, and opaque when it is collapsed.
|
||||
final Finder expandedTitle = find.text('AppBar Title').first;
|
||||
final Finder expandedTitleClip = find.ancestor(
|
||||
of: expandedTitle,
|
||||
matching: find.byType(ClipRect),
|
||||
).first;
|
||||
final Finder collapsedTitle = find.text('AppBar Title').last;
|
||||
final Finder collapsedTitleOpacity = find.ancestor(
|
||||
of: collapsedTitle,
|
||||
matching: find.byType(AnimatedOpacity),
|
||||
);
|
||||
|
||||
// Default, fully expanded app bar.
|
||||
@ -1206,6 +1219,19 @@ void main() {
|
||||
expect(tester.widget<AnimatedOpacity>(collapsedTitleOpacity).opacity, 0);
|
||||
expect(tester.getSize(expandedTitleClip).height, expandedAppBarHeight - collapsedAppBarHeight);
|
||||
|
||||
// Test the expanded title is positioned correctly.
|
||||
final Offset titleOffset = tester.getBottomLeft(expandedTitle);
|
||||
expect(titleOffset.dx, 16.0);
|
||||
expect(titleOffset.dy, closeTo(128.0, 0.1));
|
||||
|
||||
|
||||
// Test the expanded title default color.
|
||||
expect(
|
||||
tester.renderObject<RenderParagraph>(expandedTitle).text.style!.color,
|
||||
theme.colorScheme.onSurface,
|
||||
);
|
||||
|
||||
|
||||
// Scroll the expanded app bar partially out of view.
|
||||
controller.jumpTo(45);
|
||||
await tester.pump();
|
||||
@ -4049,6 +4075,7 @@ void main() {
|
||||
onPressed: () {},
|
||||
),
|
||||
title: const Text(title, maxLines: 1),
|
||||
centerTitle: true,
|
||||
actions: const <Widget>[
|
||||
Icon(Icons.search),
|
||||
Icon(Icons.sort),
|
||||
@ -4073,11 +4100,11 @@ void main() {
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final Offset leadingOffset = tester.getTopRight(find.byIcon(Icons.menu));
|
||||
Offset titleOffset = tester.getTopLeft(find.text(title).first);
|
||||
Offset titleOffset = tester.getTopLeft(find.text(title).last);
|
||||
// The title widget should be to the right of the leading widget.
|
||||
expect(titleOffset.dx, greaterThan(leadingOffset.dx));
|
||||
|
||||
titleOffset = tester.getTopRight(find.text(title).first);
|
||||
titleOffset = tester.getTopRight(find.text(title).last);
|
||||
final Offset searchOffset = tester.getTopLeft(find.byIcon(Icons.search));
|
||||
// The title widget should be to the left of the search icon.
|
||||
expect(titleOffset.dx, lessThan(searchOffset.dx));
|
||||
@ -4100,6 +4127,7 @@ void main() {
|
||||
onPressed: () {},
|
||||
),
|
||||
title: const Text(title, maxLines: 1),
|
||||
centerTitle: true,
|
||||
actions: const <Widget>[
|
||||
Icon(Icons.search),
|
||||
Icon(Icons.sort),
|
||||
@ -4124,11 +4152,11 @@ void main() {
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final Offset leadingOffset = tester.getTopRight(find.byIcon(Icons.menu));
|
||||
Offset titleOffset = tester.getTopLeft(find.text(title).first);
|
||||
Offset titleOffset = tester.getTopLeft(find.text(title).last);
|
||||
// The title widget should be to the right of the leading widget.
|
||||
expect(titleOffset.dx, greaterThan(leadingOffset.dx));
|
||||
|
||||
titleOffset = tester.getTopRight(find.text(title).first);
|
||||
titleOffset = tester.getTopRight(find.text(title).last);
|
||||
final Offset searchOffset = tester.getTopLeft(find.byIcon(Icons.search));
|
||||
// The title widget should be to the left of the search icon.
|
||||
expect(titleOffset.dx, lessThan(searchOffset.dx));
|
||||
@ -4144,18 +4172,21 @@ void main() {
|
||||
body: CustomScrollView(
|
||||
primary: true,
|
||||
slivers: <Widget>[
|
||||
SliverAppBar.medium(
|
||||
centerTitle: centerTitle,
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.menu),
|
||||
onPressed: () {},
|
||||
SliverPadding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 200),
|
||||
sliver: SliverAppBar.medium(
|
||||
leading: IconButton(
|
||||
onPressed: () {},
|
||||
icon: const Icon(Icons.menu),
|
||||
),
|
||||
title: const Text(title, maxLines: 1),
|
||||
centerTitle: centerTitle,
|
||||
titleSpacing: titleSpacing,
|
||||
actions: <Widget>[
|
||||
IconButton(onPressed: () {}, icon: const Icon(Icons.sort)),
|
||||
IconButton(onPressed: () {}, icon: const Icon(Icons.more_vert)),
|
||||
],
|
||||
),
|
||||
title: const Text(title, maxLines: 1),
|
||||
titleSpacing: titleSpacing,
|
||||
actions: const <Widget>[
|
||||
Icon(Icons.sort),
|
||||
Icon(Icons.more_vert),
|
||||
],
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: Container(
|
||||
@ -4171,55 +4202,57 @@ void main() {
|
||||
|
||||
await tester.pumpWidget(buildWidget());
|
||||
|
||||
final Finder collapsedTitle = find.text(title).last;
|
||||
|
||||
// Scroll to collapse the SliverAppBar.
|
||||
ScrollController controller = primaryScrollController(tester);
|
||||
controller.jumpTo(45);
|
||||
controller.jumpTo(120);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// By default, title widget should be to the right of the
|
||||
// leading widget and title spacing should be respected.
|
||||
Offset titleOffset = tester.getTopLeft(find.text(title).first);
|
||||
Offset iconOffset = tester.getTopRight(find.byIcon(Icons.menu));
|
||||
expect(titleOffset.dx, iconOffset.dx + titleSpacing);
|
||||
Offset titleOffset = tester.getTopLeft(collapsedTitle);
|
||||
Offset iconButtonOffset = tester.getTopRight(find.widgetWithIcon(IconButton, Icons.menu));
|
||||
expect(titleOffset.dx, iconButtonOffset.dx + titleSpacing);
|
||||
|
||||
await tester.pumpWidget(buildWidget(centerTitle: true));
|
||||
// Scroll to collapse the SliverAppBar.
|
||||
controller = primaryScrollController(tester);
|
||||
controller.jumpTo(45);
|
||||
controller.jumpTo(120);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// By default, title widget should be to the left of the first
|
||||
// leading widget and title spacing should be respected.
|
||||
titleOffset = tester.getTopRight(find.text(title).first);
|
||||
iconOffset = tester.getTopLeft(find.byIcon(Icons.sort));
|
||||
expect(titleOffset.dx, iconOffset.dx - titleSpacing);
|
||||
// trailing widget and title spacing should be respected.
|
||||
titleOffset = tester.getTopRight(collapsedTitle);
|
||||
iconButtonOffset = tester.getTopLeft(find.widgetWithIcon(IconButton, Icons.sort));
|
||||
expect(titleOffset.dx, iconButtonOffset.dx - titleSpacing);
|
||||
|
||||
// Test custom title spacing, set to 0.0.
|
||||
await tester.pumpWidget(buildWidget(titleSpacing: 0.0));
|
||||
// Scroll to collapse the SliverAppBar.
|
||||
controller = primaryScrollController(tester);
|
||||
controller.jumpTo(45);
|
||||
controller.jumpTo(120);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// The title widget should be to the right of the leading
|
||||
// widget with no spacing.
|
||||
titleOffset = tester.getTopLeft(find.text(title).first);
|
||||
iconOffset = tester.getTopRight(find.byIcon(Icons.menu));
|
||||
expect(titleOffset.dx, iconOffset.dx);
|
||||
titleOffset = tester.getTopLeft(collapsedTitle);
|
||||
iconButtonOffset = tester.getTopRight(find.widgetWithIcon(IconButton, Icons.menu));
|
||||
expect(titleOffset.dx, iconButtonOffset.dx);
|
||||
|
||||
// Set centerTitle to true so the end of the title can reach
|
||||
// the action widgets.
|
||||
await tester.pumpWidget(buildWidget(titleSpacing: 0.0, centerTitle: true));
|
||||
// Scroll to collapse the SliverAppBar.
|
||||
controller = primaryScrollController(tester);
|
||||
controller.jumpTo(45);
|
||||
controller.jumpTo(120);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// The title widget should be to the left of the first
|
||||
// leading widget with no spacing.
|
||||
titleOffset = tester.getTopRight(find.text(title).first);
|
||||
iconOffset = tester.getTopLeft(find.byIcon(Icons.sort));
|
||||
expect(titleOffset.dx, iconOffset.dx);
|
||||
titleOffset = tester.getTopRight(collapsedTitle);
|
||||
iconButtonOffset = tester.getTopLeft(find.widgetWithIcon(IconButton, Icons.sort));
|
||||
expect(titleOffset.dx, iconButtonOffset.dx);
|
||||
});
|
||||
|
||||
testWidgets('SliverAppBar.large respects title spacing', (WidgetTester tester) async {
|
||||
@ -4232,18 +4265,21 @@ void main() {
|
||||
body: CustomScrollView(
|
||||
primary: true,
|
||||
slivers: <Widget>[
|
||||
SliverAppBar.large(
|
||||
centerTitle: centerTitle,
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.menu),
|
||||
onPressed: () {},
|
||||
SliverPadding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 200),
|
||||
sliver: SliverAppBar.large(
|
||||
leading: IconButton(
|
||||
onPressed: () {},
|
||||
icon: const Icon(Icons.menu),
|
||||
),
|
||||
title: const Text(title, maxLines: 1),
|
||||
centerTitle: centerTitle,
|
||||
titleSpacing: titleSpacing,
|
||||
actions: <Widget>[
|
||||
IconButton(onPressed: () {}, icon: const Icon(Icons.sort)),
|
||||
IconButton(onPressed: () {}, icon: const Icon(Icons.more_vert)),
|
||||
],
|
||||
),
|
||||
title: const Text(title, maxLines: 1),
|
||||
titleSpacing: titleSpacing,
|
||||
actions: const <Widget>[
|
||||
Icon(Icons.sort),
|
||||
Icon(Icons.more_vert),
|
||||
],
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: Container(
|
||||
@ -4259,61 +4295,63 @@ void main() {
|
||||
|
||||
await tester.pumpWidget(buildWidget());
|
||||
|
||||
final Finder collapsedTitle = find.text(title).last;
|
||||
|
||||
// Scroll to collapse the SliverAppBar.
|
||||
ScrollController controller = primaryScrollController(tester);
|
||||
controller.jumpTo(45);
|
||||
controller.jumpTo(160);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// By default, title widget should be to the right of the leading
|
||||
// widget and title spacing should be respected.
|
||||
Offset titleOffset = tester.getTopLeft(find.text(title).first);
|
||||
Offset iconOffset = tester.getTopRight(find.byIcon(Icons.menu));
|
||||
expect(titleOffset.dx, iconOffset.dx + titleSpacing);
|
||||
Offset titleOffset = tester.getTopLeft(collapsedTitle);
|
||||
Offset iconButtonOffset = tester.getTopRight(find.widgetWithIcon(IconButton, Icons.menu));
|
||||
expect(titleOffset.dx, iconButtonOffset.dx + titleSpacing);
|
||||
|
||||
await tester.pumpWidget(buildWidget(centerTitle: true));
|
||||
// Scroll to collapse the SliverAppBar.
|
||||
controller = primaryScrollController(tester);
|
||||
controller.jumpTo(45);
|
||||
controller.jumpTo(160);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// By default, title widget should be to the right of the
|
||||
// By default, title widget should be to the left of the
|
||||
// leading widget and title spacing should be respected.
|
||||
titleOffset = tester.getTopRight(find.text(title).first);
|
||||
iconOffset = tester.getTopLeft(find.byIcon(Icons.sort));
|
||||
expect(titleOffset.dx, iconOffset.dx - titleSpacing);
|
||||
titleOffset = tester.getTopRight(collapsedTitle);
|
||||
iconButtonOffset = tester.getTopLeft(find.widgetWithIcon(IconButton, Icons.sort));
|
||||
expect(titleOffset.dx, iconButtonOffset.dx - titleSpacing);
|
||||
|
||||
// Test custom title spacing, set to 0.0.
|
||||
await tester.pumpWidget(buildWidget(titleSpacing: 0.0));
|
||||
controller = primaryScrollController(tester);
|
||||
controller.jumpTo(45);
|
||||
controller.jumpTo(160);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// The title widget should be to the right of the leading
|
||||
// widget with no spacing.
|
||||
titleOffset = tester.getTopLeft(find.text(title).first);
|
||||
iconOffset = tester.getTopRight(find.byIcon(Icons.menu));
|
||||
expect(titleOffset.dx, iconOffset.dx);
|
||||
titleOffset = tester.getTopLeft(collapsedTitle);
|
||||
iconButtonOffset = tester.getTopRight(find.widgetWithIcon(IconButton, Icons.menu));
|
||||
expect(titleOffset.dx, iconButtonOffset.dx);
|
||||
|
||||
// Set centerTitle to true so the end of the title can reach
|
||||
// the action widgets.
|
||||
await tester.pumpWidget(buildWidget(titleSpacing: 0.0, centerTitle: true));
|
||||
// Scroll to collapse the SliverAppBar.
|
||||
controller = primaryScrollController(tester);
|
||||
controller.jumpTo(45);
|
||||
controller.jumpTo(160);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// The title widget should be to the left of the first
|
||||
// leading widget with no spacing.
|
||||
titleOffset = tester.getTopRight(find.text(title).first);
|
||||
iconOffset = tester.getTopLeft(find.byIcon(Icons.sort));
|
||||
expect(titleOffset.dx, iconOffset.dx);
|
||||
titleOffset = tester.getTopRight(collapsedTitle);
|
||||
iconButtonOffset = tester.getTopLeft(find.widgetWithIcon(IconButton, Icons.sort));
|
||||
expect(titleOffset.dx, iconButtonOffset.dx);
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'SliverAppBar.medium without the leading widget updates collapsed title padding',
|
||||
(WidgetTester widgetTester) async {
|
||||
(WidgetTester tester) async {
|
||||
const String title = 'Medium SliverAppBar Title';
|
||||
const double leadingPadding = 40.0;
|
||||
const double leadingPadding = 56.0;
|
||||
const double titleSpacing = 16.0;
|
||||
|
||||
Widget buildWidget({ bool showLeading = true }) {
|
||||
@ -4323,6 +4361,7 @@ void main() {
|
||||
primary: true,
|
||||
slivers: <Widget>[
|
||||
SliverAppBar.medium(
|
||||
automaticallyImplyLeading: false,
|
||||
leading: showLeading
|
||||
? IconButton(
|
||||
icon: const Icon(Icons.menu),
|
||||
@ -4343,36 +4382,38 @@ void main() {
|
||||
);
|
||||
}
|
||||
|
||||
await widgetTester.pumpWidget(buildWidget());
|
||||
await tester.pumpWidget(buildWidget());
|
||||
|
||||
final Finder collapsedTitle = find.text(title).last;
|
||||
|
||||
// Scroll to collapse the SliverAppBar.
|
||||
ScrollController controller = primaryScrollController(widgetTester);
|
||||
ScrollController controller = primaryScrollController(tester);
|
||||
controller.jumpTo(45);
|
||||
await widgetTester.pumpAndSettle();
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// If the leading widget is present, the title widget should be to the
|
||||
// right of the leading widget and title spacing should be respected.
|
||||
Offset titleOffset = widgetTester.getTopLeft(find.text(title).first);
|
||||
Offset titleOffset = tester.getTopLeft(collapsedTitle);
|
||||
expect(titleOffset.dx, leadingPadding + titleSpacing);
|
||||
|
||||
// Hide the leading widget.
|
||||
await widgetTester.pumpWidget(buildWidget(showLeading: false));
|
||||
await tester.pumpWidget(buildWidget(showLeading: false));
|
||||
// Scroll to collapse the SliverAppBar.
|
||||
controller = primaryScrollController(widgetTester);
|
||||
controller = primaryScrollController(tester);
|
||||
controller.jumpTo(45);
|
||||
await widgetTester.pumpAndSettle();
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// If the leading widget is not present, the title widget will
|
||||
// only have the default title spacing.
|
||||
titleOffset = widgetTester.getTopLeft(find.text(title).first);
|
||||
titleOffset = tester.getTopLeft(collapsedTitle);
|
||||
expect(titleOffset.dx, titleSpacing);
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'SliverAppBar.large without the leading widget updates collapsed title padding',
|
||||
(WidgetTester widgetTester) async {
|
||||
(WidgetTester tester) async {
|
||||
const String title = 'Large SliverAppBar Title';
|
||||
const double leadingPadding = 40.0;
|
||||
const double leadingPadding = 56.0;
|
||||
const double titleSpacing = 16.0;
|
||||
|
||||
Widget buildWidget({ bool showLeading = true }) {
|
||||
@ -4382,6 +4423,7 @@ void main() {
|
||||
primary: true,
|
||||
slivers: <Widget>[
|
||||
SliverAppBar.large(
|
||||
automaticallyImplyLeading: false,
|
||||
leading: showLeading
|
||||
? IconButton(
|
||||
icon: const Icon(Icons.menu),
|
||||
@ -4402,31 +4444,211 @@ void main() {
|
||||
);
|
||||
}
|
||||
|
||||
await widgetTester.pumpWidget(buildWidget());
|
||||
await tester.pumpWidget(buildWidget());
|
||||
|
||||
final Finder collapsedTitle = find.text(title).last;
|
||||
|
||||
// Scroll CustomScrollView to collapse SliverAppBar.
|
||||
ScrollController controller = primaryScrollController(widgetTester);
|
||||
ScrollController controller = primaryScrollController(tester);
|
||||
controller.jumpTo(45);
|
||||
await widgetTester.pumpAndSettle();
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// If the leading widget is present, the title widget should be to the
|
||||
// right of the leading widget and title spacing should be respected.
|
||||
Offset titleOffset = widgetTester.getTopLeft(find.text(title).first);
|
||||
Offset titleOffset = tester.getTopLeft(collapsedTitle);
|
||||
expect(titleOffset.dx, leadingPadding + titleSpacing);
|
||||
|
||||
// Hide the leading widget.
|
||||
await widgetTester.pumpWidget(buildWidget(showLeading: false));
|
||||
await tester.pumpWidget(buildWidget(showLeading: false));
|
||||
// Scroll to collapse the SliverAppBar.
|
||||
controller = primaryScrollController(widgetTester);
|
||||
controller = primaryScrollController(tester);
|
||||
controller.jumpTo(45);
|
||||
await widgetTester.pumpAndSettle();
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// If the leading widget is not present, the title widget will
|
||||
// only have the default title spacing.
|
||||
titleOffset = widgetTester.getTopLeft(find.text(title).first);
|
||||
titleOffset = tester.getTopLeft(collapsedTitle);
|
||||
expect(titleOffset.dx, titleSpacing);
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'SliverAppBar large & medium title respects automaticallyImplyLeading',
|
||||
(WidgetTester tester) async {
|
||||
// This is a regression test for https://github.com/flutter/flutter/issues/121511
|
||||
const String title = 'AppBar Title';
|
||||
const double titleSpacing = 16.0;
|
||||
|
||||
Widget buildWidget() {
|
||||
return MaterialApp(
|
||||
home: Scaffold(
|
||||
body: Builder(
|
||||
builder: (BuildContext context) {
|
||||
return Center(
|
||||
child: FilledButton(
|
||||
onPressed: () {
|
||||
Navigator.push(context, MaterialPageRoute<void>(
|
||||
builder: (BuildContext context) {
|
||||
return Scaffold(
|
||||
body: CustomScrollView(
|
||||
primary: true,
|
||||
slivers: <Widget>[
|
||||
const SliverAppBar.large(
|
||||
title: Text(title),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: Container(
|
||||
height: 1200,
|
||||
color: Colors.orange[400],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
));
|
||||
},
|
||||
child: const Text('Go to page'),
|
||||
),
|
||||
);
|
||||
}
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
await tester.pumpWidget(buildWidget());
|
||||
|
||||
expect(find.byType(BackButton), findsNothing);
|
||||
|
||||
await tester.tap(find.byType(FilledButton));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final Finder collapsedTitle = find.text(title).last;
|
||||
final Offset backButtonOffset = tester.getTopRight(find.byType(BackButton));
|
||||
final Offset titleOffset = tester.getTopLeft(collapsedTitle);
|
||||
expect(titleOffset.dx, backButtonOffset.dx + titleSpacing);
|
||||
});
|
||||
|
||||
testWidgets('SliverAppBar.medium with bottom widget', (WidgetTester tester) async {
|
||||
// This is a regression test for https://github.com/flutter/flutter/issues/115091
|
||||
const double collapsedAppBarHeight = 64;
|
||||
const double expandedAppBarHeight = 112;
|
||||
const double bottomHeight = 48;
|
||||
const String title = 'Medium App Bar';
|
||||
|
||||
Widget buildWidget() {
|
||||
return MaterialApp(
|
||||
home: DefaultTabController(
|
||||
length: 3,
|
||||
child: Scaffold(
|
||||
body: CustomScrollView(
|
||||
primary: true,
|
||||
slivers: <Widget>[
|
||||
SliverAppBar.medium(
|
||||
leading: IconButton(
|
||||
onPressed: () {},
|
||||
icon: const Icon(Icons.menu),
|
||||
),
|
||||
title: const Text(title),
|
||||
bottom: const TabBar(
|
||||
tabs: <Widget>[
|
||||
Tab(text: 'Tab 1'),
|
||||
Tab(text: 'Tab 2'),
|
||||
Tab(text: 'Tab 3'),
|
||||
],
|
||||
),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: Container(
|
||||
height: 1200,
|
||||
color: Colors.orange[400],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
await tester.pumpWidget(buildWidget());
|
||||
|
||||
expect(find.byType(SliverAppBar), findsOneWidget);
|
||||
expect(appBarHeight(tester), expandedAppBarHeight + bottomHeight);
|
||||
|
||||
final Finder expandedTitle = find.text(title).first;
|
||||
final Offset expandedTitleOffset = tester.getBottomLeft(expandedTitle);
|
||||
final Offset tabOffset = tester.getTopLeft(find.byType(TabBar));
|
||||
expect(expandedTitleOffset.dy, tabOffset.dy);
|
||||
|
||||
// Scroll CustomScrollView to collapse SliverAppBar.
|
||||
final ScrollController controller = primaryScrollController(tester);
|
||||
controller.jumpTo(160);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(appBarHeight(tester), collapsedAppBarHeight + bottomHeight);
|
||||
});
|
||||
|
||||
testWidgets('SliverAppBar.large with bottom widget', (WidgetTester tester) async {
|
||||
// This is a regression test for https://github.com/flutter/flutter/issues/115091
|
||||
const double collapsedAppBarHeight = 64;
|
||||
const double expandedAppBarHeight = 152;
|
||||
const double bottomHeight = 48;
|
||||
const String title = 'Large App Bar';
|
||||
|
||||
Widget buildWidget() {
|
||||
return MaterialApp(
|
||||
home: DefaultTabController(
|
||||
length: 3,
|
||||
child: Scaffold(
|
||||
body: CustomScrollView(
|
||||
primary: true,
|
||||
slivers: <Widget>[
|
||||
SliverAppBar.large(
|
||||
leading: IconButton(
|
||||
onPressed: () {},
|
||||
icon: const Icon(Icons.menu),
|
||||
),
|
||||
title: const Text(title),
|
||||
bottom: const TabBar(
|
||||
tabs: <Widget>[
|
||||
Tab(text: 'Tab 1'),
|
||||
Tab(text: 'Tab 2'),
|
||||
Tab(text: 'Tab 3'),
|
||||
],
|
||||
),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: Container(
|
||||
height: 1200,
|
||||
color: Colors.orange[400],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
await tester.pumpWidget(buildWidget());
|
||||
|
||||
expect(find.byType(SliverAppBar), findsOneWidget);
|
||||
expect(appBarHeight(tester), expandedAppBarHeight + bottomHeight);
|
||||
|
||||
final Finder expandedTitle = find.text(title).first;
|
||||
final Offset expandedTitleOffset = tester.getBottomLeft(expandedTitle);
|
||||
final Offset tabOffset = tester.getTopLeft(find.byType(TabBar));
|
||||
expect(expandedTitleOffset.dy, tabOffset.dy);
|
||||
|
||||
// Scroll CustomScrollView to collapse SliverAppBar.
|
||||
final ScrollController controller = primaryScrollController(tester);
|
||||
controller.jumpTo(200);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(appBarHeight(tester), collapsedAppBarHeight + bottomHeight);
|
||||
});
|
||||
|
||||
group('AppBar.forceMaterialTransparency', () {
|
||||
Material getAppBarMaterial(WidgetTester tester) {
|
||||
return tester.widget<Material>(find
|
||||
@ -4501,4 +4723,177 @@ void main() {
|
||||
expect(buttonWasPressed, isFalse);
|
||||
});
|
||||
});
|
||||
|
||||
group('Material 2', () {
|
||||
// Tests that are only relevant for Material 2. Once ThemeData.useMaterial3
|
||||
// is turned on by default, these tests can be removed.
|
||||
|
||||
testWidgets('SliverAppBar.medium defaults', (WidgetTester tester) async {
|
||||
final ThemeData theme = ThemeData(useMaterial3: false);
|
||||
const double collapsedAppBarHeight = 64;
|
||||
const double expandedAppBarHeight = 112;
|
||||
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
theme: theme,
|
||||
home: Scaffold(
|
||||
body: CustomScrollView(
|
||||
primary: true,
|
||||
slivers: <Widget>[
|
||||
const SliverAppBar.medium(
|
||||
title: 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 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. The last is the title on the mainrow with the icons. It is
|
||||
// transparent when the app bar is expanded, and opaque when it is collapsed.
|
||||
final Finder expandedTitle = find.text('AppBar Title').first;
|
||||
final Finder expandedTitleClip = find.ancestor(
|
||||
of: expandedTitle,
|
||||
matching: find.byType(ClipRect),
|
||||
);
|
||||
final Finder collapsedTitle = find.text('AppBar Title').last;
|
||||
final Finder collapsedTitleOpacity = find.ancestor(
|
||||
of: collapsedTitle,
|
||||
matching: find.byType(AnimatedOpacity),
|
||||
);
|
||||
|
||||
// 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);
|
||||
|
||||
// Test the expanded title is positioned correctly.
|
||||
final Offset titleOffset = tester.getBottomLeft(expandedTitle);
|
||||
expect(titleOffset, const Offset(16.0, 92.0));
|
||||
|
||||
// Test the expanded title default color.
|
||||
expect(
|
||||
tester.renderObject<RenderParagraph>(expandedTitle).text.style!.color,
|
||||
theme.colorScheme.onPrimary,
|
||||
);
|
||||
|
||||
// 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 {
|
||||
final ThemeData theme = ThemeData(useMaterial3: false);
|
||||
const double collapsedAppBarHeight = 64;
|
||||
const double expandedAppBarHeight = 152;
|
||||
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
theme: ThemeData(useMaterial3: false),
|
||||
home: Scaffold(
|
||||
body: CustomScrollView(
|
||||
primary: true,
|
||||
slivers: <Widget>[
|
||||
const SliverAppBar.large(
|
||||
title: 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 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. The last is the title on the mainrow with the icons. It is
|
||||
// transparent when the app bar is expanded, and opaque when it is collapsed.
|
||||
final Finder expandedTitle = find.text('AppBar Title').first;
|
||||
final Finder expandedTitleClip = find.ancestor(
|
||||
of: expandedTitle,
|
||||
matching: find.byType(ClipRect),
|
||||
);
|
||||
final Finder collapsedTitle = find.text('AppBar Title').last;
|
||||
final Finder collapsedTitleOpacity = find.ancestor(
|
||||
of: collapsedTitle,
|
||||
matching: find.byType(AnimatedOpacity),
|
||||
);
|
||||
|
||||
// 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);
|
||||
|
||||
// Test the expanded title is positioned correctly.
|
||||
final Offset titleOffset = tester.getBottomLeft(expandedTitle);
|
||||
expect(titleOffset, const Offset(16.0, 124.0));
|
||||
|
||||
// Test the expanded title default color.
|
||||
expect(
|
||||
tester.renderObject<RenderParagraph>(expandedTitle).text.style!.color,
|
||||
theme.colorScheme.onPrimary,
|
||||
);
|
||||
|
||||
// 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);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ -9,6 +9,25 @@ import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
const AppBarTheme appBarTheme = AppBarTheme(
|
||||
backgroundColor: Color(0xff00ff00),
|
||||
foregroundColor: Color(0xff00ffff),
|
||||
elevation: 4.0,
|
||||
scrolledUnderElevation: 6.0,
|
||||
shadowColor: Color(0xff1212ff),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(14.0)),
|
||||
),
|
||||
iconTheme: IconThemeData(color: Color(0xffff0000)),
|
||||
actionsIconTheme: IconThemeData(color: Color(0xff0000ff)),
|
||||
centerTitle: false,
|
||||
titleSpacing: 10.0,
|
||||
titleTextStyle: TextStyle(
|
||||
fontSize: 22.0,
|
||||
fontStyle: FontStyle.italic,
|
||||
),
|
||||
);
|
||||
|
||||
ScrollController primaryScrollController(WidgetTester tester) {
|
||||
return PrimaryScrollController.of(tester.element(find.byType(CustomScrollView)));
|
||||
}
|
||||
@ -681,18 +700,10 @@ void main() {
|
||||
});
|
||||
|
||||
testWidgets('SliverAppBar.medium uses AppBarTheme properties', (WidgetTester tester) async {
|
||||
const String title = 'Medium SliverAppBar Title';
|
||||
const Color foregroundColor = Color(0xff00ff00);
|
||||
const double titleSpacing = 10.0;
|
||||
const String title = 'Medium App Bar';
|
||||
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
theme: ThemeData(
|
||||
appBarTheme: const AppBarTheme(
|
||||
foregroundColor: foregroundColor,
|
||||
titleSpacing: titleSpacing,
|
||||
centerTitle: false,
|
||||
),
|
||||
),
|
||||
theme: ThemeData(appBarTheme: appBarTheme),
|
||||
home: CustomScrollView(
|
||||
primary: true,
|
||||
slivers: <Widget>[
|
||||
@ -702,82 +713,130 @@ void main() {
|
||||
icon: const Icon(Icons.menu),
|
||||
),
|
||||
title: const Text(title),
|
||||
actions: <Widget>[
|
||||
IconButton(
|
||||
onPressed: () {},
|
||||
icon: const Icon(Icons.search),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
));
|
||||
|
||||
final RichText text = tester.firstWidget(find.byType(RichText));
|
||||
expect(text.text.style!.color, foregroundColor);
|
||||
// Test title.
|
||||
final RichText titleText = tester.firstWidget(find.byType(RichText));
|
||||
expect(titleText.text.style!.fontSize, appBarTheme.titleTextStyle!.fontSize);
|
||||
expect(titleText.text.style!.fontStyle, appBarTheme.titleTextStyle!.fontStyle);
|
||||
|
||||
// Test background color, shadow color, and shape.
|
||||
final Material material = tester.widget<Material>(
|
||||
find.descendant(
|
||||
of: find.byType(SliverAppBar),
|
||||
matching: find.byType(Material).first,
|
||||
),
|
||||
);
|
||||
expect(material.color, appBarTheme.backgroundColor);
|
||||
expect(material.shadowColor, appBarTheme.shadowColor);
|
||||
expect(material.shape, appBarTheme.shape);
|
||||
|
||||
final RichText actionIcon = tester.widget(find.byType(RichText).last);
|
||||
expect(actionIcon.text.style!.color, appBarTheme.actionsIconTheme!.color);
|
||||
|
||||
// Scroll to collapse the SliverAppBar.
|
||||
final ScrollController controller = primaryScrollController(tester);
|
||||
controller.jumpTo(45);
|
||||
controller.jumpTo(120);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final Offset titleOffset = tester.getTopLeft(find.text(title).first);
|
||||
final Offset iconOffset = tester.getTopRight(find.byIcon(Icons.menu));
|
||||
// Title spacing should be 10.0.
|
||||
expect(titleOffset.dx, iconOffset.dx + titleSpacing);
|
||||
// Test title spacing.
|
||||
final Finder collapsedTitle = find.text(title).last;
|
||||
final Offset titleOffset = tester.getTopLeft(collapsedTitle);
|
||||
final Offset iconOffset = tester.getTopRight(find.widgetWithIcon(IconButton, Icons.menu));
|
||||
expect(titleOffset.dx, iconOffset.dx + appBarTheme.titleSpacing!);
|
||||
});
|
||||
|
||||
testWidgets('SliverAppBar.medium properties take priority over AppBarTheme properties', (WidgetTester tester) async {
|
||||
const String title = 'Medium SliverAppBar Title';
|
||||
const Color foregroundColor = Color(0xff00ff00);
|
||||
const double titleSpacing = 10.0;
|
||||
const String title = 'Medium App Bar';
|
||||
const Color backgroundColor = Color(0xff000099);
|
||||
const Color foregroundColor = Color(0xff00ff98);
|
||||
const Color shadowColor = Color(0xff00ff97);
|
||||
const ShapeBorder shape = RoundedRectangleBorder(
|
||||
borderRadius: BorderRadiusDirectional.only(bottomStart: Radius.circular(12.0)),
|
||||
);
|
||||
const IconThemeData iconTheme = IconThemeData(color: Color(0xff00ff96));
|
||||
const IconThemeData actionsIconTheme = IconThemeData(color: Color(0xff00ff95));
|
||||
const double titleSpacing = 18.0;
|
||||
const TextStyle titleTextStyle = TextStyle(
|
||||
fontSize: 22.9,
|
||||
fontStyle: FontStyle.italic,
|
||||
);
|
||||
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
theme: ThemeData(
|
||||
appBarTheme: const AppBarTheme(
|
||||
foregroundColor: Color(0xffff0000),
|
||||
titleSpacing: 14.0,
|
||||
centerTitle: true,
|
||||
),
|
||||
),
|
||||
theme: ThemeData(appBarTheme: appBarTheme),
|
||||
home: CustomScrollView(
|
||||
primary: true,
|
||||
slivers: <Widget>[
|
||||
SliverAppBar.medium(
|
||||
centerTitle: false,
|
||||
titleSpacing: titleSpacing,
|
||||
backgroundColor: backgroundColor,
|
||||
foregroundColor: foregroundColor,
|
||||
shadowColor: shadowColor,
|
||||
shape: shape,
|
||||
iconTheme: iconTheme,
|
||||
actionsIconTheme: actionsIconTheme,
|
||||
titleSpacing: titleSpacing,
|
||||
titleTextStyle: titleTextStyle,
|
||||
leading: IconButton(
|
||||
onPressed: () {},
|
||||
icon: const Icon(Icons.menu),
|
||||
),
|
||||
title: const Text(title),
|
||||
actions: <Widget>[
|
||||
IconButton(
|
||||
onPressed: () {},
|
||||
icon: const Icon(Icons.search),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
));
|
||||
|
||||
final RichText text = tester.firstWidget(find.byType(RichText));
|
||||
expect(text.text.style!.color, foregroundColor);
|
||||
// Test title.
|
||||
final RichText titleText = tester.firstWidget(find.byType(RichText));
|
||||
expect(titleText.text.style, titleTextStyle);
|
||||
|
||||
// Test background color, shadow color, and shape.
|
||||
final Material material = tester.widget<Material>(
|
||||
find.descendant(
|
||||
of: find.byType(SliverAppBar),
|
||||
matching: find.byType(Material).first,
|
||||
),
|
||||
);
|
||||
expect(material.color, backgroundColor);
|
||||
expect(material.shadowColor, shadowColor);
|
||||
expect(material.shape, shape);
|
||||
|
||||
final RichText actionIcon = tester.widget(find.byType(RichText).last);
|
||||
expect(actionIcon.text.style!.color, actionsIconTheme.color);
|
||||
|
||||
// Scroll to collapse the SliverAppBar.
|
||||
final ScrollController controller = primaryScrollController(tester);
|
||||
controller.jumpTo(45);
|
||||
controller.jumpTo(120);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final Offset titleOffset = tester.getTopLeft(find.text(title).first);
|
||||
final Offset iconOffset = tester.getTopRight(find.byIcon(Icons.menu));
|
||||
// Title spacing should be 10.0.
|
||||
// Test title spacing.
|
||||
final Finder collapsedTitle = find.text(title).last;
|
||||
final Offset titleOffset = tester.getTopLeft(collapsedTitle);
|
||||
final Offset iconOffset = tester.getTopRight(find.widgetWithIcon(IconButton, Icons.menu));
|
||||
expect(titleOffset.dx, iconOffset.dx + titleSpacing);
|
||||
});
|
||||
|
||||
testWidgets('SliverAppBar.large uses AppBarTheme properties', (WidgetTester tester) async {
|
||||
const String title = 'Large SliverAppBar Title';
|
||||
const Color foregroundColor = Color(0xff00ff00);
|
||||
const double titleSpacing = 10.0;
|
||||
const String title = 'Large App Bar';
|
||||
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
theme: ThemeData(
|
||||
appBarTheme: const AppBarTheme(
|
||||
foregroundColor: foregroundColor,
|
||||
titleSpacing: titleSpacing,
|
||||
centerTitle: false,
|
||||
),
|
||||
),
|
||||
theme: ThemeData(appBarTheme: appBarTheme),
|
||||
home: CustomScrollView(
|
||||
primary: true,
|
||||
slivers: <Widget>[
|
||||
@ -787,69 +846,169 @@ void main() {
|
||||
icon: const Icon(Icons.menu),
|
||||
),
|
||||
title: const Text(title),
|
||||
actions: <Widget>[
|
||||
IconButton(
|
||||
onPressed: () {},
|
||||
icon: const Icon(Icons.search),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
));
|
||||
|
||||
final RichText text = tester.firstWidget(find.byType(RichText));
|
||||
expect(text.text.style!.color, foregroundColor);
|
||||
// Test title.
|
||||
final RichText titleText = tester.firstWidget(find.byType(RichText));
|
||||
expect(titleText.text.style!.fontSize, appBarTheme.titleTextStyle!.fontSize);
|
||||
expect(titleText.text.style!.fontStyle, appBarTheme.titleTextStyle!.fontStyle);
|
||||
|
||||
// Test background color, shadow color, and shape.
|
||||
final Material material = tester.widget<Material>(
|
||||
find.descendant(
|
||||
of: find.byType(SliverAppBar),
|
||||
matching: find.byType(Material).first,
|
||||
),
|
||||
);
|
||||
expect(material.color, appBarTheme.backgroundColor);
|
||||
expect(material.shadowColor, appBarTheme.shadowColor);
|
||||
expect(material.shape, appBarTheme.shape);
|
||||
|
||||
final RichText actionIcon = tester.widget(find.byType(RichText).last);
|
||||
expect(actionIcon.text.style!.color, appBarTheme.actionsIconTheme!.color);
|
||||
|
||||
// Scroll to collapse the SliverAppBar.
|
||||
final ScrollController controller = primaryScrollController(tester);
|
||||
controller.jumpTo(45);
|
||||
controller.jumpTo(120);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final Offset titleOffset = tester.getTopLeft(find.text(title).first);
|
||||
final Offset iconOffset = tester.getTopRight(find.byIcon(Icons.menu));
|
||||
// Title spacing should be 10.0.
|
||||
expect(titleOffset.dx, iconOffset.dx + titleSpacing);
|
||||
// Test title spacing.
|
||||
final Finder collapsedTitle = find.text(title).last;
|
||||
final Offset titleOffset = tester.getTopLeft(collapsedTitle);
|
||||
final Offset iconOffset = tester.getTopRight(find.widgetWithIcon(IconButton, Icons.menu));
|
||||
expect(titleOffset.dx, iconOffset.dx + appBarTheme.titleSpacing!);
|
||||
});
|
||||
|
||||
testWidgets('SliverAppBar.large properties take priority over AppBarTheme properties', (WidgetTester tester) async {
|
||||
const String title = 'Large SliverAppBar Title';
|
||||
const Color foregroundColor = Color(0xff00ff00);
|
||||
const double titleSpacing = 10.0;
|
||||
const String title = 'Large App Bar';
|
||||
const Color backgroundColor = Color(0xff000099);
|
||||
const Color foregroundColor = Color(0xff00ff98);
|
||||
const Color shadowColor = Color(0xff00ff97);
|
||||
const ShapeBorder shape = RoundedRectangleBorder(
|
||||
borderRadius: BorderRadiusDirectional.only(bottomStart: Radius.circular(12.0)),
|
||||
);
|
||||
const IconThemeData iconTheme = IconThemeData(color: Color(0xff00ff96));
|
||||
const IconThemeData actionsIconTheme = IconThemeData(color: Color(0xff00ff95));
|
||||
const double titleSpacing = 18.0;
|
||||
const TextStyle titleTextStyle = TextStyle(
|
||||
fontSize: 22.9,
|
||||
fontStyle: FontStyle.italic,
|
||||
);
|
||||
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
theme: ThemeData(
|
||||
appBarTheme: const AppBarTheme(
|
||||
foregroundColor: Color(0xffff0000),
|
||||
titleSpacing: 14.0,
|
||||
centerTitle: true,
|
||||
),
|
||||
),
|
||||
theme: ThemeData(appBarTheme: appBarTheme),
|
||||
home: CustomScrollView(
|
||||
primary: true,
|
||||
slivers: <Widget>[
|
||||
SliverAppBar.large(
|
||||
centerTitle: false,
|
||||
titleSpacing: titleSpacing,
|
||||
backgroundColor: backgroundColor,
|
||||
foregroundColor: foregroundColor,
|
||||
shadowColor: shadowColor,
|
||||
shape: shape,
|
||||
iconTheme: iconTheme,
|
||||
actionsIconTheme: actionsIconTheme,
|
||||
titleSpacing: titleSpacing,
|
||||
titleTextStyle: titleTextStyle,
|
||||
leading: IconButton(
|
||||
onPressed: () {},
|
||||
icon: const Icon(Icons.menu),
|
||||
),
|
||||
title: const Text(title),
|
||||
actions: <Widget>[
|
||||
IconButton(
|
||||
onPressed: () {},
|
||||
icon: const Icon(Icons.search),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
));
|
||||
|
||||
final RichText text = tester.firstWidget(find.byType(RichText));
|
||||
expect(text.text.style!.color, foregroundColor);
|
||||
// Test title.
|
||||
final RichText titleText = tester.firstWidget(find.byType(RichText));
|
||||
expect(titleText.text.style, titleTextStyle);
|
||||
|
||||
// Test background color, shadow color, and shape.
|
||||
final Material material = tester.widget<Material>(
|
||||
find.descendant(
|
||||
of: find.byType(SliverAppBar),
|
||||
matching: find.byType(Material).first,
|
||||
),
|
||||
);
|
||||
expect(material.color, backgroundColor);
|
||||
expect(material.shadowColor, shadowColor);
|
||||
expect(material.shape, shape);
|
||||
|
||||
final RichText actionIcon = tester.widget(find.byType(RichText).last);
|
||||
expect(actionIcon.text.style!.color, actionsIconTheme.color);
|
||||
|
||||
// Scroll to collapse the SliverAppBar.
|
||||
final ScrollController controller = primaryScrollController(tester);
|
||||
controller.jumpTo(45);
|
||||
controller.jumpTo(120);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final Offset titleOffset = tester.getTopLeft(find.text(title).first);
|
||||
final Offset iconOffset = tester.getTopRight(find.byIcon(Icons.menu));
|
||||
// Title spacing should be 10.0.
|
||||
// Test title spacing.
|
||||
final Finder collapsedTitle = find.text(title).last;
|
||||
final Offset titleOffset = tester.getTopLeft(collapsedTitle);
|
||||
final Offset iconOffset = tester.getTopRight(find.widgetWithIcon(IconButton, Icons.menu));
|
||||
expect(titleOffset.dx, iconOffset.dx + titleSpacing);
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'SliverAppBar medium & large supports foregroundColor', (WidgetTester tester) async {
|
||||
const String title = 'AppBar title';
|
||||
const AppBarTheme appBarTheme = AppBarTheme(foregroundColor: Color(0xff00ff20));
|
||||
const Color foregroundColor = Color(0xff001298);
|
||||
|
||||
Widget buildWidget({ Color? color, AppBarTheme? appBarTheme }) {
|
||||
return MaterialApp(
|
||||
theme: ThemeData(appBarTheme: appBarTheme),
|
||||
home: CustomScrollView(
|
||||
primary: true,
|
||||
slivers: <Widget>[
|
||||
SliverAppBar.medium(
|
||||
foregroundColor: color,
|
||||
title: const Text(title),
|
||||
),
|
||||
SliverAppBar.large(
|
||||
foregroundColor: color,
|
||||
title: const Text(title),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
await tester.pumpWidget(buildWidget(appBarTheme: appBarTheme));
|
||||
|
||||
// Test AppBarTheme.foregroundColor parameter.
|
||||
RichText mediumTitle = tester.widget(find.byType(RichText).first);
|
||||
expect(mediumTitle.text.style!.color, appBarTheme.foregroundColor);
|
||||
RichText largeTitle = tester.widget(find.byType(RichText).first);
|
||||
expect(largeTitle.text.style!.color, appBarTheme.foregroundColor);
|
||||
|
||||
await tester.pumpWidget(buildWidget(
|
||||
color: foregroundColor, appBarTheme: appBarTheme),
|
||||
);
|
||||
|
||||
// Test foregroundColor parameter.
|
||||
mediumTitle = tester.widget(find.byType(RichText).first);
|
||||
expect(mediumTitle.text.style!.color, foregroundColor);
|
||||
largeTitle = tester.widget(find.byType(RichText).first);
|
||||
expect(largeTitle.text.style!.color, foregroundColor);
|
||||
});
|
||||
|
||||
testWidgets('Default AppBarTheme debugFillProperties', (WidgetTester tester) async {
|
||||
final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
|
||||
const AppBarTheme().debugFillProperties(builder);
|
||||
|
||||
@ -2720,8 +2720,8 @@ void main() {
|
||||
return <Widget>[
|
||||
SliverOverlapAbsorber(
|
||||
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
||||
sliver: SliverAppBar.medium(
|
||||
title: const Text('AppBar Title'),
|
||||
sliver: const SliverAppBar.medium(
|
||||
title: Text('AppBar Title'),
|
||||
),
|
||||
),
|
||||
];
|
||||
@ -2747,11 +2747,11 @@ void main() {
|
||||
));
|
||||
|
||||
// There are two widgets for the title.
|
||||
final Finder expandedTitle = find.text('AppBar Title').last;
|
||||
final Finder expandedTitle = find.text('AppBar Title').first;
|
||||
final Finder expandedTitleClip = find.ancestor(
|
||||
of: expandedTitle,
|
||||
matching: find.byType(ClipRect),
|
||||
);
|
||||
).first;
|
||||
|
||||
// Default, fully expanded app bar.
|
||||
expect(nestedScrollView.currentState?.outerController.offset, 0);
|
||||
@ -2830,11 +2830,11 @@ void main() {
|
||||
));
|
||||
|
||||
// There are two widgets for the title.
|
||||
final Finder expandedTitle = find.text('AppBar Title').last;
|
||||
final Finder expandedTitle = find.text('AppBar Title').first;
|
||||
final Finder expandedTitleClip = find.ancestor(
|
||||
of: expandedTitle,
|
||||
matching: find.byType(ClipRect),
|
||||
);
|
||||
).first;
|
||||
|
||||
// Default, fully expanded app bar.
|
||||
expect(nestedScrollView.currentState?.outerController.offset, 0);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user