Factor out Container objects (#153619)

This pull request follows up on [a PR from 4 months ago](https://github.com/flutter/flutter/pull/147432) that aimed to reduce the number of `Container` objects in the framework.

I feel like now's a good time to wrap it up!
(especially since I've gained a grasp of how "rebase" vs. "merge commit" can [affect test results](https://github.com/flutter/flutter/blob/master/docs/contributing/Tree-hygiene.md#using-git) 🙂)

<br>

resolves #147431
This commit is contained in:
Nate Wilson 2024-09-11 17:59:53 -06:00 committed by GitHub
parent 5d5e633c44
commit 360e42c7af
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 417 additions and 344 deletions

View file

@ -389,8 +389,8 @@ class _ExpansionPanelListState extends State<ExpansionPanelList> {
_isChildExpanded(index),
);
Widget expandIconContainer = Container(
margin: const EdgeInsetsDirectional.only(end: 8.0),
Widget expandIconPadded = Padding(
padding: const EdgeInsetsDirectional.only(end: 8.0),
child: ExpandIcon(
color: widget.expandIconColor,
disabledColor: child.canTapOnHeader ? widget.expandIconColor : null,
@ -406,10 +406,10 @@ class _ExpansionPanelListState extends State<ExpansionPanelList> {
if (!child.canTapOnHeader) {
final MaterialLocalizations localizations = MaterialLocalizations.of(context);
expandIconContainer = Semantics(
expandIconPadded = Semantics(
label: _isChildExpanded(index)? localizations.expandedIconTapHint : localizations.collapsedIconTapHint,
container: true,
child: expandIconContainer,
child: expandIconPadded,
);
}
Widget header = Row(
@ -425,7 +425,7 @@ class _ExpansionPanelListState extends State<ExpansionPanelList> {
),
),
),
expandIconContainer,
expandIconPadded,
],
);
if (child.canTapOnHeader) {
@ -446,7 +446,7 @@ class _ExpansionPanelListState extends State<ExpansionPanelList> {
children: <Widget>[
header,
AnimatedCrossFade(
firstChild: Container(height: 0.0),
firstChild: const LimitedBox(maxWidth: 0.0, child: SizedBox(width: double.infinity, height: 0)),
secondChild: child.body,
firstCurve: const Interval(0.0, 0.6, curve: Curves.fastOutSlowIn),
secondCurve: const Interval(0.4, 1.0, curve: Curves.fastOutSlowIn),

View file

@ -273,9 +273,7 @@ class _FlexibleSpaceBarState extends State<FlexibleSpaceBar> {
sigmaX: blurAmount,
sigmaY: blurAmount,
),
child: Container(
color: Colors.transparent,
),
child: const ColoredBox(color: Colors.transparent),
),
));
}
@ -331,7 +329,7 @@ class _FlexibleSpaceBarState extends State<FlexibleSpaceBar> {
final Matrix4 scaleTransform = Matrix4.identity()
..scale(scaleValue, scaleValue, 1.0);
final Alignment titleAlignment = _getTitleAlignment(effectiveCenterTitle);
children.add(Container(
children.add(Padding(
padding: padding,
child: Transform(
alignment: titleAlignment,
@ -342,10 +340,12 @@ class _FlexibleSpaceBarState extends State<FlexibleSpaceBar> {
style: titleStyle,
child: LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
return Container(
return SizedBox(
width: constraints.maxWidth / scaleValue,
alignment: titleAlignment,
child: title,
child: Align(
alignment: titleAlignment,
child: title,
),
);
},
),

View file

@ -350,42 +350,47 @@ class _NavigationDestinationBuilder extends StatelessWidget {
final NavigationDrawerThemeData navigationDrawerTheme = NavigationDrawerTheme.of(context);
final NavigationDrawerThemeData defaults = _NavigationDrawerDefaultsM3(context);
final Row destinationBody = Row(
children: <Widget>[
const SizedBox(width: 16),
buildIcon(context),
const SizedBox(width: 12),
buildLabel(context),
],
final InkWell inkWell = InkWell(
highlightColor: Colors.transparent,
onTap: enabled ? info.onTap : null,
customBorder: info.indicatorShape ?? navigationDrawerTheme.indicatorShape ?? defaults.indicatorShape!,
child: Stack(
alignment: Alignment.center,
children: <Widget>[
NavigationIndicator(
animation: info.selectedAnimation,
color: info.indicatorColor ?? navigationDrawerTheme.indicatorColor ?? defaults.indicatorColor!,
shape: info.indicatorShape ?? navigationDrawerTheme.indicatorShape ?? defaults.indicatorShape!,
width: (navigationDrawerTheme.indicatorSize ?? defaults.indicatorSize!).width,
height: (navigationDrawerTheme.indicatorSize ?? defaults.indicatorSize!).height,
),
Row(
children: <Widget>[
const SizedBox(width: 16),
buildIcon(context),
const SizedBox(width: 12),
buildLabel(context),
],
),
],
),
);
return Container(
final Widget destination = Padding(
padding: info.tilePadding,
color: backgroundColor ?? navigationDrawerTheme.backgroundColor,
child: _NavigationDestinationSemantics(
child: SizedBox(
height: navigationDrawerTheme.tileHeight ?? defaults.tileHeight,
child: InkWell(
highlightColor: Colors.transparent,
onTap: enabled ? info.onTap : null,
customBorder: info.indicatorShape ?? navigationDrawerTheme.indicatorShape ?? defaults.indicatorShape!,
child: Stack(
alignment: Alignment.center,
children: <Widget>[
NavigationIndicator(
animation: info.selectedAnimation,
color: info.indicatorColor ?? navigationDrawerTheme.indicatorColor ?? defaults.indicatorColor!,
shape: info.indicatorShape ?? navigationDrawerTheme.indicatorShape ?? defaults.indicatorShape!,
width: (navigationDrawerTheme.indicatorSize ?? defaults.indicatorSize!).width,
height: (navigationDrawerTheme.indicatorSize ?? defaults.indicatorSize!).height,
),
destinationBody
],
),
),
child: inkWell,
),
),
);
final Color? color = backgroundColor ?? navigationDrawerTheme.backgroundColor;
if (color != null) {
return ColoredBox(color: color, child: destination);
}
return destination;
}
}

View file

@ -746,39 +746,38 @@ class _RailDestinationState extends State<_RailDestination> {
indicatorVerticalPadding + indicatorVerticalOffset,
);
}
content = Container(
constraints: BoxConstraints(
minWidth: widget.minWidth,
minHeight: minHeight,
),
padding: widget.padding ?? const EdgeInsets.symmetric(horizontal: _horizontalDestinationPadding),
child: ClipRect(
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
topSpacing,
_AddIndicator(
addIndicator: widget.useIndicator,
indicatorColor: widget.indicatorColor,
indicatorShape: widget.indicatorShape,
isCircular: false,
indicatorAnimation: widget.destinationAnimation,
child: themedIcon,
),
labelSpacing,
Align(
alignment: Alignment.topCenter,
heightFactor: appearingAnimationValue,
widthFactor: 1.0,
child: FadeTransition(
alwaysIncludeSemantics: true,
opacity: labelFadeAnimation,
child: styledLabel,
content = ConstrainedBox(
constraints: BoxConstraints(minWidth: widget.minWidth, minHeight: minHeight),
child: Padding(
padding: widget.padding ?? const EdgeInsets.symmetric(horizontal: _horizontalDestinationPadding),
child: ClipRect(
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
topSpacing,
_AddIndicator(
addIndicator: widget.useIndicator,
indicatorColor: widget.indicatorColor,
indicatorShape: widget.indicatorShape,
isCircular: false,
indicatorAnimation: widget.destinationAnimation,
child: themedIcon,
),
),
bottomSpacing,
],
labelSpacing,
Align(
alignment: Alignment.topCenter,
heightFactor: appearingAnimationValue,
widthFactor: 1.0,
child: FadeTransition(
alwaysIncludeSemantics: true,
opacity: labelFadeAnimation,
child: styledLabel,
),
),
bottomSpacing,
],
),
),
),
);
@ -799,27 +798,26 @@ class _RailDestinationState extends State<_RailDestination> {
indicatorVerticalPadding + indicatorVerticalOffset,
);
}
content = Container(
constraints: BoxConstraints(
minWidth: widget.minWidth,
minHeight: minHeight,
),
padding: widget.padding ?? const EdgeInsets.symmetric(horizontal: _horizontalDestinationPadding),
child: Column(
children: <Widget>[
topSpacing,
_AddIndicator(
addIndicator: widget.useIndicator,
indicatorColor: widget.indicatorColor,
indicatorShape: widget.indicatorShape,
isCircular: false,
indicatorAnimation: widget.destinationAnimation,
child: themedIcon,
),
labelSpacing,
styledLabel,
bottomSpacing,
],
content = ConstrainedBox(
constraints: BoxConstraints(minWidth: widget.minWidth, minHeight: minHeight),
child: Padding(
padding: widget.padding ?? const EdgeInsets.symmetric(horizontal: _horizontalDestinationPadding),
child: Column(
children: <Widget>[
topSpacing,
_AddIndicator(
addIndicator: widget.useIndicator,
indicatorColor: widget.indicatorColor,
indicatorShape: widget.indicatorShape,
isCircular: false,
indicatorAnimation: widget.destinationAnimation,
child: themedIcon,
),
labelSpacing,
styledLabel,
bottomSpacing,
],
),
),
);
}

View file

@ -158,16 +158,18 @@ class _OpenUpwardsPageTransitionState extends State<_OpenUpwardsPageTransition>
return AnimatedBuilder(
animation: widget.animation,
builder: (BuildContext context, Widget? child) {
return Container(
return ColoredBox(
color: Colors.black.withOpacity(opacityAnimation.value),
alignment: Alignment.bottomLeft,
child: ClipRect(
child: SizedBox(
height: clipAnimation.value,
child: OverflowBox(
alignment: Alignment.bottomLeft,
maxHeight: size.height,
child: child,
child: Align(
alignment: Alignment.bottomLeft,
child: ClipRect(
child: SizedBox(
height: clipAnimation.value,
child: OverflowBox(
alignment: Alignment.bottomLeft,
maxHeight: size.height,
child: child,
),
),
),
),

View file

@ -500,7 +500,8 @@ class PaginatedDataTableState extends State<PaginatedDataTable> {
})
.toList();
footerWidgets.addAll(<Widget>[
Container(width: 14.0), // to match trailing padding in case we overflow and end up scrolling
// Match trailing padding, in case we overflow and end up scrolling.
const SizedBox(width: 14.0),
Text(localizations.rowsPerPageTitle),
ConstrainedBox(
constraints: const BoxConstraints(minWidth: 64.0), // 40.0 for the text, 24.0 for the icon
@ -519,7 +520,7 @@ class PaginatedDataTableState extends State<PaginatedDataTable> {
]);
}
footerWidgets.addAll(<Widget>[
Container(width: 32.0),
const SizedBox(width: 32.0),
Text(
localizations.pageRowsInfoTitle(
_firstRowIndex + 1,
@ -528,7 +529,7 @@ class PaginatedDataTableState extends State<PaginatedDataTable> {
_rowCountApproximate,
),
),
Container(width: 32.0),
const SizedBox(width: 32.0),
if (widget.showFirstLastButtons)
IconButton(
icon: Icon(Icons.skip_previous, color: widget.arrowHeadColor),
@ -542,7 +543,7 @@ class PaginatedDataTableState extends State<PaginatedDataTable> {
tooltip: localizations.previousPageTooltip,
onPressed: _firstRowIndex <= 0 ? null : _handlePrevious,
),
Container(width: 24.0),
const SizedBox(width: 24.0),
IconButton(
icon: Icon(Icons.chevron_right, color: widget.arrowHeadColor),
padding: EdgeInsets.zero,
@ -558,7 +559,7 @@ class PaginatedDataTableState extends State<PaginatedDataTable> {
? null
: _handleLast,
),
Container(width: 14.0),
const SizedBox(width: 14.0),
]);
// CARD

View file

@ -375,15 +375,22 @@ class PopupMenuItemState<T, W extends PopupMenuItem<T>> extends State<W> {
if (!widget.enabled && !theme.useMaterial3) {
style = style.copyWith(color: theme.disabledColor);
}
final EdgeInsetsGeometry padding = widget.padding
?? (theme.useMaterial3 ? _PopupMenuDefaultsM3.menuItemPadding : _PopupMenuDefaultsM2.menuItemPadding);
Widget item = AnimatedDefaultTextStyle(
style: style,
duration: kThemeChangeDuration,
child: Container(
alignment: AlignmentDirectional.centerStart,
child: ConstrainedBox(
constraints: BoxConstraints(minHeight: widget.height),
padding: widget.padding ?? (theme.useMaterial3 ? _PopupMenuDefaultsM3.menuItemPadding : _PopupMenuDefaultsM2.menuItemPadding),
child: buildChild(),
child: Padding(
key: const Key('menu item padding'),
padding: padding,
child: Align(
alignment: AlignmentDirectional.centerStart,
child: buildChild(),
),
),
),
);

View file

@ -729,7 +729,7 @@ class _CircularProgressIndicatorState extends State<CircularProgressIndicator> w
return widget._buildSemanticsWrapper(
context: context,
child: Container(
child: ConstrainedBox(
constraints: const BoxConstraints(
minWidth: _kMinCircularProgressIndicatorSize,
minHeight: _kMinCircularProgressIndicatorSize,
@ -1000,32 +1000,33 @@ class _RefreshProgressIndicatorState extends _CircularProgressIndicatorState {
return widget._buildSemanticsWrapper(
context: context,
child: Container(
width: _indicatorSize,
height: _indicatorSize,
margin: widget.indicatorMargin,
child: Material(
type: MaterialType.circle,
color: backgroundColor,
elevation: widget.elevation,
child: Padding(
padding: widget.indicatorPadding,
child: Opacity(
opacity: opacity,
child: Transform.rotate(
angle: rotation,
child: CustomPaint(
painter: _RefreshProgressIndicatorPainter(
valueColor: valueColor,
value: null, // Draw the indeterminate progress indicator.
headValue: headValue,
tailValue: tailValue,
offsetValue: offsetValue,
rotationValue: rotationValue,
strokeWidth: widget.strokeWidth,
strokeAlign: widget.strokeAlign,
arrowheadScale: arrowheadScale,
strokeCap: widget.strokeCap,
child: Padding(
padding: widget.indicatorMargin,
child: SizedBox.fromSize(
size: const Size.square(_indicatorSize),
child: Material(
type: MaterialType.circle,
color: backgroundColor,
elevation: widget.elevation,
child: Padding(
padding: widget.indicatorPadding,
child: Opacity(
opacity: opacity,
child: Transform.rotate(
angle: rotation,
child: CustomPaint(
painter: _RefreshProgressIndicatorPainter(
valueColor: valueColor,
value: null, // Draw the indeterminate progress indicator.
headValue: headValue,
tailValue: tailValue,
offsetValue: offsetValue,
rotationValue: rotationValue,
strokeWidth: widget.strokeWidth,
strokeAlign: widget.strokeAlign,
arrowheadScale: arrowheadScale,
strokeCap: widget.strokeCap,
),
),
),
),

View file

@ -671,40 +671,38 @@ class RefreshIndicatorState extends State<RefreshIndicator>
child: SizeTransition(
axisAlignment: _isIndicatorAtTop! ? 1.0 : -1.0,
sizeFactor: _positionFactor, // This is what brings it down.
child: Container(
child: Padding(
padding: _isIndicatorAtTop!
? EdgeInsets.only(top: widget.displacement)
: EdgeInsets.only(bottom: widget.displacement),
alignment: _isIndicatorAtTop!
? Alignment.topCenter
: Alignment.bottomCenter,
child: ScaleTransition(
scale: _scaleFactor,
child: AnimatedBuilder(
animation: _positionController,
builder: (BuildContext context, Widget? child) {
final Widget materialIndicator = RefreshProgressIndicator(
semanticsLabel: widget.semanticsLabel ??
MaterialLocalizations.of(context)
.refreshIndicatorSemanticLabel,
semanticsValue: widget.semanticsValue,
value: showIndeterminateIndicator ? null : _value.value,
valueColor: _valueColor,
backgroundColor: widget.backgroundColor,
strokeWidth: widget.strokeWidth,
elevation: widget.elevation,
);
child: Align(
alignment: _isIndicatorAtTop!
? Alignment.topCenter
: Alignment.bottomCenter,
child: ScaleTransition(
scale: _scaleFactor,
child: AnimatedBuilder(
animation: _positionController,
builder: (BuildContext context, Widget? child) {
final Widget materialIndicator = RefreshProgressIndicator(
semanticsLabel: widget.semanticsLabel ?? MaterialLocalizations.of(context).refreshIndicatorSemanticLabel,
semanticsValue: widget.semanticsValue,
value: showIndeterminateIndicator ? null : _value.value,
valueColor: _valueColor,
backgroundColor: widget.backgroundColor,
strokeWidth: widget.strokeWidth,
elevation: widget.elevation,
);
final Widget cupertinoIndicator = CupertinoActivityIndicator(
color: widget.color,
);
final Widget cupertinoIndicator = CupertinoActivityIndicator(
color: widget.color,
);
switch (widget._indicatorType) {
case _IndicatorType.material:
return materialIndicator;
switch (widget._indicatorType) {
case _IndicatorType.material:
return materialIndicator;
case _IndicatorType.adaptive:
{
case _IndicatorType.adaptive:
final ThemeData theme = Theme.of(context);
switch (theme.platform) {
case TargetPlatform.android:
@ -716,11 +714,12 @@ class RefreshIndicatorState extends State<RefreshIndicator>
case TargetPlatform.macOS:
return cupertinoIndicator;
}
}
case _IndicatorType.noSpinner:
return Container();
}
},
case _IndicatorType.noSpinner:
return Container();
}
},
),
),
),
),

View file

@ -2993,13 +2993,15 @@ class ScaffoldState extends State<Scaffold> with TickerProviderStateMixin, Resto
child: SafeArea(
top: false,
child: IntrinsicHeight(
child: Container(
alignment: widget.persistentFooterAlignment,
child: Padding(
padding: const EdgeInsets.all(8),
child: OverflowBar(
spacing: 8,
overflowAlignment: OverflowBarAlignment.end,
children: widget.persistentFooterButtons!,
child: Align(
alignment: widget.persistentFooterAlignment,
child: OverflowBar(
spacing: 8,
overflowAlignment: OverflowBarAlignment.end,
children: widget.persistentFooterButtons!,
),
),
),
),

View file

@ -738,11 +738,10 @@ class _SnackBarState extends State<SnackBar> {
Row(
children: <Widget>[
Expanded(
child: Container(
child: Padding(
padding: widget.padding == null
? const EdgeInsets.symmetric(
vertical: _singleLineVerticalPadding)
: null,
? const EdgeInsets.symmetric(vertical: _singleLineVerticalPadding)
: EdgeInsets.zero,
child: DefaultTextStyle(
style: contentTextStyle!,
child: widget.content,
@ -793,10 +792,9 @@ class _SnackBarState extends State<SnackBar> {
if (isFloatingSnackBar) {
// If width is provided, do not include horizontal margins.
if (width != null) {
snackBar = Container(
margin: EdgeInsets.only(top: margin.top, bottom: margin.bottom),
width: width,
child: snackBar,
snackBar = Padding(
padding: EdgeInsets.only(top: margin.top, bottom: margin.bottom),
child: SizedBox(width: width, child: snackBar),
);
} else {
snackBar = Padding(

View file

@ -457,10 +457,12 @@ class _StepperState extends State<Stepper> with TickerProviderStateMixin {
}
Widget _buildLine(bool visible, bool isActive) {
return Container(
width: visible ? widget.connectorThickness ?? 1.0 : 0.0,
height: 16.0,
return ColoredBox(
color: _connectorColor(isActive),
child: SizedBox(
width: visible ? widget.connectorThickness ?? 1.0 : 0.0,
height: 16.0,
),
);
}
@ -500,22 +502,24 @@ class _StepperState extends State<Stepper> with TickerProviderStateMixin {
}
Widget _buildCircle(int index, bool oldState) {
return Container(
margin:_stepIconMargin ?? const EdgeInsets.symmetric(vertical: 8.0),
width: _stepIconWidth ?? _kStepSize,
height: _stepIconHeight ?? _kStepSize,
child: AnimatedContainer(
curve: Curves.fastOutSlowIn,
duration: kThemeAnimationDuration,
decoration: BoxDecoration(
color: _stepStyle(index)?.color ?? _circleColor(index),
shape: BoxShape.circle,
border: _stepStyle(index)?.border,
boxShadow: _stepStyle(index)?.boxShadow != null ? <BoxShadow>[_stepStyle(index)!.boxShadow!] : null,
gradient: _stepStyle(index)?.gradient,
),
child: Center(
child: _buildCircleChild(index, oldState && widget.steps[index].state == StepState.error),
return Padding(
padding: _stepIconMargin ?? const EdgeInsets.symmetric(vertical: 8.0),
child: SizedBox(
width: _stepIconWidth ?? _kStepSize,
height: _stepIconHeight ?? _kStepSize,
child: AnimatedContainer(
curve: Curves.fastOutSlowIn,
duration: kThemeAnimationDuration,
decoration: BoxDecoration(
color: _stepStyle(index)?.color ?? _circleColor(index),
shape: BoxShape.circle,
border: _stepStyle(index)?.border,
boxShadow: _stepStyle(index)?.boxShadow != null ? <BoxShadow>[_stepStyle(index)!.boxShadow!] : null,
gradient: _stepStyle(index)?.gradient,
),
child: Center(
child: _buildCircleChild(index, oldState && widget.steps[index].state == StepState.error),
),
),
),
);
@ -525,21 +529,23 @@ class _StepperState extends State<Stepper> with TickerProviderStateMixin {
Color? color = _stepStyle(index)?.errorColor;
color ??= _isDark() ? _kErrorDark : _kErrorLight;
return Container(
margin: _stepIconMargin ?? const EdgeInsets.symmetric(vertical: 8.0),
width: _stepIconWidth ?? _kStepSize,
height: _stepIconHeight ?? _kStepSize,
child: Center(
child: SizedBox(
width: _stepIconWidth ?? _kStepSize,
height: _stepIconHeight != null ? _stepIconHeight! * _kTriangleSqrt : _kTriangleHeight,
child: CustomPaint(
painter: _TrianglePainter(
color: color,
),
child: Align(
alignment: const Alignment(0.0, 0.8), // 0.8 looks better than the geometrical 0.33.
child: _buildCircleChild(index, oldState && widget.steps[index].state != StepState.error),
return Padding(
padding: _stepIconMargin ?? const EdgeInsets.symmetric(vertical: 8.0),
child: SizedBox(
width: _stepIconWidth ?? _kStepSize,
height: _stepIconHeight ?? _kStepSize,
child: Center(
child: SizedBox(
width: _stepIconWidth ?? _kStepSize,
height: _stepIconHeight != null ? _stepIconHeight! * _kTriangleSqrt : _kTriangleHeight,
child: CustomPaint(
painter: _TrianglePainter(
color: color,
),
child: Align(
alignment: const Alignment(0.0, 0.8), // 0.8 looks better than the geometrical 0.33.
child: _buildCircleChild(index, oldState && widget.steps[index].state != StepState.error),
),
),
),
),
@ -592,10 +598,10 @@ class _StepperState extends State<Stepper> with TickerProviderStateMixin {
const OutlinedBorder buttonShape = RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(2)));
const EdgeInsets buttonPadding = EdgeInsets.symmetric(horizontal: 16.0);
return Container(
margin: const EdgeInsets.only(top: 16.0),
child: ConstrainedBox(
constraints: const BoxConstraints.tightFor(height: 48.0),
return Padding(
padding: const EdgeInsets.only(top: 16.0),
child: SizedBox(
height: 48.0,
child: Row(
// The Material spec no longer includes a Stepper widget. The continue
// and cancel button styles have been configured to match the original
@ -619,8 +625,8 @@ class _StepperState extends State<Stepper> with TickerProviderStateMixin {
: localizations.continueButtonLabel.toUpperCase()
),
),
Container(
margin: const EdgeInsetsDirectional.only(start: 8.0),
Padding(
padding: const EdgeInsetsDirectional.only(start: 8.0),
child: TextButton(
onPressed: widget.onStepCancel,
style: TextButton.styleFrom(
@ -713,8 +719,8 @@ class _StepperState extends State<Stepper> with TickerProviderStateMixin {
child: widget.steps[index].title,
),
if (widget.steps[index].subtitle != null)
Container(
margin: const EdgeInsets.only(top: 2.0),
Padding(
padding: const EdgeInsets.only(top: 2.0),
child: AnimatedDefaultTextStyle(
style: _subtitleStyle(index),
duration: kThemeAnimationDuration,
@ -739,8 +745,8 @@ class _StepperState extends State<Stepper> with TickerProviderStateMixin {
Widget _buildVerticalHeader(int index) {
final bool isActive = widget.steps[index].isActive;
return Container(
margin: const EdgeInsets.symmetric(horizontal: 24.0),
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 24.0),
child: Row(
children: <Widget>[
Column(
@ -753,8 +759,8 @@ class _StepperState extends State<Stepper> with TickerProviderStateMixin {
],
),
Expanded(
child: Container(
margin: const EdgeInsetsDirectional.only(start: 12.0),
child: Padding(
padding: const EdgeInsetsDirectional.only(start: 12.0),
child: _buildHeaderText(index),
),
),
@ -786,17 +792,15 @@ class _StepperState extends State<Stepper> with TickerProviderStateMixin {
child: Center(
child: SizedBox(
width: !_isLast(index) ? (widget.connectorThickness ?? 1.0) : 0.0,
child: Container(
color: _connectorColor(widget.steps[index].isActive),
),
child: ColoredBox(color: _connectorColor(widget.steps[index].isActive)),
),
),
),
),
AnimatedCrossFade(
firstChild: Container(height: 0.0),
secondChild: Container(
margin: EdgeInsetsDirectional.only(
firstChild: const SizedBox(height: 0),
secondChild: Padding(
padding: EdgeInsetsDirectional.only(
// Adjust [controlsBuilder] padding so that the content is
// centered vertically.
start: 60.0 + (marginLeft ?? 0.0),
@ -876,8 +880,8 @@ class _StepperState extends State<Stepper> with TickerProviderStateMixin {
],
),
),
Container(
margin: _stepIconMargin ?? const EdgeInsetsDirectional.only(start: 12.0),
Padding(
padding: _stepIconMargin ?? const EdgeInsetsDirectional.only(start: 12.0),
child: _buildHeaderText(i),
),
],
@ -885,11 +889,15 @@ class _StepperState extends State<Stepper> with TickerProviderStateMixin {
),
if (!_isLast(i))
Expanded(
child: Container(
child: Padding(
key: Key('line$i'),
margin: _stepIconMargin ?? const EdgeInsets.symmetric(horizontal: 8.0),
height: widget.steps[i].stepStyle?.connectorThickness ?? widget.connectorThickness ?? 1.0,
color: widget.steps[i].stepStyle?.connectorColor ?? _connectorColor(widget.steps[i].isActive),
padding: _stepIconMargin ?? const EdgeInsets.symmetric(horizontal: 8.0),
child: SizedBox(
height: widget.steps[i].stepStyle?.connectorThickness ?? widget.connectorThickness ?? 1.0,
child: ColoredBox(
color: widget.steps[i].stepStyle?.connectorColor ?? _connectorColor(widget.steps[i].isActive),
),
),
),
),
],
@ -913,11 +921,11 @@ class _StepperState extends State<Stepper> with TickerProviderStateMixin {
children: <Widget>[
Material(
elevation: widget.elevation ?? 2,
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 24.0),
height: _stepIconHeight != null ? _stepIconHeight! * _heightFactor : null,
child: Row(
children: children,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 24.0),
child: SizedBox(
height: _stepIconHeight != null ? _stepIconHeight! * _heightFactor : null,
child: Row(children: children),
),
),
),

View file

@ -186,8 +186,8 @@ class Tab extends StatelessWidget implements PreferredSizeWidget {
label = Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
margin: effectiveIconMargin,
Padding(
padding: effectiveIconMargin,
child: icon,
),
_buildLabelText(),
@ -1693,8 +1693,9 @@ class _TabBarState extends State<TabBar> {
final MaterialLocalizations localizations = MaterialLocalizations.of(context);
if (_controller!.length == 0) {
return Container(
height: _kTabHeight + widget.indicatorWeight,
return LimitedBox(
maxWidth: 0.0,
child: SizedBox(width: double.infinity, height: _kTabHeight + widget.indicatorWeight),
);
}

View file

@ -2422,27 +2422,29 @@ class _TimePickerDialogState extends State<TimePickerDialog> with RestorationMix
: MaterialLocalizations.of(context).dialModeButtonLabel,
),
Expanded(
child: Container(
alignment: AlignmentDirectional.centerEnd,
child: ConstrainedBox(
constraints: const BoxConstraints(minHeight: 36),
child: OverflowBar(
spacing: 8,
overflowAlignment: OverflowBarAlignment.end,
children: <Widget>[
TextButton(
style: pickerTheme.cancelButtonStyle ?? defaultTheme.cancelButtonStyle,
onPressed: _handleCancel,
child: Text(widget.cancelText ??
(theme.useMaterial3
? localizations.cancelButtonLabel
: localizations.cancelButtonLabel.toUpperCase())),
),
TextButton(
style: pickerTheme.confirmButtonStyle ?? defaultTheme.confirmButtonStyle,
onPressed: _handleOk,
child: Text(widget.confirmText ?? localizations.okButtonLabel),
),
],
child: Align(
alignment: AlignmentDirectional.centerEnd,
child: OverflowBar(
spacing: 8,
overflowAlignment: OverflowBarAlignment.end,
children: <Widget>[
TextButton(
style: pickerTheme.cancelButtonStyle ?? defaultTheme.cancelButtonStyle,
onPressed: _handleCancel,
child: Text(widget.cancelText ??
(theme.useMaterial3
? localizations.cancelButtonLabel
: localizations.cancelButtonLabel.toUpperCase())),
),
TextButton(
style: pickerTheme.confirmButtonStyle ?? defaultTheme.confirmButtonStyle,
onPressed: _handleOk,
child: Text(widget.confirmText ?? localizations.okButtonLabel),
),
],
),
),
),
),

View file

@ -11,7 +11,6 @@ import 'package:flutter/services.dart';
import 'actions.dart';
import 'basic.dart';
import 'container.dart';
import 'editable_text.dart';
import 'focus_manager.dart';
import 'framework.dart';
@ -509,7 +508,7 @@ class _RawAutocompleteState<T extends Object> extends State<RawAutocomplete<T>>
controller: _optionsViewController,
overlayChildBuilder: _buildOptionsView,
child: TextFieldTapRegion(
child: Container(
child: SizedBox(
key: _fieldKey,
child: Shortcuts(
shortcuts: _shortcuts,

View file

@ -19,7 +19,6 @@ import 'package:flutter/services.dart';
import 'basic.dart';
import 'binding.dart';
import 'constants.dart';
import 'container.dart';
import 'context_menu_controller.dart';
import 'debug.dart';
import 'editable_text.dart';
@ -1911,45 +1910,47 @@ class _SelectionHandleOverlayState extends State<_SelectionHandleOverlay> with S
showWhenUnlinked: false,
child: FadeTransition(
opacity: _opacity,
child: Container(
alignment: Alignment.topLeft,
child: SizedBox(
width: interactiveRect.width,
height: interactiveRect.height,
child: RawGestureDetector(
behavior: HitTestBehavior.translucent,
gestures: <Type, GestureRecognizerFactory>{
PanGestureRecognizer: GestureRecognizerFactoryWithHandlers<PanGestureRecognizer>(
() => PanGestureRecognizer(
debugOwner: this,
// Mouse events select the text and do not drag the cursor.
supportedDevices: <PointerDeviceKind>{
PointerDeviceKind.touch,
PointerDeviceKind.stylus,
PointerDeviceKind.unknown,
child: Align(
alignment: Alignment.topLeft,
child: RawGestureDetector(
behavior: HitTestBehavior.translucent,
gestures: <Type, GestureRecognizerFactory>{
PanGestureRecognizer: GestureRecognizerFactoryWithHandlers<PanGestureRecognizer>(
() => PanGestureRecognizer(
debugOwner: this,
// Mouse events select the text and do not drag the cursor.
supportedDevices: <PointerDeviceKind>{
PointerDeviceKind.touch,
PointerDeviceKind.stylus,
PointerDeviceKind.unknown,
},
),
(PanGestureRecognizer instance) {
instance
..dragStartBehavior = widget.dragStartBehavior
..gestureSettings = eagerlyAcceptDragWhenCollapsed ? const DeviceGestureSettings(touchSlop: 1.0) : null
..onStart = widget.onSelectionHandleDragStart
..onUpdate = widget.onSelectionHandleDragUpdate
..onEnd = widget.onSelectionHandleDragEnd;
},
),
(PanGestureRecognizer instance) {
instance
..dragStartBehavior = widget.dragStartBehavior
..gestureSettings = eagerlyAcceptDragWhenCollapsed ? const DeviceGestureSettings(touchSlop: 1.0) : null
..onStart = widget.onSelectionHandleDragStart
..onUpdate = widget.onSelectionHandleDragUpdate
..onEnd = widget.onSelectionHandleDragEnd;
},
),
},
child: Padding(
padding: EdgeInsets.only(
left: padding.left,
top: padding.top,
right: padding.right,
bottom: padding.bottom,
),
child: widget.selectionControls.buildHandle(
context,
widget.type,
widget.preferredLineHeight,
widget.onSelectionHandleTapped,
},
child: Padding(
padding: EdgeInsets.only(
left: padding.left,
top: padding.top,
right: padding.right,
bottom: padding.bottom,
),
child: widget.selectionControls.buildHandle(
context,
widget.type,
widget.preferredLineHeight,
widget.onSelectionHandleTapped,
),
),
),
),

View file

@ -112,9 +112,11 @@ void main() {
scaffoldKey.currentState!.openDrawer();
await tester.pump(const Duration(seconds: 1)); // animation done
final Container destinationColor = tester.firstWidget<Container>(
final ColoredBox destinationColor = tester.firstWidget<ColoredBox>(
find.descendant(
of: find.byType(NavigationDrawerDestination), matching: find.byType(Container)),
of: find.byType(NavigationDrawerDestination),
matching: find.byType(ColoredBox),
),
);
expect(destinationColor.color, equals(color));

View file

@ -1667,8 +1667,17 @@ void main() {
await tester.tap(find.byKey(popupMenuButtonKey));
await tester.pumpAndSettle();
expect(tester.widget<Container>(find.widgetWithText(Container, 'Item 0')).padding, const EdgeInsets.symmetric(horizontal: 12.0));
expect(tester.widget<Container>(find.widgetWithText(Container, 'Item 1')).padding, const EdgeInsets.symmetric(horizontal: 12.0));
EdgeInsetsGeometry paddingFor(String text) {
return tester.widget<Padding>(
find.ancestor(
of: find.widgetWithText(Align, 'Item 0'),
matching: find.byKey(const Key('menu item padding')),
),
).padding;
}
expect(paddingFor('Item 0'), const EdgeInsets.symmetric(horizontal: 12.0));
expect(paddingFor('Item 1'), const EdgeInsets.symmetric(horizontal: 12.0));
});
testWidgets('PopupMenu default padding', (WidgetTester tester) async {
@ -1743,8 +1752,17 @@ void main() {
await tester.tap(find.byKey(popupMenuButtonKey));
await tester.pumpAndSettle();
expect(tester.widget<Container>(find.widgetWithText(Container, 'Item 0')).padding, const EdgeInsets.symmetric(horizontal: 16.0));
expect(tester.widget<Container>(find.widgetWithText(Container, 'Item 1')).padding, const EdgeInsets.symmetric(horizontal: 16.0));
EdgeInsetsGeometry paddingFor(String text) {
return tester.widget<Padding>(
find.ancestor(
of: find.widgetWithText(Align, 'Item 0'),
matching: find.byKey(const Key('menu item padding')),
),
).padding;
}
expect(paddingFor('Item 0'), const EdgeInsets.symmetric(horizontal: 16.0));
expect(paddingFor('Item 1'), const EdgeInsets.symmetric(horizontal: 16.0));
});
testWidgets('Material2 - PopupMenuItem default padding', (WidgetTester tester) async {
@ -1842,10 +1860,19 @@ void main() {
expect(tester.getSize(find.widgetWithText(menuItemType, 'Item 2')).height, 56); // Padding (20.0 + 20.0) + Height of text (16) = 56
expect(tester.getSize(find.widgetWithText(menuItemType, 'Item 3')).height, 100); // Height value of 100, since child (16) + padding (40) < 100
expect(tester.widget<Container>(find.widgetWithText(Container, 'Item 0')).padding, EdgeInsets.zero);
expect(tester.widget<Container>(find.widgetWithText(Container, 'Item 1')).padding, EdgeInsets.zero);
expect(tester.widget<Container>(find.widgetWithText(Container, 'Item 2')).padding, const EdgeInsets.all(20));
expect(tester.widget<Container>(find.widgetWithText(Container, 'Item 3')).padding, const EdgeInsets.all(20));
EdgeInsetsGeometry paddingFor(String text) {
final ConstrainedBox widget = tester.widget<ConstrainedBox>(
find.ancestor(of: find.text(text), matching: find.byWidgetPredicate(
(Widget widget) => widget is ConstrainedBox && widget.child is Padding,
)),
);
return (widget.child! as Padding).padding;
}
expect(paddingFor('Item 0'), EdgeInsets.zero);
expect(paddingFor('Item 1'), EdgeInsets.zero);
expect(paddingFor('Item 2'), const EdgeInsets.all(20));
expect(paddingFor('Item 3'), const EdgeInsets.all(20));
});
testWidgets('CheckedPopupMenuItem child height is a minimum, child is vertically centered', (WidgetTester tester) async {
@ -1992,10 +2019,19 @@ void main() {
expect(tester.getSize(find.widgetWithText(menuItemType, 'Item 2')).height, 96); // Padding (20.0 + 20.0) + Height of ListTile (56) = 96
expect(tester.getSize(find.widgetWithText(menuItemType, 'Item 3')).height, 100); // Height value of 100, since child (56) + padding (40) < 100
expect(tester.widget<Container>(find.widgetWithText(Container, 'Item 0')).padding, EdgeInsets.zero);
expect(tester.widget<Container>(find.widgetWithText(Container, 'Item 1')).padding, EdgeInsets.zero);
expect(tester.widget<Container>(find.widgetWithText(Container, 'Item 2')).padding, const EdgeInsets.all(20));
expect(tester.widget<Container>(find.widgetWithText(Container, 'Item 3')).padding, const EdgeInsets.all(20));
EdgeInsetsGeometry paddingFor(String text) {
final ConstrainedBox widget = tester.widget<ConstrainedBox>(
find.ancestor(of: find.text(text), matching: find.byWidgetPredicate(
(Widget widget) => widget is ConstrainedBox && widget.child is Padding,
)),
);
return (widget.child! as Padding).padding;
}
expect(paddingFor('Item 0'), EdgeInsets.zero);
expect(paddingFor('Item 1'), EdgeInsets.zero);
expect(paddingFor('Item 2'), const EdgeInsets.all(20));
expect(paddingFor('Item 3'), const EdgeInsets.all(20));
});
testWidgets('Update PopupMenuItem layout while the menu is visible', (WidgetTester tester) async {

View file

@ -1245,24 +1245,24 @@ void main() {
matching: find.byType(Material),
),
);
Container container = tester.widget(
Padding padding = tester.widget(
find.descendant(
of: find.byType(RefreshProgressIndicator),
matching: find.byType(Container),
),
matching: find.byType(Padding),
).first,
);
Padding padding = tester.widget(
Padding innerPadding = tester.widget(
find.descendant(
of: find.descendant(
of: find.byType(RefreshProgressIndicator),
matching: find.byType(Material),
),
matching: find.byType(Padding),
),
).last,
);
expect(material.elevation, 2.0);
expect(container.margin, const EdgeInsets.all(4.0));
expect(padding.padding, const EdgeInsets.all(12.0));
expect(padding.padding, const EdgeInsets.all(4.0));
expect(innerPadding.padding, const EdgeInsets.all(12.0));
// With values provided.
const double testElevation = 1.0;
@ -1281,24 +1281,24 @@ void main() {
matching: find.byType(Material),
),
);
container = tester.widget(
padding = tester.widget(
find.descendant(
of: find.byType(RefreshProgressIndicator),
matching: find.byType(Container),
),
matching: find.byType(Padding),
).first,
);
padding = tester.widget(
innerPadding = tester.widget(
find.descendant(
of: find.descendant(
of: find.byType(RefreshProgressIndicator),
matching: find.byType(Material),
),
matching: find.byType(Padding),
),
).last,
);
expect(material.elevation, testElevation);
expect(container.margin, testIndicatorMargin);
expect(padding.padding, testIndicatorPadding);
expect(padding.padding, testIndicatorMargin);
expect(innerPadding.padding, testIndicatorPadding);
});
}

View file

@ -1520,7 +1520,14 @@ testWidgets('Stepper custom indexed controls test', (WidgetTester tester) async
find.widgetWithText(AnimatedContainer, circleText),
).decoration as BoxDecoration?)?.color;
Color? lineColor(String keyStep) => tester.widget<Container>(find.byKey(Key(keyStep))).color;
Color lineColor(String keyStep) {
return tester.widget<ColoredBox>(
find.descendant(
of: find.byKey(Key(keyStep)),
matching: find.byType(ColoredBox).last,
),
).color;
}
// Step 1
// check if I'm in step 1
@ -1714,10 +1721,13 @@ testWidgets('Stepper custom indexed controls test', (WidgetTester tester) async
)
);
final SizedBox lastConnector = tester.widget<SizedBox>(
find.descendant(of: find.byType(PositionedDirectional),
matching: find.byType(SizedBox).last,
));
final SizedBox lastConnector = tester.widget<Center>(
find.descendant(
of: find.byType(PositionedDirectional),
matching: find.byType(Center).last,
),
).child! as SizedBox;
expect(lastConnector.width, equals(0.0));
});

View file

@ -1443,6 +1443,7 @@ void main() {
home: Scaffold(
body: Center(
child: SizedBox(
key: const Key('outer box'),
height: 50.0,
child: InteractiveViewer.builder(
transformationController: transformationController,
@ -1488,7 +1489,7 @@ void main() {
}
// Drag to pan down past the first child.
final Offset childOffset = tester.getTopLeft(find.byType(SizedBox));
final Offset childOffset = tester.getTopLeft(find.byKey(const Key('outer box')));
const double translationY = 15.0;
final Offset childInterior = Offset(
childOffset.dx,