mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Add width property to SnackBarThemeData (#112636)
* Adding snackbar theme data width field * Whitespace formatting * Update docstrings * version update * tidy up * Revert auto text formatting * Text formatting * Remove whitespace * Test tidy * Whitespace fix * y Please enter the commit message for your changes. Lines starting * whitespace * test fixes * de-British-ification * comment modification
This commit is contained in:
parent
248110839d
commit
4862a84bd5
@ -2832,7 +2832,7 @@ class ScaffoldState extends State<Scaffold> with TickerProviderStateMixin, Resto
|
||||
?? themeData.snackBarTheme.behavior
|
||||
?? SnackBarBehavior.fixed;
|
||||
isSnackBarFloating = snackBarBehavior == SnackBarBehavior.floating;
|
||||
snackBarWidth = _messengerSnackBar?._widget.width;
|
||||
snackBarWidth = _messengerSnackBar?._widget.width ?? themeData.snackBarTheme.width;
|
||||
|
||||
_addIfNonNull(
|
||||
children,
|
||||
|
||||
@ -293,8 +293,9 @@ class SnackBar extends StatefulWidget {
|
||||
/// available space. This property is only used when [behavior] is
|
||||
/// [SnackBarBehavior.floating]. It can not be used if [margin] is specified.
|
||||
///
|
||||
/// If this property is null, then the snack bar will take up the full device
|
||||
/// width less the margin.
|
||||
/// If this property is null, then [SnackBarThemeData.width] of
|
||||
/// [ThemeData.snackBarTheme] is used. If that is null, the snack bar will
|
||||
/// take up the full device width less the margin.
|
||||
final double? width;
|
||||
|
||||
/// The shape of the snack bar's [Material].
|
||||
@ -470,6 +471,7 @@ class _SnackBarState extends State<SnackBar> {
|
||||
|
||||
final TextStyle? contentTextStyle = snackBarTheme.contentTextStyle ?? ThemeData(brightness: brightness).textTheme.titleMedium;
|
||||
final SnackBarBehavior snackBarBehavior = widget.behavior ?? snackBarTheme.behavior ?? SnackBarBehavior.fixed;
|
||||
final double? width = widget.width ?? snackBarTheme.width;
|
||||
assert((){
|
||||
// Whether the behavior is set through the constructor or the theme,
|
||||
// assert that our other properties are configured properly.
|
||||
@ -485,7 +487,7 @@ class _SnackBarState extends State<SnackBar> {
|
||||
}
|
||||
}
|
||||
assert(widget.margin == null, message('Margin'));
|
||||
assert(widget.width == null, message('Width'));
|
||||
assert(width == null, message('Width'));
|
||||
}
|
||||
return true;
|
||||
}());
|
||||
@ -567,10 +569,10 @@ class _SnackBarState extends State<SnackBar> {
|
||||
const double topMargin = 5.0;
|
||||
const double bottomMargin = 10.0;
|
||||
// If width is provided, do not include horizontal margins.
|
||||
if (widget.width != null) {
|
||||
if (width != null) {
|
||||
snackBar = Container(
|
||||
margin: const EdgeInsets.only(top: topMargin, bottom: bottomMargin),
|
||||
width: widget.width,
|
||||
width: width,
|
||||
child: snackBar,
|
||||
);
|
||||
} else {
|
||||
|
||||
@ -60,8 +60,12 @@ class SnackBarThemeData with Diagnosticable {
|
||||
this.elevation,
|
||||
this.shape,
|
||||
this.behavior,
|
||||
}) : assert(elevation == null || elevation >= 0.0);
|
||||
|
||||
this.width,
|
||||
}) : assert(elevation == null || elevation >= 0.0),
|
||||
assert(
|
||||
width == null ||
|
||||
(width != null && identical(behavior, SnackBarBehavior.floating)),
|
||||
'Width can only be set if behaviour is SnackBarBehavior.floating');
|
||||
/// Default value for [SnackBar.backgroundColor].
|
||||
///
|
||||
/// If null, [SnackBar] defaults to dark grey: `Color(0xFF323232)`.
|
||||
@ -104,6 +108,13 @@ class SnackBarThemeData with Diagnosticable {
|
||||
/// If null, [SnackBar] will default to [SnackBarBehavior.fixed].
|
||||
final SnackBarBehavior? behavior;
|
||||
|
||||
/// Default value for [SnackBar.width].
|
||||
///
|
||||
/// If this property is null, then the snack bar will take up the full device
|
||||
/// width less the margin. This value is only used when [behavior] is
|
||||
/// [SnackBarBehavior.floating].
|
||||
final double? width;
|
||||
|
||||
/// Creates a copy of this object with the given fields replaced with the
|
||||
/// new values.
|
||||
SnackBarThemeData copyWith({
|
||||
@ -114,6 +125,7 @@ class SnackBarThemeData with Diagnosticable {
|
||||
double? elevation,
|
||||
ShapeBorder? shape,
|
||||
SnackBarBehavior? behavior,
|
||||
double? width,
|
||||
}) {
|
||||
return SnackBarThemeData(
|
||||
backgroundColor: backgroundColor ?? this.backgroundColor,
|
||||
@ -123,6 +135,7 @@ class SnackBarThemeData with Diagnosticable {
|
||||
elevation: elevation ?? this.elevation,
|
||||
shape: shape ?? this.shape,
|
||||
behavior: behavior ?? this.behavior,
|
||||
width: width ?? this.width,
|
||||
);
|
||||
}
|
||||
|
||||
@ -141,19 +154,21 @@ class SnackBarThemeData with Diagnosticable {
|
||||
elevation: lerpDouble(a?.elevation, b?.elevation, t),
|
||||
shape: ShapeBorder.lerp(a?.shape, b?.shape, t),
|
||||
behavior: t < 0.5 ? a?.behavior : b?.behavior,
|
||||
width: lerpDouble(a?.width, b?.width, t),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(
|
||||
backgroundColor,
|
||||
actionTextColor,
|
||||
disabledActionTextColor,
|
||||
contentTextStyle,
|
||||
elevation,
|
||||
shape,
|
||||
behavior,
|
||||
);
|
||||
backgroundColor,
|
||||
actionTextColor,
|
||||
disabledActionTextColor,
|
||||
contentTextStyle,
|
||||
elevation,
|
||||
shape,
|
||||
behavior,
|
||||
width,
|
||||
);
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
@ -170,7 +185,8 @@ class SnackBarThemeData with Diagnosticable {
|
||||
&& other.contentTextStyle == contentTextStyle
|
||||
&& other.elevation == elevation
|
||||
&& other.shape == shape
|
||||
&& other.behavior == behavior;
|
||||
&& other.behavior == behavior
|
||||
&& other.width == width;
|
||||
}
|
||||
|
||||
@override
|
||||
@ -183,5 +199,6 @@ class SnackBarThemeData with Diagnosticable {
|
||||
properties.add(DoubleProperty('elevation', elevation, defaultValue: null));
|
||||
properties.add(DiagnosticsProperty<ShapeBorder>('shape', shape, defaultValue: null));
|
||||
properties.add(DiagnosticsProperty<SnackBarBehavior>('behavior', behavior, defaultValue: null));
|
||||
properties.add(DoubleProperty('width', width, defaultValue: null));
|
||||
}
|
||||
}
|
||||
|
||||
@ -724,6 +724,93 @@ void main() {
|
||||
expect(snackBarBottomRight.dx, (800 + width) / 2); // Device width is 800.
|
||||
});
|
||||
|
||||
testWidgets('Snackbar width can be customized from ThemeData',
|
||||
(WidgetTester tester) async {
|
||||
const double width = 200.0;
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: ThemeData(
|
||||
snackBarTheme: const SnackBarThemeData(
|
||||
width: width, behavior: SnackBarBehavior.floating),
|
||||
),
|
||||
home: Scaffold(
|
||||
body: Builder(
|
||||
builder: (BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Feeling snackish'),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: const Text('X'),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.tap(find.text('X'));
|
||||
await tester.pump(); // start animation
|
||||
await tester.pump(const Duration(milliseconds: 750));
|
||||
|
||||
final Finder materialFinder = find.descendant(
|
||||
of: find.byType(SnackBar),
|
||||
matching: find.byType(Material),
|
||||
);
|
||||
final Offset snackBarBottomLeft = tester.getBottomLeft(materialFinder);
|
||||
final Offset snackBarBottomRight = tester.getBottomRight(materialFinder);
|
||||
expect(snackBarBottomLeft.dx, (800 - width) / 2); // Device width is 800.
|
||||
expect(snackBarBottomRight.dx, (800 + width) / 2); // Device width is 800.
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'Snackbar width customization takes preference of widget over theme',
|
||||
(WidgetTester tester) async {
|
||||
const double themeWidth = 200.0;
|
||||
const double widgetWidth = 400.0;
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: ThemeData(
|
||||
snackBarTheme: const SnackBarThemeData(
|
||||
width: themeWidth, behavior: SnackBarBehavior.floating),
|
||||
),
|
||||
home: Scaffold(
|
||||
body: Builder(
|
||||
builder: (BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Feeling super snackish'),
|
||||
width: widgetWidth,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: const Text('X'),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.tap(find.text('X'));
|
||||
await tester.pump(); // start animation
|
||||
await tester.pump(const Duration(milliseconds: 750));
|
||||
|
||||
final Finder materialFinder = find.descendant(
|
||||
of: find.byType(SnackBar),
|
||||
matching: find.byType(Material),
|
||||
);
|
||||
final Offset snackBarBottomLeft = tester.getBottomLeft(materialFinder);
|
||||
final Offset snackBarBottomRight = tester.getBottomRight(materialFinder);
|
||||
expect(snackBarBottomLeft.dx, (800 - widgetWidth) / 2); // Device width is 800.
|
||||
expect(snackBarBottomRight.dx, (800 + widgetWidth) / 2); // Device width is 800.
|
||||
});
|
||||
|
||||
testWidgets('Snackbar labels can be colored', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
|
||||
@ -21,9 +21,22 @@ void main() {
|
||||
expect(snackBarTheme.elevation, null);
|
||||
expect(snackBarTheme.shape, null);
|
||||
expect(snackBarTheme.behavior, null);
|
||||
expect(snackBarTheme.width, null);
|
||||
});
|
||||
|
||||
testWidgets('Default SnackBarThemeData debugFillProperties', (WidgetTester tester) async {
|
||||
test(
|
||||
'SnackBarTheme throws assertion if width is provided with fixed behaviour',
|
||||
() {
|
||||
expect(
|
||||
() => SnackBarThemeData(
|
||||
behavior: SnackBarBehavior.fixed,
|
||||
width: 300.0,
|
||||
),
|
||||
throwsAssertionError);
|
||||
});
|
||||
|
||||
testWidgets('Default SnackBarThemeData debugFillProperties',
|
||||
(WidgetTester tester) async {
|
||||
final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
|
||||
const SnackBarThemeData().debugFillProperties(builder);
|
||||
|
||||
@ -45,6 +58,7 @@ void main() {
|
||||
elevation: 2.0,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(2.0))),
|
||||
behavior: SnackBarBehavior.floating,
|
||||
width: 400.0,
|
||||
).debugFillProperties(builder);
|
||||
|
||||
final List<String> description = builder.properties
|
||||
@ -60,6 +74,7 @@ void main() {
|
||||
'elevation: 2.0',
|
||||
'shape: RoundedRectangleBorder(BorderSide(width: 0.0, style: none), BorderRadius.circular(2.0))',
|
||||
'behavior: SnackBarBehavior.floating',
|
||||
'width: 400.0',
|
||||
]);
|
||||
});
|
||||
|
||||
@ -145,6 +160,7 @@ void main() {
|
||||
const ShapeBorder shape = RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(9.0)),
|
||||
);
|
||||
const double snackBarWidth = 400.0;
|
||||
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
theme: ThemeData(snackBarTheme: _snackBarTheme()),
|
||||
@ -155,6 +171,8 @@ void main() {
|
||||
onTap: () {
|
||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
||||
backgroundColor: backgroundColor,
|
||||
behavior: SnackBarBehavior.floating,
|
||||
width: snackBarWidth,
|
||||
elevation: elevation,
|
||||
shape: shape,
|
||||
content: const Text('I am a snack bar.'),
|
||||
@ -177,13 +195,20 @@ void main() {
|
||||
await tester.pump(); // start animation
|
||||
await tester.pump(const Duration(milliseconds: 750));
|
||||
|
||||
final Finder materialFinder = _getSnackBarMaterialFinder(tester);
|
||||
final Material material = _getSnackBarMaterial(tester);
|
||||
final RenderParagraph button = _getSnackBarActionTextRenderObject(tester, action);
|
||||
final RenderParagraph button =
|
||||
_getSnackBarActionTextRenderObject(tester, action);
|
||||
|
||||
expect(material.color, backgroundColor);
|
||||
expect(material.elevation, elevation);
|
||||
expect(material.shape, shape);
|
||||
expect(button.text.style!.color, textColor);
|
||||
// Assert width.
|
||||
final Offset snackBarBottomLeft = tester.getBottomLeft(materialFinder.first);
|
||||
final Offset snackBarBottomRight = tester.getBottomRight(materialFinder.first);
|
||||
expect(snackBarBottomLeft.dx, (800 - snackBarWidth) / 2); // Device width is 800.
|
||||
expect(snackBarBottomRight.dx, (800 + snackBarWidth) / 2); // Device width is 800.
|
||||
});
|
||||
|
||||
testWidgets('SnackBar theme behavior is correct for floating', (WidgetTester tester) async {
|
||||
@ -376,10 +401,15 @@ SnackBarThemeData _snackBarTheme() {
|
||||
|
||||
Material _getSnackBarMaterial(WidgetTester tester) {
|
||||
return tester.widget<Material>(
|
||||
find.descendant(
|
||||
of: find.byType(SnackBar),
|
||||
matching: find.byType(Material),
|
||||
).first,
|
||||
_getSnackBarMaterialFinder(tester).first,
|
||||
);
|
||||
}
|
||||
|
||||
Finder _getSnackBarMaterialFinder(WidgetTester tester) {
|
||||
return find.descendant(
|
||||
of: find.byType(SnackBar),
|
||||
matching: find.byType(Material),
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user