blob: 7c71092962bcbb06f909948581ac6eea3ff2567a [file] [log] [blame]
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// This file is run as part of a reduced test set in CI on Mac and Windows
// machines.
@Tags(<String>['reduced-test-set'])
library;
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart';
import '../widgets/semantics_tester.dart';
void main() {
testWidgets('FlexibleSpaceBar centers title on iOS', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(platform: TargetPlatform.android),
home: Scaffold(
appBar: AppBar(flexibleSpace: const FlexibleSpaceBar(title: Text('X'))),
),
),
);
final Finder title = find.text('X');
Offset center = tester.getCenter(title);
Size size = tester.getSize(title);
expect(center.dx, lessThan(400.0 - size.width / 2.0));
for (final platform in <TargetPlatform>[TargetPlatform.iOS, TargetPlatform.macOS]) {
// Clear the widget tree to avoid animating between platforms.
await tester.pumpWidget(Container(key: UniqueKey()));
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(platform: platform),
home: Scaffold(
appBar: AppBar(flexibleSpace: const FlexibleSpaceBar(title: Text('X'))),
),
),
);
center = tester.getCenter(title);
size = tester.getSize(title);
expect(center.dx, greaterThan(400.0 - size.width / 2.0));
expect(center.dx, lessThan(400.0 + size.width / 2.0));
}
});
testWidgets('Material3 - FlexibleSpaceBarSettings provides settings to a FlexibleSpaceBar', (
WidgetTester tester,
) async {
const minExtent = 100.0;
const initExtent = 200.0;
const maxExtent = 300.0;
const alpha = 0.5;
final customSettings =
FlexibleSpaceBar.createSettings(
currentExtent: initExtent,
minExtent: minExtent,
maxExtent: maxExtent,
toolbarOpacity: alpha,
child: AppBar(
flexibleSpace: const FlexibleSpaceBar(
title: Text('title'),
background: Text('X2'),
collapseMode: CollapseMode.pin,
),
),
)
as FlexibleSpaceBarSettings;
const dragTarget = Key('orange box');
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: CustomScrollView(
key: dragTarget,
primary: true,
slivers: <Widget>[
SliverPersistentHeader(
floating: true,
pinned: true,
delegate: TestDelegate(settings: customSettings),
),
SliverToBoxAdapter(child: Container(height: 1200.0, color: Colors.orange[400])),
],
),
),
),
);
final RenderBox clipRect = tester.renderObject(find.byType(ClipRect).at(1));
final Transform transform = tester.firstWidget(
find.descendant(of: find.byType(FlexibleSpaceBar), matching: find.byType(Transform)),
);
// The current (200) is half way between the min (100) and max (300) and the
// lerp values used to calculate the scale are 1 and 1.5, so we check for 1.25.
expect(transform.transform.getMaxScaleOnAxis(), 1.25);
// The space bar rect always starts fully expanded.
expect(clipRect.size.height, maxExtent);
final Element actionTextBox = tester.element(find.text('title'));
final textWidget = actionTextBox.widget as Text;
final DefaultTextStyle defaultTextStyle = DefaultTextStyle.of(actionTextBox);
final TextStyle effectiveStyle = defaultTextStyle.style.merge(textWidget.style);
expect(effectiveStyle.color?.alpha, 128); // Which is alpha of .5
// We drag up to fully collapse the space bar.
await tester.drag(find.byKey(dragTarget), const Offset(0, -400.0));
await tester.pumpAndSettle();
expect(clipRect.size.height, minExtent);
});
testWidgets('Material2 - FlexibleSpaceBarSettings provides settings to a FlexibleSpaceBar', (
WidgetTester tester,
) async {
const minExtent = 100.0;
const initExtent = 200.0;
const maxExtent = 300.0;
const alpha = 0.5;
final customSettings =
FlexibleSpaceBar.createSettings(
currentExtent: initExtent,
minExtent: minExtent,
maxExtent: maxExtent,
toolbarOpacity: alpha,
child: AppBar(
flexibleSpace: const FlexibleSpaceBar(
title: Text('title'),
background: Text('X2'),
collapseMode: CollapseMode.pin,
),
),
)
as FlexibleSpaceBarSettings;
const dragTarget = Key('orange box');
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(useMaterial3: false),
home: Scaffold(
body: CustomScrollView(
key: dragTarget,
primary: true,
slivers: <Widget>[
SliverPersistentHeader(
floating: true,
pinned: true,
delegate: TestDelegate(settings: customSettings),
),
SliverToBoxAdapter(child: Container(height: 1200.0, color: Colors.orange[400])),
],
),
),
),
);
final RenderBox clipRect = tester.renderObject(find.byType(ClipRect).first);
final Transform transform = tester.firstWidget(
find.descendant(of: find.byType(FlexibleSpaceBar), matching: find.byType(Transform)),
);
// The current (200) is half way between the min (100) and max (300) and the
// lerp values used to calculate the scale are 1 and 1.5, so we check for 1.25.
expect(transform.transform.getMaxScaleOnAxis(), 1.25);
// The space bar rect always starts fully expanded.
expect(clipRect.size.height, maxExtent);
final Element actionTextBox = tester.element(find.text('title'));
final textWidget = actionTextBox.widget as Text;
final DefaultTextStyle defaultTextStyle = DefaultTextStyle.of(actionTextBox);
final TextStyle effectiveStyle = defaultTextStyle.style.merge(textWidget.style);
expect(effectiveStyle.color?.alpha, 128); // Which is alpha of .5
// We drag up to fully collapse the space bar.
await tester.drag(find.byKey(dragTarget), const Offset(0, -400.0));
await tester.pumpAndSettle();
expect(clipRect.size.height, minExtent);
});
testWidgets(
'FlexibleSpaceBar.background is visible when using height other than kToolbarHeight',
(WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/80451
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
appBar: AppBar(
toolbarHeight: 300,
flexibleSpace: const FlexibleSpaceBar(
title: Text('Title'),
background: Text('Background'),
collapseMode: CollapseMode.pin,
),
),
body: CustomScrollView(
primary: true,
slivers: <Widget>[
SliverToBoxAdapter(child: Container(height: 1200.0, color: Colors.orange[400])),
],
),
),
),
);
final dynamic backgroundOpacity = tester.firstWidget(
find.byWidgetPredicate(
(Widget widget) => widget.runtimeType.toString() == '_FlexibleSpaceHeaderOpacity',
),
);
// accessing private type member.
// ignore: avoid_dynamic_calls
expect(backgroundOpacity.opacity, 1.0);
},
);
testWidgets('Material3 - Collapsed FlexibleSpaceBar has correct semantics', (
WidgetTester tester,
) async {
final semantics = SemanticsTester(tester);
const double expandedHeight = 200;
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: CustomScrollView(
slivers: <Widget>[
const SliverAppBar(
pinned: true,
expandedHeight: expandedHeight,
title: Text('Title'),
flexibleSpace: FlexibleSpaceBar(background: Text('Expanded title')),
),
SliverList.builder(
itemCount: 50,
itemBuilder: (BuildContext context, int index) {
return SizedBox(height: 200, child: Center(child: Text('Item $index')));
},
),
],
),
),
),
);
var expectedSemantics = TestSemantics.root(
children: <TestSemantics>[
TestSemantics.rootChild(
id: 1,
rect: TestSemantics.fullScreen,
children: <TestSemantics>[
TestSemantics(
id: 2,
rect: TestSemantics.fullScreen,
children: <TestSemantics>[
TestSemantics(
id: 3,
rect: TestSemantics.fullScreen,
flags: <SemanticsFlag>[SemanticsFlag.scopesRoute],
children: <TestSemantics>[
TestSemantics(
id: 4,
rect: TestSemantics.fullScreen,
children: <TestSemantics>[
TestSemantics(
id: 9,
rect: const Rect.fromLTRB(0.0, 0.0, 800.0, expandedHeight),
children: <TestSemantics>[
TestSemantics(
id: 12,
rect: const Rect.fromLTRB(0.0, 0.0, 800.0, 200.0),
children: <TestSemantics>[
TestSemantics(
id: 13,
rect: const Rect.fromLTRB(0.0, 0.0, 110.0, 28.0),
flags: <SemanticsFlag>[
SemanticsFlag.isHeader,
SemanticsFlag.namesRoute,
],
label: 'Title',
textDirection: TextDirection.ltr,
),
],
),
TestSemantics(
id: 10,
rect: const Rect.fromLTRB(0.0, 0.0, 800.0, 200.0),
children: <TestSemantics>[
TestSemantics(
id: 11,
rect: const Rect.fromLTRB(0.0, 0.0, 800.0, expandedHeight),
label: 'Expanded title',
textDirection: TextDirection.ltr,
),
],
),
],
),
TestSemantics(
id: 14,
flags: <SemanticsFlag>[SemanticsFlag.hasImplicitScrolling],
rect: TestSemantics.fullScreen,
actions: <SemanticsAction>[
SemanticsAction.scrollUp,
SemanticsAction.scrollToOffset,
],
children: <TestSemantics>[
TestSemantics(
id: 5,
rect: const Rect.fromLTRB(0.0, 0.0, 800.0, 200.0),
label: 'Item 0',
textDirection: TextDirection.ltr,
),
TestSemantics(
id: 6,
rect: const Rect.fromLTRB(0.0, 0.0, 800.0, 200.0),
label: 'Item 1',
textDirection: TextDirection.ltr,
),
TestSemantics(
id: 7,
rect: const Rect.fromLTRB(0.0, 0.0, 800.0, 200.0),
flags: <SemanticsFlag>[SemanticsFlag.isHidden],
label: 'Item 2',
textDirection: TextDirection.ltr,
),
TestSemantics(
id: 8,
rect: const Rect.fromLTRB(0.0, 0.0, 800.0, 50.0),
flags: <SemanticsFlag>[SemanticsFlag.isHidden],
label: 'Item 3',
textDirection: TextDirection.ltr,
),
],
),
],
),
],
),
],
),
],
),
],
);
expect(semantics, hasSemantics(expectedSemantics, ignoreTransform: true));
// We drag up to fully collapse the space bar.
await tester.drag(find.text('Item 1'), const Offset(0, -600.0));
await tester.pumpAndSettle();
expectedSemantics = TestSemantics.root(
children: <TestSemantics>[
TestSemantics.rootChild(
id: 1,
rect: TestSemantics.fullScreen,
children: <TestSemantics>[
TestSemantics(
id: 2,
rect: TestSemantics.fullScreen,
children: <TestSemantics>[
TestSemantics(
id: 3,
rect: TestSemantics.fullScreen,
flags: <SemanticsFlag>[SemanticsFlag.scopesRoute],
children: <TestSemantics>[
TestSemantics(
id: 4,
rect: TestSemantics.fullScreen,
children: <TestSemantics>[
TestSemantics(
id: 9,
// The app bar is collapsed.
rect: const Rect.fromLTRB(0.0, 0.0, 800.0, 56.0),
children: <TestSemantics>[
TestSemantics(
id: 12,
rect: const Rect.fromLTRB(0.0, 0.0, 800.0, 56.0),
children: <TestSemantics>[
TestSemantics(
id: 13,
rect: const Rect.fromLTRB(0.0, 0.0, 110.0, 28.0),
flags: <SemanticsFlag>[
SemanticsFlag.isHeader,
SemanticsFlag.namesRoute,
],
label: 'Title',
textDirection: TextDirection.ltr,
),
],
),
// The flexible space bar still persists in the
// semantic tree even if it is collapsed.
TestSemantics(
id: 10,
rect: const Rect.fromLTRB(0.0, 0.0, 800.0, 56.0),
children: <TestSemantics>[
TestSemantics(
id: 11,
rect: const Rect.fromLTRB(0.0, 36.0, 800.0, 92.0),
label: 'Expanded title',
textDirection: TextDirection.ltr,
),
],
),
],
),
TestSemantics(
id: 14,
flags: <SemanticsFlag>[SemanticsFlag.hasImplicitScrolling],
rect: TestSemantics.fullScreen,
actions: <SemanticsAction>[
SemanticsAction.scrollUp,
SemanticsAction.scrollDown,
SemanticsAction.scrollToOffset,
],
children: <TestSemantics>[
TestSemantics(
id: 5,
rect: const Rect.fromLTRB(0.0, 150.0, 800.0, 200.0),
flags: <SemanticsFlag>[SemanticsFlag.isHidden],
label: 'Item 0',
textDirection: TextDirection.ltr,
),
TestSemantics(
id: 6,
rect: const Rect.fromLTRB(0.0, 0.0, 800.0, 200.0),
flags: <SemanticsFlag>[SemanticsFlag.isHidden],
label: 'Item 1',
textDirection: TextDirection.ltr,
),
TestSemantics(
id: 7,
rect: const Rect.fromLTRB(0.0, 56.0, 800.0, 200.0),
label: 'Item 2',
textDirection: TextDirection.ltr,
),
TestSemantics(
id: 8,
rect: const Rect.fromLTRB(0.0, 0.0, 800.0, 200.0),
label: 'Item 3',
textDirection: TextDirection.ltr,
),
TestSemantics(
id: 15,
rect: const Rect.fromLTRB(0.0, 0.0, 800.0, 200.0),
label: 'Item 4',
textDirection: TextDirection.ltr,
),
TestSemantics(
id: 16,
rect: const Rect.fromLTRB(0.0, 0.0, 800.0, 200.0),
flags: <SemanticsFlag>[SemanticsFlag.isHidden],
label: 'Item 5',
textDirection: TextDirection.ltr,
),
TestSemantics(
id: 17,
rect: const Rect.fromLTRB(0.0, 0.0, 800.0, 50.0),
flags: <SemanticsFlag>[SemanticsFlag.isHidden],
label: 'Item 6',
textDirection: TextDirection.ltr,
),
],
),
],
),
],
),
],
),
],
),
],
);
expect(semantics, hasSemantics(expectedSemantics, ignoreTransform: true));
semantics.dispose();
});
testWidgets('Material2 - Collapsed FlexibleSpaceBar has correct semantics', (
WidgetTester tester,
) async {
final semantics = SemanticsTester(tester);
const double expandedHeight = 200;
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(useMaterial3: false),
home: Scaffold(
body: CustomScrollView(
slivers: <Widget>[
const SliverAppBar(
pinned: true,
expandedHeight: expandedHeight,
title: Text('Title'),
flexibleSpace: FlexibleSpaceBar(background: Text('Expanded title')),
),
SliverList.builder(
itemCount: 50,
itemBuilder: (BuildContext context, int index) {
return SizedBox(height: 200, child: Center(child: Text('Item $index')));
},
),
],
),
),
),
);
var expectedSemantics = TestSemantics.root(
children: <TestSemantics>[
TestSemantics.rootChild(
id: 1,
rect: TestSemantics.fullScreen,
children: <TestSemantics>[
TestSemantics(
id: 2,
rect: TestSemantics.fullScreen,
children: <TestSemantics>[
TestSemantics(
id: 3,
rect: TestSemantics.fullScreen,
flags: <SemanticsFlag>[SemanticsFlag.scopesRoute],
children: <TestSemantics>[
TestSemantics(
id: 4,
rect: TestSemantics.fullScreen,
children: <TestSemantics>[
TestSemantics(
id: 9,
rect: const Rect.fromLTRB(0.0, 0.0, 800.0, expandedHeight),
children: <TestSemantics>[
TestSemantics(
id: 12,
rect: const Rect.fromLTRB(0.0, 0.0, 800.0, 200.0),
children: <TestSemantics>[
TestSemantics(
id: 13,
rect: const Rect.fromLTRB(0.0, 0.0, 100.0, 20.0),
flags: <SemanticsFlag>[
SemanticsFlag.isHeader,
SemanticsFlag.namesRoute,
],
label: 'Title',
textDirection: TextDirection.ltr,
),
],
),
TestSemantics(
id: 10,
rect: const Rect.fromLTRB(0.0, 0.0, 800.0, 200.0),
children: <TestSemantics>[
TestSemantics(
id: 11,
rect: const Rect.fromLTRB(0.0, 0.0, 800.0, expandedHeight),
label: 'Expanded title',
textDirection: TextDirection.ltr,
),
],
),
],
),
TestSemantics(
id: 14,
flags: <SemanticsFlag>[SemanticsFlag.hasImplicitScrolling],
rect: TestSemantics.fullScreen,
actions: <SemanticsAction>[
SemanticsAction.scrollUp,
SemanticsAction.scrollToOffset,
],
children: <TestSemantics>[
TestSemantics(
id: 5,
rect: const Rect.fromLTRB(0.0, 0.0, 800.0, 200.0),
label: 'Item 0',
textDirection: TextDirection.ltr,
),
TestSemantics(
id: 6,
rect: const Rect.fromLTRB(0.0, 0.0, 800.0, 200.0),
label: 'Item 1',
textDirection: TextDirection.ltr,
),
TestSemantics(
id: 7,
rect: const Rect.fromLTRB(0.0, 0.0, 800.0, 200.0),
flags: <SemanticsFlag>[SemanticsFlag.isHidden],
label: 'Item 2',
textDirection: TextDirection.ltr,
),
TestSemantics(
id: 8,
rect: const Rect.fromLTRB(0.0, 0.0, 800.0, 50.0),
flags: <SemanticsFlag>[SemanticsFlag.isHidden],
label: 'Item 3',
textDirection: TextDirection.ltr,
),
],
),
],
),
],
),
],
),
],
),
],
);
expect(semantics, hasSemantics(expectedSemantics, ignoreTransform: true));
// We drag up to fully collapse the space bar.
await tester.drag(find.text('Item 1'), const Offset(0, -600.0));
await tester.pumpAndSettle();
expectedSemantics = TestSemantics.root(
children: <TestSemantics>[
TestSemantics.rootChild(
id: 1,
rect: TestSemantics.fullScreen,
children: <TestSemantics>[
TestSemantics(
id: 2,
rect: TestSemantics.fullScreen,
children: <TestSemantics>[
TestSemantics(
id: 3,
rect: TestSemantics.fullScreen,
flags: <SemanticsFlag>[SemanticsFlag.scopesRoute],
children: <TestSemantics>[
TestSemantics(
id: 4,
rect: TestSemantics.fullScreen,
children: <TestSemantics>[
TestSemantics(
id: 9,
// The app bar is collapsed.
rect: const Rect.fromLTRB(0.0, 0.0, 800.0, 56.0),
children: <TestSemantics>[
TestSemantics(
id: 12,
rect: const Rect.fromLTRB(0.0, 0.0, 800.0, 56.0),
children: <TestSemantics>[
TestSemantics(
id: 13,
rect: const Rect.fromLTRB(0.0, 0.0, 100.0, 20.0),
flags: <SemanticsFlag>[
SemanticsFlag.isHeader,
SemanticsFlag.namesRoute,
],
label: 'Title',
textDirection: TextDirection.ltr,
),
],
),
// The flexible space bar still persists in the
// semantic tree even if it is collapsed.
TestSemantics(
id: 10,
rect: const Rect.fromLTRB(0.0, 0.0, 800.0, 56.0),
children: <TestSemantics>[
TestSemantics(
id: 11,
rect: const Rect.fromLTRB(0.0, 36.0, 800.0, 92.0),
label: 'Expanded title',
textDirection: TextDirection.ltr,
),
],
),
],
),
TestSemantics(
id: 14,
flags: <SemanticsFlag>[SemanticsFlag.hasImplicitScrolling],
rect: TestSemantics.fullScreen,
actions: <SemanticsAction>[
SemanticsAction.scrollUp,
SemanticsAction.scrollDown,
SemanticsAction.scrollToOffset,
],
children: <TestSemantics>[
TestSemantics(
id: 5,
rect: const Rect.fromLTRB(0.0, 150.0, 800.0, 200.0),
flags: <SemanticsFlag>[SemanticsFlag.isHidden],
label: 'Item 0',
textDirection: TextDirection.ltr,
),
TestSemantics(
id: 6,
rect: const Rect.fromLTRB(0.0, 0.0, 800.0, 200.0),
flags: <SemanticsFlag>[SemanticsFlag.isHidden],
label: 'Item 1',
textDirection: TextDirection.ltr,
),
TestSemantics(
id: 7,
rect: const Rect.fromLTRB(0.0, 56.0, 800.0, 200.0),
label: 'Item 2',
textDirection: TextDirection.ltr,
),
TestSemantics(
id: 8,
rect: const Rect.fromLTRB(0.0, 0.0, 800.0, 200.0),
label: 'Item 3',
textDirection: TextDirection.ltr,
),
TestSemantics(
id: 15,
rect: const Rect.fromLTRB(0.0, 0.0, 800.0, 200.0),
label: 'Item 4',
textDirection: TextDirection.ltr,
),
TestSemantics(
id: 16,
rect: const Rect.fromLTRB(0.0, 0.0, 800.0, 200.0),
flags: <SemanticsFlag>[SemanticsFlag.isHidden],
label: 'Item 5',
textDirection: TextDirection.ltr,
),
TestSemantics(
id: 17,
rect: const Rect.fromLTRB(0.0, 0.0, 800.0, 50.0),
flags: <SemanticsFlag>[SemanticsFlag.isHidden],
label: 'Item 6',
textDirection: TextDirection.ltr,
),
],
),
],
),
],
),
],
),
],
),
],
);
expect(semantics, hasSemantics(expectedSemantics, ignoreTransform: true));
semantics.dispose();
});
// This is a regression test for https://github.com/flutter/flutter/issues/14227
testWidgets('Material3 - FlexibleSpaceBar sets width constraints for the title', (
WidgetTester tester,
) async {
const titleFontSize = 20.0;
const height = 300.0;
late double width;
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: Builder(
builder: (BuildContext context) {
width = MediaQuery.sizeOf(context).width;
return CustomScrollView(
slivers: <Widget>[
SliverAppBar(
expandedHeight: height,
pinned: true,
stretch: true,
flexibleSpace: FlexibleSpaceBar(
titlePadding: EdgeInsets.zero,
title: Text(
'X' * 2000,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(fontSize: titleFontSize, height: 1.0),
),
centerTitle: false,
),
),
],
);
},
),
),
),
);
final textWidth = width;
// The title is scaled and transformed to be 1.5 times bigger, when the
// FlexibleSpaceBar is fully expanded, thus we expect the width to be
// 1.5 times smaller than the full width. The height of the text is the same
// as the font size, with 10 dps bottom margin.
expect(
tester.getRect(find.byType(Text)),
rectMoreOrLessEquals(
Rect.fromLTRB(0, height - titleFontSize - 10, textWidth, height),
epsilon: 0.0001,
),
);
});
// This is a regression test for https://github.com/flutter/flutter/issues/14227
testWidgets('Material2 - FlexibleSpaceBar sets width constraints for the title', (
WidgetTester tester,
) async {
const titleFontSize = 20.0;
const height = 300.0;
late double width;
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(useMaterial3: false),
home: Scaffold(
body: Builder(
builder: (BuildContext context) {
width = MediaQuery.sizeOf(context).width;
return CustomScrollView(
slivers: <Widget>[
SliverAppBar(
expandedHeight: height,
pinned: true,
stretch: true,
flexibleSpace: FlexibleSpaceBar(
titlePadding: EdgeInsets.zero,
title: Text(
'X' * 2000,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(fontSize: titleFontSize),
),
centerTitle: false,
),
),
],
);
},
),
),
),
);
final textWidth = width;
// The title is scaled and transformed to be 1.5 times bigger, when the
// FlexibleSpaceBar is fully expanded, thus we expect the width to be
// 1.5 times smaller than the full width. The height of the text is the same
// as the font size, with 10 dps bottom margin.
expect(
tester.getRect(find.byType(Text)),
rectMoreOrLessEquals(
Rect.fromLTRB(0, height - titleFontSize - 10, textWidth, height),
epsilon: 0.0001,
),
);
});
testWidgets(
'Material3 - FlexibleSpaceBar sets constraints for the title - override expandedTitleScale',
(WidgetTester tester) async {
const titleFontSize = 20.0;
const height = 300.0;
const expandedTitleScale = 3.0;
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: CustomScrollView(
slivers: <Widget>[
SliverAppBar(
expandedHeight: height,
pinned: true,
stretch: true,
flexibleSpace: FlexibleSpaceBar(
expandedTitleScale: expandedTitleScale,
titlePadding: EdgeInsets.zero,
title: Text(
'X' * 41,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(fontSize: titleFontSize, height: 1.0),
),
centerTitle: false,
),
),
SliverList.builder(
itemCount: 3,
itemBuilder: (BuildContext context, int index) {
return SizedBox(height: 200.0, child: Center(child: Text('Item $index')));
},
),
],
),
),
),
);
// We drag up to fully collapse the space bar.
await tester.drag(find.text('Item 0'), const Offset(0, -600.0));
await tester.pumpAndSettle();
final Finder title = find.byType(Text).first;
final double collapsedWidth = tester.getRect(title).width;
// We drag down to fully expand the space bar.
await tester.drag(find.text('Item 2'), const Offset(0, 600.0));
await tester.pumpAndSettle();
// The title is shifted by this margin to maintain the position of the
// bottom edge.
const double bottomMargin = titleFontSize * (expandedTitleScale - 1);
final textWidth = collapsedWidth;
// The title is scaled and transformed to be 3 times bigger, when the
// FlexibleSpaceBar is fully expanded, thus we expect the width to be
// 3 times smaller than the full width. The height of the text is the same
// as the font size, with 40 dps bottom margin to maintain its bottom position.
expect(
tester.getRect(title),
rectMoreOrLessEquals(
Rect.fromLTRB(0, height - titleFontSize - bottomMargin, textWidth, height),
epsilon: 0.0001,
),
);
},
);
testWidgets(
'Material2 - FlexibleSpaceBar sets constraints for the title - override expandedTitleScale',
(WidgetTester tester) async {
const titleFontSize = 20.0;
const height = 300.0;
const expandedTitleScale = 3.0;
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(useMaterial3: false),
home: Scaffold(
body: CustomScrollView(
slivers: <Widget>[
SliverAppBar(
expandedHeight: height,
pinned: true,
stretch: true,
flexibleSpace: FlexibleSpaceBar(
expandedTitleScale: expandedTitleScale,
titlePadding: EdgeInsets.zero,
title: Text(
'X' * 41,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(fontSize: titleFontSize),
),
centerTitle: false,
),
),
SliverList.builder(
itemCount: 3,
itemBuilder: (BuildContext context, int index) {
return SizedBox(height: 200.0, child: Center(child: Text('Item $index')));
},
),
],
),
),
),
);
// We drag up to fully collapse the space bar.
await tester.drag(find.text('Item 0'), const Offset(0, -600.0));
await tester.pumpAndSettle();
final Finder title = find.byType(Text).first;
final double collapsedWidth = tester.getRect(title).width;
// We drag down to fully expand the space bar.
await tester.drag(find.text('Item 2'), const Offset(0, 600.0));
await tester.pumpAndSettle();
// The title is shifted by this margin to maintain the position of the
// bottom edge.
const double bottomMargin = titleFontSize * (expandedTitleScale - 1);
final textWidth = collapsedWidth;
// The title is scaled and transformed to be 3 times bigger, when the
// FlexibleSpaceBar is fully expanded, thus we expect the width to be
// 3 times smaller than the full width. The height of the text is the same
// as the font size, with 40 dps bottom margin to maintain its bottom position.
expect(
tester.getRect(title),
rectMoreOrLessEquals(
Rect.fromLTRB(0, height - titleFontSize - bottomMargin, textWidth, height),
epsilon: 0.0001,
),
);
},
);
testWidgets('Material3 - FlexibleSpaceBar scaled title', (WidgetTester tester) async {
const titleFontSize = 20.0;
const height = 300.0;
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: CustomScrollView(
slivers: <Widget>[
const SliverAppBar(
expandedHeight: height,
pinned: true,
stretch: true,
flexibleSpace: RepaintBoundary(
child: FlexibleSpaceBar(
title: Text('X', style: TextStyle(fontSize: titleFontSize)),
centerTitle: false,
),
),
),
SliverList.builder(
itemCount: 3,
itemBuilder: (BuildContext context, int index) {
return SizedBox(height: 200.0, child: Center(child: Text('Item $index')));
},
),
],
),
),
),
);
// We drag up to fully collapse the space bar.
await tester.drag(find.text('Item 0'), const Offset(0, -600.0));
await tester.pumpAndSettle();
final Finder flexibleSpaceBar = find.ancestor(
of: find.byType(FlexibleSpaceBar),
matching: find.byType(RepaintBoundary).first,
);
await expectLater(
flexibleSpaceBar,
matchesGoldenFile('flexible_space_bar.m3.expanded_title_scale_default.collapsed.png'),
);
// We drag down to fully expand the space bar.
await tester.drag(find.text('Item 2'), const Offset(0, 600.0));
await tester.pumpAndSettle();
await expectLater(
flexibleSpaceBar,
matchesGoldenFile('flexible_space_bar.m3.expanded_title_scale_default.expanded.png'),
);
});
testWidgets('Material2 - FlexibleSpaceBar scaled title', (WidgetTester tester) async {
const titleFontSize = 20.0;
const height = 300.0;
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(useMaterial3: false),
home: Scaffold(
body: CustomScrollView(
slivers: <Widget>[
const SliverAppBar(
expandedHeight: height,
pinned: true,
stretch: true,
flexibleSpace: RepaintBoundary(
child: FlexibleSpaceBar(
title: Text('X', style: TextStyle(fontSize: titleFontSize)),
centerTitle: false,
),
),
),
SliverList.builder(
itemCount: 3,
itemBuilder: (BuildContext context, int index) {
return SizedBox(height: 200.0, child: Center(child: Text('Item $index')));
},
),
],
),
),
),
);
// We drag up to fully collapse the space bar.
await tester.drag(find.text('Item 0'), const Offset(0, -600.0));
await tester.pumpAndSettle();
final Finder flexibleSpaceBar = find.ancestor(
of: find.byType(FlexibleSpaceBar),
matching: find.byType(RepaintBoundary).first,
);
await expectLater(
flexibleSpaceBar,
matchesGoldenFile('flexible_space_bar.m2.expanded_title_scale_default.collapsed.png'),
);
// We drag down to fully expand the space bar.
await tester.drag(find.text('Item 2'), const Offset(0, 600.0));
await tester.pumpAndSettle();
await expectLater(
flexibleSpaceBar,
matchesGoldenFile('flexible_space_bar.m2.expanded_title_scale_default.expanded.png'),
);
});
testWidgets('Material3 - FlexibleSpaceBar scaled title - override expandedTitleScale', (
WidgetTester tester,
) async {
const titleFontSize = 20.0;
const height = 300.0;
const expandedTitleScale = 3.0;
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: CustomScrollView(
slivers: <Widget>[
const SliverAppBar(
expandedHeight: height,
pinned: true,
stretch: true,
flexibleSpace: RepaintBoundary(
child: FlexibleSpaceBar(
title: Text('X', style: TextStyle(fontSize: titleFontSize)),
centerTitle: false,
expandedTitleScale: expandedTitleScale,
),
),
),
SliverList.builder(
itemCount: 3,
itemBuilder: (BuildContext context, int index) {
return SizedBox(height: 200.0, child: Center(child: Text('Item $index')));
},
),
],
),
),
),
);
// We drag up to fully collapse the space bar.
await tester.drag(find.text('Item 0'), const Offset(0, -600.0));
await tester.pumpAndSettle();
final Finder flexibleSpaceBar = find.ancestor(
of: find.byType(FlexibleSpaceBar),
matching: find.byType(RepaintBoundary).first,
);
// This should match the default behavior
await expectLater(
flexibleSpaceBar,
matchesGoldenFile('flexible_space_bar.m3.expanded_title_scale_default.collapsed.png'),
);
// We drag down to fully expand the space bar.
await tester.drag(find.text('Item 2'), const Offset(0, 600.0));
await tester.pumpAndSettle();
await expectLater(
flexibleSpaceBar,
matchesGoldenFile('flexible_space_bar.m3.expanded_title_scale_override.expanded.png'),
);
});
testWidgets('Material2 - FlexibleSpaceBar scaled title - override expandedTitleScale', (
WidgetTester tester,
) async {
const titleFontSize = 20.0;
const height = 300.0;
const expandedTitleScale = 3.0;
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(useMaterial3: false),
home: Scaffold(
body: CustomScrollView(
slivers: <Widget>[
const SliverAppBar(
expandedHeight: height,
pinned: true,
stretch: true,
flexibleSpace: RepaintBoundary(
child: FlexibleSpaceBar(
title: Text('X', style: TextStyle(fontSize: titleFontSize)),
centerTitle: false,
expandedTitleScale: expandedTitleScale,
),
),
),
SliverList.builder(
itemCount: 3,
itemBuilder: (BuildContext context, int index) {
return SizedBox(height: 200.0, child: Center(child: Text('Item $index')));
},
),
],
),
),
),
);
// We drag up to fully collapse the space bar.
await tester.drag(find.text('Item 0'), const Offset(0, -600.0));
await tester.pumpAndSettle();
final Finder flexibleSpaceBar = find.ancestor(
of: find.byType(FlexibleSpaceBar),
matching: find.byType(RepaintBoundary).first,
);
// This should match the default behavior
await expectLater(
flexibleSpaceBar,
matchesGoldenFile('flexible_space_bar.m2.expanded_title_scale_default.collapsed.png'),
);
// We drag down to fully expand the space bar.
await tester.drag(find.text('Item 2'), const Offset(0, 600.0));
await tester.pumpAndSettle();
await expectLater(
flexibleSpaceBar,
matchesGoldenFile('flexible_space_bar.m2.expanded_title_scale_override.expanded.png'),
);
});
testWidgets('Material3 - FlexibleSpaceBar titlePadding defaults', (WidgetTester tester) async {
Widget buildFrame(TargetPlatform platform, bool? centerTitle) {
return MaterialApp(
theme: ThemeData(platform: platform),
home: Scaffold(
appBar: AppBar(
flexibleSpace: FlexibleSpaceBar(centerTitle: centerTitle, title: const Text('X')),
),
),
);
}
final Finder title = find.text('X');
final Finder flexibleSpaceBar = find.byType(FlexibleSpaceBar);
Offset getTitleBottomLeft() {
return Offset(
tester.getTopLeft(title).dx,
tester.getBottomRight(flexibleSpaceBar).dy - tester.getBottomRight(title).dy,
);
}
await tester.pumpWidget(buildFrame(TargetPlatform.android, null));
expect(getTitleBottomLeft(), const Offset(72.0, 16.0));
await tester.pumpWidget(buildFrame(TargetPlatform.android, true));
expect(getTitleBottomLeft(), const Offset(389.0, 16.0));
// Clear the widget tree to avoid animating between Android and iOS.
await tester.pumpWidget(Container(key: UniqueKey()));
await tester.pumpWidget(buildFrame(TargetPlatform.iOS, null));
expect(getTitleBottomLeft(), const Offset(389.0, 16.0));
await tester.pumpWidget(buildFrame(TargetPlatform.iOS, false));
expect(getTitleBottomLeft(), const Offset(72.0, 16.0));
// Clear the widget tree to avoid animating between iOS and macOS.
await tester.pumpWidget(Container(key: UniqueKey()));
await tester.pumpWidget(buildFrame(TargetPlatform.macOS, null));
expect(getTitleBottomLeft(), const Offset(389.0, 16.0));
await tester.pumpWidget(buildFrame(TargetPlatform.macOS, false));
expect(getTitleBottomLeft(), const Offset(72.0, 16.0));
});
testWidgets('Material2 - FlexibleSpaceBar titlePadding defaults', (WidgetTester tester) async {
Widget buildFrame(TargetPlatform platform, bool? centerTitle) {
return MaterialApp(
theme: ThemeData(platform: platform, useMaterial3: false),
home: Scaffold(
appBar: AppBar(
flexibleSpace: FlexibleSpaceBar(centerTitle: centerTitle, title: const Text('X')),
),
),
);
}
final Finder title = find.text('X');
final Finder flexibleSpaceBar = find.byType(FlexibleSpaceBar);
Offset getTitleBottomLeft() {
return Offset(
tester.getTopLeft(title).dx,
tester.getBottomRight(flexibleSpaceBar).dy - tester.getBottomRight(title).dy,
);
}
await tester.pumpWidget(buildFrame(TargetPlatform.android, null));
expect(getTitleBottomLeft(), const Offset(72.0, 16.0));
await tester.pumpWidget(buildFrame(TargetPlatform.android, true));
expect(getTitleBottomLeft(), const Offset(390.0, 16.0));
// Clear the widget tree to avoid animating between Android and iOS.
await tester.pumpWidget(Container(key: UniqueKey()));
await tester.pumpWidget(buildFrame(TargetPlatform.iOS, null));
expect(getTitleBottomLeft(), const Offset(390.0, 16.0));
await tester.pumpWidget(buildFrame(TargetPlatform.iOS, false));
expect(getTitleBottomLeft(), const Offset(72.0, 16.0));
// Clear the widget tree to avoid animating between iOS and macOS.
await tester.pumpWidget(Container(key: UniqueKey()));
await tester.pumpWidget(buildFrame(TargetPlatform.macOS, null));
expect(getTitleBottomLeft(), const Offset(390.0, 16.0));
await tester.pumpWidget(buildFrame(TargetPlatform.macOS, false));
expect(getTitleBottomLeft(), const Offset(72.0, 16.0));
});
testWidgets('Material3 - FlexibleSpaceBar titlePadding override', (WidgetTester tester) async {
Widget buildFrame(TargetPlatform platform, bool? centerTitle) {
return MaterialApp(
theme: ThemeData(platform: platform),
home: Scaffold(
appBar: AppBar(
flexibleSpace: FlexibleSpaceBar(
titlePadding: EdgeInsets.zero,
centerTitle: centerTitle,
title: const Text('X'),
),
),
),
);
}
final Finder title = find.text('X');
final Finder flexibleSpaceBar = find.byType(FlexibleSpaceBar);
Offset getTitleBottomLeft() {
return Offset(
tester.getTopLeft(title).dx,
tester.getBottomRight(flexibleSpaceBar).dy - tester.getBottomRight(title).dy,
);
}
await tester.pumpWidget(buildFrame(TargetPlatform.android, null));
expect(getTitleBottomLeft(), Offset.zero);
await tester.pumpWidget(buildFrame(TargetPlatform.android, true));
expect(getTitleBottomLeft(), const Offset(389.0, 0.0));
// Clear the widget tree to avoid animating between platforms.
await tester.pumpWidget(Container(key: UniqueKey()));
await tester.pumpWidget(buildFrame(TargetPlatform.iOS, null));
expect(getTitleBottomLeft(), const Offset(389.0, 0.0));
await tester.pumpWidget(buildFrame(TargetPlatform.iOS, false));
expect(getTitleBottomLeft(), Offset.zero);
// Clear the widget tree to avoid animating between platforms.
await tester.pumpWidget(Container(key: UniqueKey()));
await tester.pumpWidget(buildFrame(TargetPlatform.macOS, null));
expect(getTitleBottomLeft(), const Offset(389.0, 0.0));
await tester.pumpWidget(buildFrame(TargetPlatform.macOS, false));
expect(getTitleBottomLeft(), Offset.zero);
// Clear the widget tree to avoid animating between platforms.
await tester.pumpWidget(Container(key: UniqueKey()));
await tester.pumpWidget(buildFrame(TargetPlatform.windows, null));
expect(getTitleBottomLeft(), Offset.zero);
await tester.pumpWidget(buildFrame(TargetPlatform.windows, true));
expect(getTitleBottomLeft(), const Offset(389.0, 0.0));
// Clear the widget tree to avoid animating between platforms.
await tester.pumpWidget(Container(key: UniqueKey()));
await tester.pumpWidget(buildFrame(TargetPlatform.linux, null));
expect(getTitleBottomLeft(), Offset.zero);
await tester.pumpWidget(buildFrame(TargetPlatform.linux, true));
expect(getTitleBottomLeft(), const Offset(389.0, 0.0));
});
testWidgets('Material2 - FlexibleSpaceBar titlePadding override', (WidgetTester tester) async {
Widget buildFrame(TargetPlatform platform, bool? centerTitle) {
return MaterialApp(
theme: ThemeData(platform: platform, useMaterial3: false),
home: Scaffold(
appBar: AppBar(
flexibleSpace: FlexibleSpaceBar(
titlePadding: EdgeInsets.zero,
centerTitle: centerTitle,
title: const Text('X'),
),
),
),
);
}
final Finder title = find.text('X');
final Finder flexibleSpaceBar = find.byType(FlexibleSpaceBar);
Offset getTitleBottomLeft() {
return Offset(
tester.getTopLeft(title).dx,
tester.getBottomRight(flexibleSpaceBar).dy - tester.getBottomRight(title).dy,
);
}
await tester.pumpWidget(buildFrame(TargetPlatform.android, null));
expect(getTitleBottomLeft(), Offset.zero);
await tester.pumpWidget(buildFrame(TargetPlatform.android, true));
expect(getTitleBottomLeft(), const Offset(390.0, 0.0));
// Clear the widget tree to avoid animating between platforms.
await tester.pumpWidget(Container(key: UniqueKey()));
await tester.pumpWidget(buildFrame(TargetPlatform.iOS, null));
expect(getTitleBottomLeft(), const Offset(390.0, 0.0));
await tester.pumpWidget(buildFrame(TargetPlatform.iOS, false));
expect(getTitleBottomLeft(), Offset.zero);
// Clear the widget tree to avoid animating between platforms.
await tester.pumpWidget(Container(key: UniqueKey()));
await tester.pumpWidget(buildFrame(TargetPlatform.macOS, null));
expect(getTitleBottomLeft(), const Offset(390.0, 0.0));
await tester.pumpWidget(buildFrame(TargetPlatform.macOS, false));
expect(getTitleBottomLeft(), Offset.zero);
// Clear the widget tree to avoid animating between platforms.
await tester.pumpWidget(Container(key: UniqueKey()));
await tester.pumpWidget(buildFrame(TargetPlatform.windows, null));
expect(getTitleBottomLeft(), Offset.zero);
await tester.pumpWidget(buildFrame(TargetPlatform.windows, true));
expect(getTitleBottomLeft(), const Offset(390.0, 0.0));
// Clear the widget tree to avoid animating between platforms.
await tester.pumpWidget(Container(key: UniqueKey()));
await tester.pumpWidget(buildFrame(TargetPlatform.linux, null));
expect(getTitleBottomLeft(), Offset.zero);
await tester.pumpWidget(buildFrame(TargetPlatform.linux, true));
expect(getTitleBottomLeft(), const Offset(390.0, 0.0));
});
testWidgets('FlexibleSpaceBar rebuilds when scrolling.', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
home: const SubCategoryScreenView(),
theme: ThemeData(
pageTransitionsTheme: const PageTransitionsTheme(
builders: <TargetPlatform, PageTransitionsBuilder>{
TargetPlatform.android: ZoomPageTransitionsBuilder(),
},
),
),
),
);
expect(RenderRebuildTracker.count, 1);
expect(
tester.layers.lastWhere((Layer element) => element is OpacityLayer),
isA<OpacityLayer>().having((OpacityLayer p0) => p0.alpha, 'alpha', 255),
);
// We drag up to fully collapse the space bar.
for (var i = 0; i < 9; i++) {
await tester.drag(find.byKey(SubCategoryScreenView.scrollKey), const Offset(0, -50.0));
await tester.pumpAndSettle();
}
expect(
tester.layers.lastWhere((Layer element) => element is OpacityLayer),
isA<OpacityLayer>().having((OpacityLayer p0) => p0.alpha, 'alpha', lessThan(255)),
);
for (var i = 0; i < 11; i++) {
await tester.drag(find.byKey(SubCategoryScreenView.scrollKey), const Offset(0, -50.0));
await tester.pumpAndSettle();
}
expect(RenderRebuildTracker.count, greaterThan(1));
expect(tester.layers.whereType<OpacityLayer>(), isEmpty);
});
// This is a regression test for https://github.com/flutter/flutter/issues/132030.
testWidgets('FlexibleSpaceBarSettings.hasLeading provides a gap between leading and title', (
WidgetTester tester,
) async {
final customSettings =
FlexibleSpaceBar.createSettings(
currentExtent: 200.0,
hasLeading: true,
child: AppBar(
leading: const Icon(Icons.menu),
flexibleSpace: FlexibleSpaceBar(
title: Text('title ' * 10),
// Set centerTitle to false to create a gap between the leading widget
// and the long title.
centerTitle: false,
),
),
)
as FlexibleSpaceBarSettings;
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: CustomScrollView(
slivers: <Widget>[
SliverPersistentHeader(
floating: true,
pinned: true,
delegate: TestDelegate(settings: customSettings),
),
SliverToBoxAdapter(child: Container(height: 1200.0, color: Colors.orange[400])),
],
),
),
),
);
await tester.pumpAndSettle();
expect(tester.getTopLeft(find.byType(Text)).dx, closeTo(72.0, 0.01));
});
// This is a regression test for https://github.com/flutter/flutter/issues/135698.
testWidgets(
'_FlexibleSpaceHeaderOpacity with near zero opacity avoids compositing',
(WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
home: Material(
child: NestedScrollView(
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
SliverOverlapAbsorber(
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
sliver: const SliverAppBar(
pinned: true,
expandedHeight: 200.0,
collapsedHeight: 56.0,
flexibleSpace: FlexibleSpaceBar(background: SizedBox()),
),
),
];
},
body: const SingleChildScrollView(
child: Column(children: <Widget>[Placeholder(fallbackHeight: 300.0)]),
),
),
),
),
);
// Drag the scroll view to the top to collapse the sliver app bar.
// Ensure collapsed height - current extent is near zero for the
// FlexibleSpaceBar to avoid compositing.
await tester.drag(
find.byType(SingleChildScrollView),
const Offset(0, -(200.0 - 56.08787892026129)),
);
await tester.pumpAndSettle();
expect(tester.takeException(), isNull);
},
variant: TargetPlatformVariant.mobile(),
);
// This is a regression test for https://github.com/flutter/flutter/issues/138608.
testWidgets('FlexibleSpaceBar centers title with a leading widget', (WidgetTester tester) async {
await tester.pumpWidget(
const MaterialApp(
home: Material(
child: CustomScrollView(
slivers: <Widget>[
SliverAppBar(
leading: Icon(Icons.menu),
flexibleSpace: FlexibleSpaceBar(centerTitle: true, title: Text('X')),
),
],
),
),
),
);
final Offset appBarCenter = tester.getCenter(find.byType(AppBar));
final Offset titleCenter = tester.getCenter(find.text('X'));
expect(appBarCenter.dx, titleCenter.dx);
});
// This is a regression test for https://github.com/flutter/flutter/issues/138296.
testWidgets('Material3 - Default title color', (WidgetTester tester) async {
final theme = ThemeData();
await tester.pumpWidget(
MaterialApp(
theme: theme, // Provide the expected theme data.
home: const Material(
child: CustomScrollView(
slivers: <Widget>[SliverAppBar(flexibleSpace: FlexibleSpaceBar(title: Text('Title')))],
),
),
),
);
final DefaultTextStyle textStyle = DefaultTextStyle.of(tester.element(find.text('Title')));
expect(textStyle.style.color, theme.textTheme.titleLarge!.color);
});
testWidgets('FlexibleSpaceBar does not crash at zero area', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
home: Center(
child: SizedBox.shrink(
child: Scaffold(
appBar: AppBar(flexibleSpace: const FlexibleSpaceBar(title: Text('X'))),
),
),
),
),
);
expect(tester.getSize(find.byType(FlexibleSpaceBar)), Size.zero);
});
}
class TestDelegate extends SliverPersistentHeaderDelegate {
const TestDelegate({required this.settings});
final FlexibleSpaceBarSettings settings;
@override
double get maxExtent => settings.maxExtent;
@override
double get minExtent => settings.minExtent;
@override
Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
return settings;
}
@override
bool shouldRebuild(TestDelegate oldDelegate) => false;
}
class RebuildTracker extends SingleChildRenderObjectWidget {
const RebuildTracker({super.key});
@override
RenderObject createRenderObject(BuildContext context) {
return RenderRebuildTracker();
}
}
class RenderRebuildTracker extends RenderProxyBox {
static int count = 0;
@override
void paint(PaintingContext context, Offset offset) {
count++;
super.paint(context, offset);
}
}
class SubCategoryScreenView extends StatefulWidget {
const SubCategoryScreenView({super.key});
static const Key scrollKey = Key('orange box');
@override
State<SubCategoryScreenView> createState() => _SubCategoryScreenViewState();
}
class _SubCategoryScreenViewState extends State<SubCategoryScreenView>
with TickerProviderStateMixin {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(centerTitle: true, title: const Text('Test')),
body: CustomScrollView(
key: SubCategoryScreenView.scrollKey,
slivers: <Widget>[
SliverAppBar(
leading: const SizedBox(),
expandedHeight: MediaQuery.of(context).size.width / 1.7,
collapsedHeight: 0,
toolbarHeight: 0,
titleSpacing: 0,
leadingWidth: 0,
flexibleSpace: const FlexibleSpaceBar(
background: AspectRatio(aspectRatio: 1.7, child: RebuildTracker()),
),
),
const SliverToBoxAdapter(child: SizedBox(height: 12)),
SliverGrid.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3),
itemCount: 300,
itemBuilder: (BuildContext context, int index) {
return Card(
color: Colors.amber,
child: Center(child: Text('$index')),
);
},
),
const SliverToBoxAdapter(child: SizedBox(height: 12)),
],
),
);
}
}