Fix SliverAppBar.medium
& SliverAppBar.large
title overlap with leading/actions widgets, leading width, and title spacing (#120780)
* Fix `SliverAppBar.medium` & `SliverAppBar.large` title overlap with leading/actions widgets, leading width, and title spacing * Add `titleSpacing` theme tests and consolidate multiple tests for the same widgets
This commit is contained in:
parent
af347d6677
commit
56e1bddc59
@ -79,10 +79,10 @@ class _MediumScrollUnderFlexibleConfig with _ScrollUnderFlexibleConfig {
|
|||||||
${textStyle('md.comp.top-app-bar.medium.headline')}?.apply(color: ${color('md.comp.top-app-bar.medium.headline.color')});
|
${textStyle('md.comp.top-app-bar.medium.headline')}?.apply(color: ${color('md.comp.top-app-bar.medium.headline.color')});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
EdgeInsetsGeometry? get collapsedTitlePadding => const EdgeInsetsDirectional.fromSTEB(48, 0, 16, 0);
|
EdgeInsetsGeometry? get collapsedTitlePadding => const EdgeInsetsDirectional.only(start: 40);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
EdgeInsetsGeometry? get collapsedCenteredTitlePadding => const EdgeInsets.fromLTRB(16, 0, 16, 0);
|
EdgeInsetsGeometry? get collapsedCenteredTitlePadding => const EdgeInsetsDirectional.only(start: 40);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
EdgeInsetsGeometry? get expandedTitlePadding => const EdgeInsets.fromLTRB(16, 0, 16, 20);
|
EdgeInsetsGeometry? get expandedTitlePadding => const EdgeInsets.fromLTRB(16, 0, 16, 20);
|
||||||
@ -108,10 +108,10 @@ class _LargeScrollUnderFlexibleConfig with _ScrollUnderFlexibleConfig {
|
|||||||
${textStyle('md.comp.top-app-bar.large.headline')}?.apply(color: ${color('md.comp.top-app-bar.large.headline.color')});
|
${textStyle('md.comp.top-app-bar.large.headline')}?.apply(color: ${color('md.comp.top-app-bar.large.headline.color')});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
EdgeInsetsGeometry? get collapsedTitlePadding => const EdgeInsetsDirectional.fromSTEB(48, 0, 16, 0);
|
EdgeInsetsGeometry? get collapsedTitlePadding => const EdgeInsetsDirectional.only(start: 40);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
EdgeInsetsGeometry? get collapsedCenteredTitlePadding => const EdgeInsets.fromLTRB(16, 0, 16, 0);
|
EdgeInsetsGeometry? get collapsedCenteredTitlePadding => const EdgeInsetsDirectional.only(start: 40);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
EdgeInsetsGeometry? get expandedTitlePadding => const EdgeInsets.fromLTRB(16, 0, 16, 28);
|
EdgeInsetsGeometry? get expandedTitlePadding => const EdgeInsets.fromLTRB(16, 0, 16, 28);
|
||||||
|
@ -1528,13 +1528,16 @@ class SliverAppBar extends StatefulWidget {
|
|||||||
key: key,
|
key: key,
|
||||||
leading: leading,
|
leading: leading,
|
||||||
automaticallyImplyLeading: automaticallyImplyLeading,
|
automaticallyImplyLeading: automaticallyImplyLeading,
|
||||||
actions: actions,
|
|
||||||
flexibleSpace: flexibleSpace ?? _ScrollUnderFlexibleSpace(
|
flexibleSpace: flexibleSpace ?? _ScrollUnderFlexibleSpace(
|
||||||
|
hasLeading: leading != null,
|
||||||
title: title,
|
title: title,
|
||||||
|
actions: actions,
|
||||||
foregroundColor: foregroundColor,
|
foregroundColor: foregroundColor,
|
||||||
variant: _ScrollUnderFlexibleVariant.medium,
|
variant: _ScrollUnderFlexibleVariant.medium,
|
||||||
centerCollapsedTitle: centerTitle,
|
centerCollapsedTitle: centerTitle,
|
||||||
primary: primary,
|
primary: primary,
|
||||||
|
leadingWidth: leadingWidth,
|
||||||
|
titleSpacing: titleSpacing,
|
||||||
),
|
),
|
||||||
bottom: bottom,
|
bottom: bottom,
|
||||||
elevation: elevation,
|
elevation: elevation,
|
||||||
@ -1630,13 +1633,16 @@ class SliverAppBar extends StatefulWidget {
|
|||||||
key: key,
|
key: key,
|
||||||
leading: leading,
|
leading: leading,
|
||||||
automaticallyImplyLeading: automaticallyImplyLeading,
|
automaticallyImplyLeading: automaticallyImplyLeading,
|
||||||
actions: actions,
|
|
||||||
flexibleSpace: flexibleSpace ?? _ScrollUnderFlexibleSpace(
|
flexibleSpace: flexibleSpace ?? _ScrollUnderFlexibleSpace(
|
||||||
|
hasLeading: leading != null,
|
||||||
title: title,
|
title: title,
|
||||||
|
actions: actions,
|
||||||
foregroundColor: foregroundColor,
|
foregroundColor: foregroundColor,
|
||||||
variant: _ScrollUnderFlexibleVariant.large,
|
variant: _ScrollUnderFlexibleVariant.large,
|
||||||
centerCollapsedTitle: centerTitle,
|
centerCollapsedTitle: centerTitle,
|
||||||
primary: primary,
|
primary: primary,
|
||||||
|
leadingWidth: leadingWidth,
|
||||||
|
titleSpacing: titleSpacing,
|
||||||
),
|
),
|
||||||
bottom: bottom,
|
bottom: bottom,
|
||||||
elevation: elevation,
|
elevation: elevation,
|
||||||
@ -2077,18 +2083,26 @@ enum _ScrollUnderFlexibleVariant { medium, large }
|
|||||||
|
|
||||||
class _ScrollUnderFlexibleSpace extends StatelessWidget {
|
class _ScrollUnderFlexibleSpace extends StatelessWidget {
|
||||||
const _ScrollUnderFlexibleSpace({
|
const _ScrollUnderFlexibleSpace({
|
||||||
|
required this.hasLeading,
|
||||||
this.title,
|
this.title,
|
||||||
|
this.actions,
|
||||||
this.foregroundColor,
|
this.foregroundColor,
|
||||||
required this.variant,
|
required this.variant,
|
||||||
this.centerCollapsedTitle,
|
this.centerCollapsedTitle,
|
||||||
this.primary = true,
|
this.primary = true,
|
||||||
|
this.leadingWidth,
|
||||||
|
this.titleSpacing,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
final bool hasLeading;
|
||||||
final Widget? title;
|
final Widget? title;
|
||||||
|
final List<Widget>? actions;
|
||||||
final Color? foregroundColor;
|
final Color? foregroundColor;
|
||||||
final _ScrollUnderFlexibleVariant variant;
|
final _ScrollUnderFlexibleVariant variant;
|
||||||
final bool? centerCollapsedTitle;
|
final bool? centerCollapsedTitle;
|
||||||
final bool primary;
|
final bool primary;
|
||||||
|
final double? leadingWidth;
|
||||||
|
final double? titleSpacing;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -2142,6 +2156,14 @@ class _ScrollUnderFlexibleSpace extends StatelessWidget {
|
|||||||
centerTitle = centerCollapsedTitle ?? appBarTheme.centerTitle ?? platformCenter();
|
centerTitle = centerCollapsedTitle ?? appBarTheme.centerTitle ?? platformCenter();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
final bool isCollapsed = settings.isScrolledUnder ?? false;
|
||||||
return Column(
|
return Column(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
@ -2149,17 +2171,20 @@ class _ScrollUnderFlexibleSpace extends StatelessWidget {
|
|||||||
padding: EdgeInsets.only(top: topPadding),
|
padding: EdgeInsets.only(top: topPadding),
|
||||||
child: Container(
|
child: Container(
|
||||||
height: collapsedHeight,
|
height: collapsedHeight,
|
||||||
padding: centerTitle ? config.collapsedCenteredTitlePadding : config.collapsedTitlePadding,
|
padding: effectiveCollapsedTitlePadding,
|
||||||
child: AnimatedOpacity(
|
child: NavigationToolbar(
|
||||||
|
centerMiddle: centerTitle,
|
||||||
|
middleSpacing: titleSpacing ?? appBarTheme.titleSpacing ?? NavigationToolbar.kMiddleSpacing,
|
||||||
|
middle: AnimatedOpacity(
|
||||||
opacity: isCollapsed ? 1 : 0,
|
opacity: isCollapsed ? 1 : 0,
|
||||||
duration: const Duration(milliseconds: 500),
|
duration: const Duration(milliseconds: 500),
|
||||||
curve: const Cubic(0.2, 0.0, 0.0, 1.0),
|
curve: const Cubic(0.2, 0.0, 0.0, 1.0),
|
||||||
child: Align(
|
|
||||||
alignment: centerTitle
|
|
||||||
? Alignment.center
|
|
||||||
: AlignmentDirectional.centerStart,
|
|
||||||
child: collapsedTitle,
|
child: collapsedTitle,
|
||||||
),
|
),
|
||||||
|
trailing: actions != null ? Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: actions!,
|
||||||
|
) : null,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -2295,10 +2320,10 @@ class _MediumScrollUnderFlexibleConfig with _ScrollUnderFlexibleConfig {
|
|||||||
_textTheme.headlineSmall?.apply(color: _colors.onSurface);
|
_textTheme.headlineSmall?.apply(color: _colors.onSurface);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
EdgeInsetsGeometry? get collapsedTitlePadding => const EdgeInsetsDirectional.fromSTEB(48, 0, 16, 0);
|
EdgeInsetsGeometry? get collapsedTitlePadding => const EdgeInsetsDirectional.only(start: 40);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
EdgeInsetsGeometry? get collapsedCenteredTitlePadding => const EdgeInsets.fromLTRB(16, 0, 16, 0);
|
EdgeInsetsGeometry? get collapsedCenteredTitlePadding => const EdgeInsetsDirectional.only(start: 40);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
EdgeInsetsGeometry? get expandedTitlePadding => const EdgeInsets.fromLTRB(16, 0, 16, 20);
|
EdgeInsetsGeometry? get expandedTitlePadding => const EdgeInsets.fromLTRB(16, 0, 16, 20);
|
||||||
@ -2324,10 +2349,10 @@ class _LargeScrollUnderFlexibleConfig with _ScrollUnderFlexibleConfig {
|
|||||||
_textTheme.headlineMedium?.apply(color: _colors.onSurface);
|
_textTheme.headlineMedium?.apply(color: _colors.onSurface);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
EdgeInsetsGeometry? get collapsedTitlePadding => const EdgeInsetsDirectional.fromSTEB(48, 0, 16, 0);
|
EdgeInsetsGeometry? get collapsedTitlePadding => const EdgeInsetsDirectional.only(start: 40);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
EdgeInsetsGeometry? get collapsedCenteredTitlePadding => const EdgeInsets.fromLTRB(16, 0, 16, 0);
|
EdgeInsetsGeometry? get collapsedCenteredTitlePadding => const EdgeInsetsDirectional.only(start: 40);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
EdgeInsetsGeometry? get expandedTitlePadding => const EdgeInsets.fromLTRB(16, 0, 16, 28);
|
EdgeInsetsGeometry? get expandedTitlePadding => const EdgeInsets.fromLTRB(16, 0, 16, 28);
|
||||||
|
@ -3974,6 +3974,401 @@ void main() {
|
|||||||
expect(tester.getSize(find.byKey(leadingKey)).width, leadingWidth);
|
expect(tester.getSize(find.byKey(leadingKey)).width, leadingWidth);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets(
|
||||||
|
'SliverAppBar.medium collapsed title does not overlap with leading/actions widgets',
|
||||||
|
(WidgetTester tester) async {
|
||||||
|
const String title = 'Medium SliverAppBar Very Long Title';
|
||||||
|
await tester.pumpWidget(MaterialApp(
|
||||||
|
home: Scaffold(
|
||||||
|
body: CustomScrollView(
|
||||||
|
primary: true,
|
||||||
|
slivers: <Widget>[
|
||||||
|
SliverPadding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 200),
|
||||||
|
sliver: SliverAppBar.medium(
|
||||||
|
leading: IconButton(
|
||||||
|
icon: const Icon(Icons.menu),
|
||||||
|
onPressed: () {},
|
||||||
|
),
|
||||||
|
title: const Text(title, maxLines: 1),
|
||||||
|
actions: const <Widget>[
|
||||||
|
Icon(Icons.search),
|
||||||
|
Icon(Icons.sort),
|
||||||
|
Icon(Icons.more_vert),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SliverToBoxAdapter(
|
||||||
|
child: Container(
|
||||||
|
height: 1200,
|
||||||
|
color: Colors.orange[400],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
));
|
||||||
|
|
||||||
|
// Scroll to collapse the SliverAppBar.
|
||||||
|
final ScrollController controller = primaryScrollController(tester);
|
||||||
|
controller.jumpTo(45);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
final Offset leadingOffset = tester.getTopRight(find.byIcon(Icons.menu));
|
||||||
|
Offset titleOffset = tester.getTopLeft(find.text(title).first);
|
||||||
|
// 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);
|
||||||
|
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));
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets(
|
||||||
|
'SliverAppBar.large collapsed title does not overlap with leading/actions widgets',
|
||||||
|
(WidgetTester tester) async {
|
||||||
|
const String title = 'Large SliverAppBar Very Long Title';
|
||||||
|
await tester.pumpWidget(MaterialApp(
|
||||||
|
home: Scaffold(
|
||||||
|
body: CustomScrollView(
|
||||||
|
primary: true,
|
||||||
|
slivers: <Widget>[
|
||||||
|
SliverPadding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 200),
|
||||||
|
sliver: SliverAppBar.large(
|
||||||
|
leading: IconButton(
|
||||||
|
icon: const Icon(Icons.menu),
|
||||||
|
onPressed: () {},
|
||||||
|
),
|
||||||
|
title: const Text(title, maxLines: 1),
|
||||||
|
actions: const <Widget>[
|
||||||
|
Icon(Icons.search),
|
||||||
|
Icon(Icons.sort),
|
||||||
|
Icon(Icons.more_vert),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SliverToBoxAdapter(
|
||||||
|
child: Container(
|
||||||
|
height: 1200,
|
||||||
|
color: Colors.orange[400],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
));
|
||||||
|
|
||||||
|
// Scroll to collapse the SliverAppBar.
|
||||||
|
final ScrollController controller = primaryScrollController(tester);
|
||||||
|
controller.jumpTo(45);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
final Offset leadingOffset = tester.getTopRight(find.byIcon(Icons.menu));
|
||||||
|
Offset titleOffset = tester.getTopLeft(find.text(title).first);
|
||||||
|
// 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);
|
||||||
|
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));
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('SliverAppBar.medium respects title spacing', (WidgetTester tester) async {
|
||||||
|
const String title = 'Medium SliverAppBar Very Long Title';
|
||||||
|
const double titleSpacing = 16.0;
|
||||||
|
|
||||||
|
Widget buildWidget({double? titleSpacing, bool? centerTitle}) {
|
||||||
|
return MaterialApp(
|
||||||
|
home: Scaffold(
|
||||||
|
body: CustomScrollView(
|
||||||
|
primary: true,
|
||||||
|
slivers: <Widget>[
|
||||||
|
SliverAppBar.medium(
|
||||||
|
centerTitle: centerTitle,
|
||||||
|
leading: IconButton(
|
||||||
|
icon: const Icon(Icons.menu),
|
||||||
|
onPressed: () {},
|
||||||
|
),
|
||||||
|
title: const Text(title, maxLines: 1),
|
||||||
|
titleSpacing: titleSpacing,
|
||||||
|
actions: const <Widget>[
|
||||||
|
Icon(Icons.sort),
|
||||||
|
Icon(Icons.more_vert),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SliverToBoxAdapter(
|
||||||
|
child: Container(
|
||||||
|
height: 1200,
|
||||||
|
color: Colors.orange[400],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await tester.pumpWidget(buildWidget());
|
||||||
|
|
||||||
|
// Scroll to collapse the SliverAppBar.
|
||||||
|
ScrollController controller = primaryScrollController(tester);
|
||||||
|
controller.jumpTo(45);
|
||||||
|
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);
|
||||||
|
|
||||||
|
await tester.pumpWidget(buildWidget(centerTitle: true));
|
||||||
|
// Scroll to collapse the SliverAppBar.
|
||||||
|
controller = primaryScrollController(tester);
|
||||||
|
controller.jumpTo(45);
|
||||||
|
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);
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
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);
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('SliverAppBar.large respects title spacing', (WidgetTester tester) async {
|
||||||
|
const String title = 'Large SliverAppBar Very Long Title';
|
||||||
|
const double titleSpacing = 16.0;
|
||||||
|
|
||||||
|
Widget buildWidget({double? titleSpacing, bool? centerTitle}) {
|
||||||
|
return MaterialApp(
|
||||||
|
home: Scaffold(
|
||||||
|
body: CustomScrollView(
|
||||||
|
primary: true,
|
||||||
|
slivers: <Widget>[
|
||||||
|
SliverAppBar.large(
|
||||||
|
centerTitle: centerTitle,
|
||||||
|
leading: IconButton(
|
||||||
|
icon: const Icon(Icons.menu),
|
||||||
|
onPressed: () {},
|
||||||
|
),
|
||||||
|
title: const Text(title, maxLines: 1),
|
||||||
|
titleSpacing: titleSpacing,
|
||||||
|
actions: const <Widget>[
|
||||||
|
Icon(Icons.sort),
|
||||||
|
Icon(Icons.more_vert),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SliverToBoxAdapter(
|
||||||
|
child: Container(
|
||||||
|
height: 1200,
|
||||||
|
color: Colors.orange[400],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await tester.pumpWidget(buildWidget());
|
||||||
|
|
||||||
|
// Scroll to collapse the SliverAppBar.
|
||||||
|
ScrollController controller = primaryScrollController(tester);
|
||||||
|
controller.jumpTo(45);
|
||||||
|
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);
|
||||||
|
|
||||||
|
await tester.pumpWidget(buildWidget(centerTitle: true));
|
||||||
|
// Scroll to collapse the SliverAppBar.
|
||||||
|
controller = primaryScrollController(tester);
|
||||||
|
controller.jumpTo(45);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
// By default, title widget should be to the right 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);
|
||||||
|
|
||||||
|
// Test custom title spacing, set to 0.0.
|
||||||
|
await tester.pumpWidget(buildWidget(titleSpacing: 0.0));
|
||||||
|
controller = primaryScrollController(tester);
|
||||||
|
controller.jumpTo(45);
|
||||||
|
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);
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets(
|
||||||
|
'SliverAppBar.medium without the leading widget updates collapsed title padding',
|
||||||
|
(WidgetTester widgetTester) async {
|
||||||
|
const String title = 'Medium SliverAppBar Title';
|
||||||
|
const double leadingPadding = 40.0;
|
||||||
|
const double titleSpacing = 16.0;
|
||||||
|
|
||||||
|
Widget buildWidget({ bool showLeading = true }) {
|
||||||
|
return MaterialApp(
|
||||||
|
home: Scaffold(
|
||||||
|
body: CustomScrollView(
|
||||||
|
primary: true,
|
||||||
|
slivers: <Widget>[
|
||||||
|
SliverAppBar.medium(
|
||||||
|
leading: showLeading
|
||||||
|
? IconButton(
|
||||||
|
icon: const Icon(Icons.menu),
|
||||||
|
onPressed: () {},
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
title: const Text(title),
|
||||||
|
),
|
||||||
|
SliverToBoxAdapter(
|
||||||
|
child: Container(
|
||||||
|
height: 1200,
|
||||||
|
color: Colors.orange[400],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await widgetTester.pumpWidget(buildWidget());
|
||||||
|
|
||||||
|
// Scroll to collapse the SliverAppBar.
|
||||||
|
ScrollController controller = primaryScrollController(widgetTester);
|
||||||
|
controller.jumpTo(45);
|
||||||
|
await widgetTester.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);
|
||||||
|
expect(titleOffset.dx, leadingPadding + titleSpacing);
|
||||||
|
|
||||||
|
// Hide the leading widget.
|
||||||
|
await widgetTester.pumpWidget(buildWidget(showLeading: false));
|
||||||
|
// Scroll to collapse the SliverAppBar.
|
||||||
|
controller = primaryScrollController(widgetTester);
|
||||||
|
controller.jumpTo(45);
|
||||||
|
await widgetTester.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);
|
||||||
|
expect(titleOffset.dx, titleSpacing);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets(
|
||||||
|
'SliverAppBar.large without the leading widget updates collapsed title padding',
|
||||||
|
(WidgetTester widgetTester) async {
|
||||||
|
const String title = 'Large SliverAppBar Title';
|
||||||
|
const double leadingPadding = 40.0;
|
||||||
|
const double titleSpacing = 16.0;
|
||||||
|
|
||||||
|
Widget buildWidget({ bool showLeading = true }) {
|
||||||
|
return MaterialApp(
|
||||||
|
home: Scaffold(
|
||||||
|
body: CustomScrollView(
|
||||||
|
primary: true,
|
||||||
|
slivers: <Widget>[
|
||||||
|
SliverAppBar.large(
|
||||||
|
leading: showLeading
|
||||||
|
? IconButton(
|
||||||
|
icon: const Icon(Icons.menu),
|
||||||
|
onPressed: () {},
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
title: const Text(title),
|
||||||
|
),
|
||||||
|
SliverToBoxAdapter(
|
||||||
|
child: Container(
|
||||||
|
height: 1200,
|
||||||
|
color: Colors.orange[400],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await widgetTester.pumpWidget(buildWidget());
|
||||||
|
|
||||||
|
// Scroll CustomScrollView to collapse SliverAppBar.
|
||||||
|
ScrollController controller = primaryScrollController(widgetTester);
|
||||||
|
controller.jumpTo(45);
|
||||||
|
await widgetTester.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);
|
||||||
|
expect(titleOffset.dx, leadingPadding + titleSpacing);
|
||||||
|
|
||||||
|
// Hide the leading widget.
|
||||||
|
await widgetTester.pumpWidget(buildWidget(showLeading: false));
|
||||||
|
// Scroll to collapse the SliverAppBar.
|
||||||
|
controller = primaryScrollController(widgetTester);
|
||||||
|
controller.jumpTo(45);
|
||||||
|
await widgetTester.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);
|
||||||
|
expect(titleOffset.dx, titleSpacing);
|
||||||
|
});
|
||||||
|
|
||||||
group('AppBar.forceMaterialTransparency', () {
|
group('AppBar.forceMaterialTransparency', () {
|
||||||
Material getAppBarMaterial(WidgetTester tester) {
|
Material getAppBarMaterial(WidgetTester tester) {
|
||||||
return tester.widget<Material>(find
|
return tester.widget<Material>(find
|
||||||
@ -4026,7 +4421,8 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
testWidgets(
|
testWidgets(
|
||||||
'forceMaterialTransparency == false does not allow gestures beneath the app bar', (WidgetTester tester) async {
|
'forceMaterialTransparency == false does not allow gestures beneath the app bar',
|
||||||
|
(WidgetTester tester) async {
|
||||||
// Set this, and tester.tap(warnIfMissed:false), to suppress
|
// Set this, and tester.tap(warnIfMissed:false), to suppress
|
||||||
// errors/warning that the button is not hittable (which is expected).
|
// errors/warning that the button is not hittable (which is expected).
|
||||||
WidgetController.hitTestWarningShouldBeFatal = false;
|
WidgetController.hitTestWarningShouldBeFatal = false;
|
||||||
|
@ -9,6 +9,10 @@ import 'package:flutter/services.dart';
|
|||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
|
ScrollController primaryScrollController(WidgetTester tester) {
|
||||||
|
return PrimaryScrollController.of(tester.element(find.byType(CustomScrollView)));
|
||||||
|
}
|
||||||
|
|
||||||
test('AppBarTheme copyWith, ==, hashCode basics', () {
|
test('AppBarTheme copyWith, ==, hashCode basics', () {
|
||||||
expect(const AppBarTheme(), const AppBarTheme().copyWith());
|
expect(const AppBarTheme(), const AppBarTheme().copyWith());
|
||||||
expect(const AppBarTheme().hashCode, const AppBarTheme().copyWith().hashCode);
|
expect(const AppBarTheme().hashCode, const AppBarTheme().copyWith().hashCode);
|
||||||
@ -676,14 +680,28 @@ void main() {
|
|||||||
expect(navToolbar.middleSpacing, 40);
|
expect(navToolbar.middleSpacing, 40);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets("SliverAppBar.medium's title uses AppBarTheme.foregroundColor", (WidgetTester tester) async {
|
testWidgets('SliverAppBar.medium uses AppBarTheme properties', (WidgetTester tester) async {
|
||||||
|
const String title = 'Medium SliverAppBar Title';
|
||||||
const Color foregroundColor = Color(0xff00ff00);
|
const Color foregroundColor = Color(0xff00ff00);
|
||||||
|
const double titleSpacing = 10.0;
|
||||||
|
|
||||||
await tester.pumpWidget(MaterialApp(
|
await tester.pumpWidget(MaterialApp(
|
||||||
theme: ThemeData(appBarTheme: const AppBarTheme(foregroundColor: foregroundColor)),
|
theme: ThemeData(
|
||||||
|
appBarTheme: const AppBarTheme(
|
||||||
|
foregroundColor: foregroundColor,
|
||||||
|
titleSpacing: titleSpacing,
|
||||||
|
centerTitle: false,
|
||||||
|
),
|
||||||
|
),
|
||||||
home: CustomScrollView(
|
home: CustomScrollView(
|
||||||
|
primary: true,
|
||||||
slivers: <Widget>[
|
slivers: <Widget>[
|
||||||
SliverAppBar.medium(
|
SliverAppBar.medium(
|
||||||
title: const Text('Medium Title'),
|
leading: IconButton(
|
||||||
|
onPressed: () {},
|
||||||
|
icon: const Icon(Icons.menu),
|
||||||
|
),
|
||||||
|
title: const Text(title),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -691,18 +709,43 @@ void main() {
|
|||||||
|
|
||||||
final RichText text = tester.firstWidget(find.byType(RichText));
|
final RichText text = tester.firstWidget(find.byType(RichText));
|
||||||
expect(text.text.style!.color, foregroundColor);
|
expect(text.text.style!.color, foregroundColor);
|
||||||
|
|
||||||
|
// Scroll to collapse the SliverAppBar.
|
||||||
|
final ScrollController controller = primaryScrollController(tester);
|
||||||
|
controller.jumpTo(45);
|
||||||
|
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);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets(
|
testWidgets('SliverAppBar.medium properties take priority over AppBarTheme properties', (WidgetTester tester) async {
|
||||||
"SliverAppBar.medium's foregroundColor takes priority over AppBarTheme.foregroundColor", (WidgetTester tester) async {
|
const String title = 'Medium SliverAppBar Title';
|
||||||
const Color foregroundColor = Color(0xff00ff00);
|
const Color foregroundColor = Color(0xff00ff00);
|
||||||
|
const double titleSpacing = 10.0;
|
||||||
|
|
||||||
await tester.pumpWidget(MaterialApp(
|
await tester.pumpWidget(MaterialApp(
|
||||||
theme: ThemeData(appBarTheme: const AppBarTheme(foregroundColor: Color(0xffff0000))),
|
theme: ThemeData(
|
||||||
|
appBarTheme: const AppBarTheme(
|
||||||
|
foregroundColor: Color(0xffff0000),
|
||||||
|
titleSpacing: 14.0,
|
||||||
|
centerTitle: true,
|
||||||
|
),
|
||||||
|
),
|
||||||
home: CustomScrollView(
|
home: CustomScrollView(
|
||||||
|
primary: true,
|
||||||
slivers: <Widget>[
|
slivers: <Widget>[
|
||||||
SliverAppBar.medium(
|
SliverAppBar.medium(
|
||||||
|
centerTitle: false,
|
||||||
|
titleSpacing: titleSpacing,
|
||||||
foregroundColor: foregroundColor,
|
foregroundColor: foregroundColor,
|
||||||
title: const Text('Medium Title'),
|
leading: IconButton(
|
||||||
|
onPressed: () {},
|
||||||
|
icon: const Icon(Icons.menu),
|
||||||
|
),
|
||||||
|
title: const Text(title),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -710,16 +753,40 @@ void main() {
|
|||||||
|
|
||||||
final RichText text = tester.firstWidget(find.byType(RichText));
|
final RichText text = tester.firstWidget(find.byType(RichText));
|
||||||
expect(text.text.style!.color, foregroundColor);
|
expect(text.text.style!.color, foregroundColor);
|
||||||
|
|
||||||
|
// Scroll to collapse the SliverAppBar.
|
||||||
|
final ScrollController controller = primaryScrollController(tester);
|
||||||
|
controller.jumpTo(45);
|
||||||
|
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);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets("SliverAppBar.large's title uses AppBarTheme.foregroundColor", (WidgetTester tester) async {
|
testWidgets('SliverAppBar.large uses AppBarTheme properties', (WidgetTester tester) async {
|
||||||
|
const String title = 'Large SliverAppBar Title';
|
||||||
const Color foregroundColor = Color(0xff00ff00);
|
const Color foregroundColor = Color(0xff00ff00);
|
||||||
|
const double titleSpacing = 10.0;
|
||||||
|
|
||||||
await tester.pumpWidget(MaterialApp(
|
await tester.pumpWidget(MaterialApp(
|
||||||
theme: ThemeData(appBarTheme: const AppBarTheme(foregroundColor: foregroundColor)),
|
theme: ThemeData(
|
||||||
|
appBarTheme: const AppBarTheme(
|
||||||
|
foregroundColor: foregroundColor,
|
||||||
|
titleSpacing: titleSpacing,
|
||||||
|
centerTitle: false,
|
||||||
|
),
|
||||||
|
),
|
||||||
home: CustomScrollView(
|
home: CustomScrollView(
|
||||||
|
primary: true,
|
||||||
slivers: <Widget>[
|
slivers: <Widget>[
|
||||||
SliverAppBar.large(
|
SliverAppBar.large(
|
||||||
title: const Text('Large Title'),
|
leading: IconButton(
|
||||||
|
onPressed: () {},
|
||||||
|
icon: const Icon(Icons.menu),
|
||||||
|
),
|
||||||
|
title: const Text(title),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -727,18 +794,43 @@ void main() {
|
|||||||
|
|
||||||
final RichText text = tester.firstWidget(find.byType(RichText));
|
final RichText text = tester.firstWidget(find.byType(RichText));
|
||||||
expect(text.text.style!.color, foregroundColor);
|
expect(text.text.style!.color, foregroundColor);
|
||||||
|
|
||||||
|
// Scroll to collapse the SliverAppBar.
|
||||||
|
final ScrollController controller = primaryScrollController(tester);
|
||||||
|
controller.jumpTo(45);
|
||||||
|
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);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets(
|
testWidgets('SliverAppBar.large properties take priority over AppBarTheme properties', (WidgetTester tester) async {
|
||||||
"SliverAppBar.large's foregroundColor takes priority over AppBarTheme.foregroundColor", (WidgetTester tester) async {
|
const String title = 'Large SliverAppBar Title';
|
||||||
const Color foregroundColor = Color(0xff00ff00);
|
const Color foregroundColor = Color(0xff00ff00);
|
||||||
|
const double titleSpacing = 10.0;
|
||||||
|
|
||||||
await tester.pumpWidget(MaterialApp(
|
await tester.pumpWidget(MaterialApp(
|
||||||
theme: ThemeData(appBarTheme: const AppBarTheme(foregroundColor: Color(0xffff0000))),
|
theme: ThemeData(
|
||||||
|
appBarTheme: const AppBarTheme(
|
||||||
|
foregroundColor: Color(0xffff0000),
|
||||||
|
titleSpacing: 14.0,
|
||||||
|
centerTitle: true,
|
||||||
|
),
|
||||||
|
),
|
||||||
home: CustomScrollView(
|
home: CustomScrollView(
|
||||||
|
primary: true,
|
||||||
slivers: <Widget>[
|
slivers: <Widget>[
|
||||||
SliverAppBar.large(
|
SliverAppBar.large(
|
||||||
|
centerTitle: false,
|
||||||
|
titleSpacing: titleSpacing,
|
||||||
foregroundColor: foregroundColor,
|
foregroundColor: foregroundColor,
|
||||||
title: const Text('Large Title'),
|
leading: IconButton(
|
||||||
|
onPressed: () {},
|
||||||
|
icon: const Icon(Icons.menu),
|
||||||
|
),
|
||||||
|
title: const Text(title),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -746,6 +838,16 @@ void main() {
|
|||||||
|
|
||||||
final RichText text = tester.firstWidget(find.byType(RichText));
|
final RichText text = tester.firstWidget(find.byType(RichText));
|
||||||
expect(text.text.style!.color, foregroundColor);
|
expect(text.text.style!.color, foregroundColor);
|
||||||
|
|
||||||
|
// Scroll to collapse the SliverAppBar.
|
||||||
|
final ScrollController controller = primaryScrollController(tester);
|
||||||
|
controller.jumpTo(45);
|
||||||
|
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);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('Default AppBarTheme debugFillProperties', (WidgetTester tester) async {
|
testWidgets('Default AppBarTheme debugFillProperties', (WidgetTester tester) async {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user