mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
WIP Commits separated as follows: - Update lints in analysis_options files - Run `dart fix --apply` - Clean up leftover analysis issues - Run `dart format .` in the right places. Local analysis and testing passes. Checking CI now. Part of https://github.com/flutter/flutter/issues/178827 - Adoption of flutter_lints in examples/api coming in a separate change (cc @loic-sharma) ## Pre-launch Checklist - [ ] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [ ] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [ ] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [ ] I signed the [CLA]. - [ ] I listed at least one issue that this PR fixes in the description above. - [ ] I updated/added relevant documentation (doc comments with `///`). - [ ] I added new tests to check the change I am making, or this PR is [test-exempt]. - [ ] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [ ] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
1726 lines
63 KiB
Dart
1726 lines
63 KiB
Dart
// 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)),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|