Unverified Commit 0c7d84aa authored by Daniel Iglesia's avatar Daniel Iglesia Committed by GitHub

Add AppBar.forceMaterialTransparency (#101248) (#116867)

* Add AppBar.forceMaterialTransparency (#101248)

Allows gestures to reach widgets beneath the AppBar (when Scaffold.extendBodyBehindAppBar is true).
parent 96597c25
......@@ -220,6 +220,7 @@ class AppBar extends StatefulWidget implements PreferredSizeWidget {
this.toolbarTextStyle,
this.titleTextStyle,
this.systemOverlayStyle,
this.forceMaterialTransparency = false,
}) : assert(automaticallyImplyLeading != null),
assert(elevation == null || elevation >= 0.0),
assert(notificationPredicate != null),
......@@ -789,6 +790,21 @@ class AppBar extends StatefulWidget implements PreferredSizeWidget {
/// * [SystemChrome.setSystemUIOverlayStyle]
final SystemUiOverlayStyle? systemOverlayStyle;
/// {@template flutter.material.appbar.forceMaterialTransparency}
/// Forces the AppBar's Material widget type to be [MaterialType.transparency]
/// (instead of Material's default type).
///
/// This will remove the visual display of [backgroundColor] and [elevation],
/// and affect other characteristics of the AppBar's Material widget.
///
/// Provided for cases where the app bar is to be transparent, and gestures
/// must pass through the app bar to widgets beneath the app bar (i.e. with
/// [Scaffold.extendBodyBehindAppBar] set to true).
///
/// Defaults to false.
/// {@endtemplate}
final bool forceMaterialTransparency;
bool _getEffectiveCenterTitle(ThemeData theme) {
bool platformCenter() {
assert(theme.platform != null);
......@@ -1193,6 +1209,9 @@ class _AppBarState extends State<AppBar> {
child: Material(
color: backgroundColor,
elevation: effectiveElevation,
type: widget.forceMaterialTransparency
? MaterialType.transparency
: MaterialType.canvas,
shadowColor: widget.shadowColor
?? appBarTheme.shadowColor
?? defaults.shadowColor,
......@@ -1249,6 +1268,7 @@ class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
required this.toolbarTextStyle,
required this.titleTextStyle,
required this.systemOverlayStyle,
required this.forceMaterialTransparency,
}) : assert(primary || topPadding == 0.0),
assert(
!floating || (snapConfiguration == null && showOnScreenConfiguration == null) || vsync != null,
......@@ -1290,6 +1310,7 @@ class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
final TextStyle? titleTextStyle;
final SystemUiOverlayStyle? systemOverlayStyle;
final double _bottomHeight;
final bool forceMaterialTransparency;
@override
double get minExtent => collapsedHeight;
......@@ -1362,6 +1383,7 @@ class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
toolbarTextStyle: toolbarTextStyle,
titleTextStyle: titleTextStyle,
systemOverlayStyle: systemOverlayStyle,
forceMaterialTransparency: forceMaterialTransparency,
),
);
return appBar;
......@@ -1401,7 +1423,8 @@ class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
|| backwardsCompatibility != oldDelegate.backwardsCompatibility
|| toolbarTextStyle != oldDelegate.toolbarTextStyle
|| titleTextStyle != oldDelegate.titleTextStyle
|| systemOverlayStyle != oldDelegate.systemOverlayStyle;
|| systemOverlayStyle != oldDelegate.systemOverlayStyle
|| forceMaterialTransparency != oldDelegate.forceMaterialTransparency;
}
@override
......@@ -1550,6 +1573,7 @@ class SliverAppBar extends StatefulWidget {
this.toolbarTextStyle,
this.titleTextStyle,
this.systemOverlayStyle,
this.forceMaterialTransparency = false,
}) : assert(automaticallyImplyLeading != null),
assert(forceElevated != null),
assert(primary != null),
......@@ -2038,6 +2062,11 @@ class SliverAppBar extends StatefulWidget {
/// This property is used to configure an [AppBar].
final SystemUiOverlayStyle? systemOverlayStyle;
/// {@macro flutter.material.appbar.forceMaterialTransparency}
///
/// This property is used to configure an [AppBar].
final bool forceMaterialTransparency;
@override
State<SliverAppBar> createState() => _SliverAppBarState();
}
......@@ -2146,6 +2175,7 @@ class _SliverAppBarState extends State<SliverAppBar> with TickerProviderStateMix
toolbarTextStyle: widget.toolbarTextStyle,
titleTextStyle: widget.titleTextStyle,
systemOverlayStyle: widget.systemOverlayStyle,
forceMaterialTransparency: widget.forceMaterialTransparency,
),
),
);
......
......@@ -1390,6 +1390,95 @@ void main() {
});
});
group('SliverAppBar.forceMaterialTransparency', () {
Material getSliverAppBarMaterial(WidgetTester tester) {
return tester.widget<Material>(find
.descendant(of: find.byType(SliverAppBar), matching: find.byType(Material))
.first);
}
// Generates a MaterialApp with a SliverAppBar in a CustomScrollView.
// The first cell of the scroll view contains a button at its top, and is
// initially scrolled so that it is beneath the SliverAppBar.
Widget buildWidget({
required bool forceMaterialTransparency,
required VoidCallback onPressed
}) {
const double appBarHeight = 120;
return MaterialApp(
home: Scaffold(
body: CustomScrollView(
controller: ScrollController(initialScrollOffset:appBarHeight),
slivers: <Widget>[
SliverAppBar(
collapsedHeight: appBarHeight,
expandedHeight: appBarHeight,
pinned: true,
elevation: 0,
backgroundColor: Colors.transparent,
forceMaterialTransparency: forceMaterialTransparency,
title: const Text('AppBar'),
),
SliverList(
delegate: SliverChildBuilderDelegate((BuildContext context, int index) {
return SizedBox(
height: appBarHeight,
child: index == 0
? Align(
alignment: Alignment.topCenter,
child: TextButton(onPressed: onPressed, child: const Text('press')))
: const SizedBox(),
);
},
childCount: 20,
),
),
]),
),
);
}
testWidgets(
'forceMaterialTransparency == true allows gestures beneath the app bar', (WidgetTester tester) async {
bool buttonWasPressed = false;
final Widget widget = buildWidget(
forceMaterialTransparency:true,
onPressed:() { buttonWasPressed = true; },
);
await tester.pumpWidget(widget);
final Material material = getSliverAppBarMaterial(tester);
expect(material.type, MaterialType.transparency);
final Finder buttonFinder = find.byType(TextButton);
await tester.tap(buttonFinder);
await tester.pump();
expect(buttonWasPressed, isTrue);
});
testWidgets(
'forceMaterialTransparency == false does not allow gestures beneath the app bar', (WidgetTester tester) async {
// Set this, and tester.tap(warnIfMissed:false), to suppress
// errors/warning that the button is not hittable (which is expected).
WidgetController.hitTestWarningShouldBeFatal = false;
bool buttonWasPressed = false;
final Widget widget = buildWidget(
forceMaterialTransparency:false,
onPressed:() { buttonWasPressed = true; },
);
await tester.pumpWidget(widget);
final Material material = getSliverAppBarMaterial(tester);
expect(material.type, MaterialType.canvas);
final Finder buttonFinder = find.byType(TextButton);
await tester.tap(buttonFinder, warnIfMissed:false);
await tester.pump();
expect(buttonWasPressed, isFalse);
});
});
testWidgets('AppBar dimensions, with and without bottom, primary', (WidgetTester tester) async {
const MediaQueryData topPadding100 = MediaQueryData(padding: EdgeInsets.only(top: 100.0));
......@@ -3760,4 +3849,78 @@ void main() {
expect(tester.getTopLeft(find.byKey(titleKey)).dx, leadingWidth + 16.0);
expect(tester.getSize(find.byKey(leadingKey)).width, leadingWidth);
});
group('AppBar.forceMaterialTransparency', () {
Material getAppBarMaterial(WidgetTester tester) {
return tester.widget<Material>(find
.descendant(of: find.byType(AppBar), matching: find.byType(Material))
.first);
}
// Generates a MaterialApp with an AppBar with a TextButton beneath it
// (via extendBodyBehindAppBar = true).
Widget buildWidget({
required bool forceMaterialTransparency,
required VoidCallback onPressed
}) {
return MaterialApp(
home: Scaffold(
extendBodyBehindAppBar: true,
appBar: AppBar(
forceMaterialTransparency: forceMaterialTransparency,
elevation: 3,
backgroundColor: Colors.red,
title: const Text('AppBar'),
),
body: Align(
alignment: Alignment.topCenter,
child: TextButton(
onPressed: onPressed,
child: const Text('press me'),
),
),
),
);
}
testWidgets(
'forceMaterialTransparency == true allows gestures beneath the app bar', (WidgetTester tester) async {
bool buttonWasPressed = false;
final Widget widget = buildWidget(
forceMaterialTransparency:true,
onPressed:() { buttonWasPressed = true; },
);
await tester.pumpWidget(widget);
final Material material = getAppBarMaterial(tester);
expect(material.type, MaterialType.transparency);
final Finder buttonFinder = find.byType(TextButton);
await tester.tap(buttonFinder);
await tester.pump();
expect(buttonWasPressed, isTrue);
});
testWidgets(
'forceMaterialTransparency == false does not allow gestures beneath the app bar', (WidgetTester tester) async {
// Set this, and tester.tap(warnIfMissed:false), to suppress
// errors/warning that the button is not hittable (which is expected).
WidgetController.hitTestWarningShouldBeFatal = false;
bool buttonWasPressed = false;
final Widget widget = buildWidget(
forceMaterialTransparency:false,
onPressed:() { buttonWasPressed = true; },
);
await tester.pumpWidget(widget);
final Material material = getAppBarMaterial(tester);
expect(material.type, MaterialType.canvas);
final Finder buttonFinder = find.byType(TextButton);
await tester.tap(buttonFinder, warnIfMissed:false);
await tester.pump();
expect(buttonWasPressed, isFalse);
});
});
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment