From 360e42c7afe0767fd5ffff8758791b8d36a4f3ff Mon Sep 17 00:00:00 2001 From: Nate Wilson Date: Wed, 11 Sep 2024 17:59:53 -0600 Subject: [PATCH] Factor out `Container` objects (#153619) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) 🙂)
resolves #147431 --- .../lib/src/material/expansion_panel.dart | 12 +- .../lib/src/material/flexible_space_bar.dart | 14 +- .../lib/src/material/navigation_drawer.dart | 59 ++++---- .../lib/src/material/navigation_rail.dart | 104 +++++++------- .../src/material/page_transitions_theme.dart | 20 +-- .../src/material/paginated_data_table.dart | 11 +- .../flutter/lib/src/material/popup_menu.dart | 15 +- .../lib/src/material/progress_indicator.dart | 55 +++---- .../lib/src/material/refresh_indicator.dart | 65 +++++---- .../flutter/lib/src/material/scaffold.dart | 14 +- .../flutter/lib/src/material/snack_bar.dart | 14 +- .../flutter/lib/src/material/stepper.dart | 134 ++++++++++-------- packages/flutter/lib/src/material/tabs.dart | 9 +- .../flutter/lib/src/material/time_picker.dart | 42 +++--- .../flutter/lib/src/widgets/autocomplete.dart | 3 +- .../lib/src/widgets/text_selection.dart | 73 +++++----- .../test/material/navigation_drawer_test.dart | 6 +- .../test/material/popup_menu_test.dart | 60 ++++++-- .../material/progress_indicator_test.dart | 28 ++-- .../flutter/test/material/stepper_test.dart | 20 ++- .../test/widgets/interactive_viewer_test.dart | 3 +- 21 files changed, 417 insertions(+), 344 deletions(-) diff --git a/packages/flutter/lib/src/material/expansion_panel.dart b/packages/flutter/lib/src/material/expansion_panel.dart index 93f41346a4..eb5ded8a8f 100644 --- a/packages/flutter/lib/src/material/expansion_panel.dart +++ b/packages/flutter/lib/src/material/expansion_panel.dart @@ -389,8 +389,8 @@ class _ExpansionPanelListState extends State { _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 { 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 { ), ), ), - expandIconContainer, + expandIconPadded, ], ); if (child.canTapOnHeader) { @@ -446,7 +446,7 @@ class _ExpansionPanelListState extends State { children: [ 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), diff --git a/packages/flutter/lib/src/material/flexible_space_bar.dart b/packages/flutter/lib/src/material/flexible_space_bar.dart index d188981785..658db0221d 100644 --- a/packages/flutter/lib/src/material/flexible_space_bar.dart +++ b/packages/flutter/lib/src/material/flexible_space_bar.dart @@ -273,9 +273,7 @@ class _FlexibleSpaceBarState extends State { sigmaX: blurAmount, sigmaY: blurAmount, ), - child: Container( - color: Colors.transparent, - ), + child: const ColoredBox(color: Colors.transparent), ), )); } @@ -331,7 +329,7 @@ class _FlexibleSpaceBarState extends State { 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 { 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, + ), ); }, ), diff --git a/packages/flutter/lib/src/material/navigation_drawer.dart b/packages/flutter/lib/src/material/navigation_drawer.dart index ea6949926a..3cb24aecf0 100644 --- a/packages/flutter/lib/src/material/navigation_drawer.dart +++ b/packages/flutter/lib/src/material/navigation_drawer.dart @@ -350,42 +350,47 @@ class _NavigationDestinationBuilder extends StatelessWidget { final NavigationDrawerThemeData navigationDrawerTheme = NavigationDrawerTheme.of(context); final NavigationDrawerThemeData defaults = _NavigationDrawerDefaultsM3(context); - final Row destinationBody = Row( - children: [ - 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: [ + 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: [ + 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: [ - 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; } } diff --git a/packages/flutter/lib/src/material/navigation_rail.dart b/packages/flutter/lib/src/material/navigation_rail.dart index 4971311146..b3e666e39e 100644 --- a/packages/flutter/lib/src/material/navigation_rail.dart +++ b/packages/flutter/lib/src/material/navigation_rail.dart @@ -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: [ - 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: [ + 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: [ - 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: [ + topSpacing, + _AddIndicator( + addIndicator: widget.useIndicator, + indicatorColor: widget.indicatorColor, + indicatorShape: widget.indicatorShape, + isCircular: false, + indicatorAnimation: widget.destinationAnimation, + child: themedIcon, + ), + labelSpacing, + styledLabel, + bottomSpacing, + ], + ), ), ); } diff --git a/packages/flutter/lib/src/material/page_transitions_theme.dart b/packages/flutter/lib/src/material/page_transitions_theme.dart index e72ef919ea..70aeb06054 100644 --- a/packages/flutter/lib/src/material/page_transitions_theme.dart +++ b/packages/flutter/lib/src/material/page_transitions_theme.dart @@ -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, + ), ), ), ), diff --git a/packages/flutter/lib/src/material/paginated_data_table.dart b/packages/flutter/lib/src/material/paginated_data_table.dart index 27587a15fa..971aee5dd5 100644 --- a/packages/flutter/lib/src/material/paginated_data_table.dart +++ b/packages/flutter/lib/src/material/paginated_data_table.dart @@ -500,7 +500,8 @@ class PaginatedDataTableState extends State { }) .toList(); footerWidgets.addAll([ - 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 { ]); } footerWidgets.addAll([ - Container(width: 32.0), + const SizedBox(width: 32.0), Text( localizations.pageRowsInfoTitle( _firstRowIndex + 1, @@ -528,7 +529,7 @@ class PaginatedDataTableState extends State { _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 { 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 { ? null : _handleLast, ), - Container(width: 14.0), + const SizedBox(width: 14.0), ]); // CARD diff --git a/packages/flutter/lib/src/material/popup_menu.dart b/packages/flutter/lib/src/material/popup_menu.dart index e16689cfc0..762ce503a9 100644 --- a/packages/flutter/lib/src/material/popup_menu.dart +++ b/packages/flutter/lib/src/material/popup_menu.dart @@ -375,15 +375,22 @@ class PopupMenuItemState> extends State { 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(), + ), + ), ), ); diff --git a/packages/flutter/lib/src/material/progress_indicator.dart b/packages/flutter/lib/src/material/progress_indicator.dart index ee1e7c3c5e..d380c2c0d8 100644 --- a/packages/flutter/lib/src/material/progress_indicator.dart +++ b/packages/flutter/lib/src/material/progress_indicator.dart @@ -729,7 +729,7 @@ class _CircularProgressIndicatorState extends State 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, + ), ), ), ), diff --git a/packages/flutter/lib/src/material/refresh_indicator.dart b/packages/flutter/lib/src/material/refresh_indicator.dart index 11d3268069..b26427d41c 100644 --- a/packages/flutter/lib/src/material/refresh_indicator.dart +++ b/packages/flutter/lib/src/material/refresh_indicator.dart @@ -671,40 +671,38 @@ class RefreshIndicatorState extends State 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 case TargetPlatform.macOS: return cupertinoIndicator; } - } - case _IndicatorType.noSpinner: - return Container(); - } - }, + + case _IndicatorType.noSpinner: + return Container(); + } + }, + ), ), ), ), diff --git a/packages/flutter/lib/src/material/scaffold.dart b/packages/flutter/lib/src/material/scaffold.dart index 5249d761c1..3155b377e3 100644 --- a/packages/flutter/lib/src/material/scaffold.dart +++ b/packages/flutter/lib/src/material/scaffold.dart @@ -2993,13 +2993,15 @@ class ScaffoldState extends State 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!, + ), ), ), ), diff --git a/packages/flutter/lib/src/material/snack_bar.dart b/packages/flutter/lib/src/material/snack_bar.dart index e7ff60b76d..e0bb9845f4 100644 --- a/packages/flutter/lib/src/material/snack_bar.dart +++ b/packages/flutter/lib/src/material/snack_bar.dart @@ -738,11 +738,10 @@ class _SnackBarState extends State { Row( children: [ 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 { 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( diff --git a/packages/flutter/lib/src/material/stepper.dart b/packages/flutter/lib/src/material/stepper.dart index 17eec2e018..3a2978a483 100644 --- a/packages/flutter/lib/src/material/stepper.dart +++ b/packages/flutter/lib/src/material/stepper.dart @@ -457,10 +457,12 @@ class _StepperState extends State 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 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 ? [_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 ? [_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 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 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 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 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 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: [ Column( @@ -753,8 +759,8 @@ class _StepperState extends State 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 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 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 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 with TickerProviderStateMixin { children: [ 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), ), ), ), diff --git a/packages/flutter/lib/src/material/tabs.dart b/packages/flutter/lib/src/material/tabs.dart index 06d8e5cefe..bc6687466a 100644 --- a/packages/flutter/lib/src/material/tabs.dart +++ b/packages/flutter/lib/src/material/tabs.dart @@ -186,8 +186,8 @@ class Tab extends StatelessWidget implements PreferredSizeWidget { label = Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - Container( - margin: effectiveIconMargin, + Padding( + padding: effectiveIconMargin, child: icon, ), _buildLabelText(), @@ -1693,8 +1693,9 @@ class _TabBarState extends State { 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), ); } diff --git a/packages/flutter/lib/src/material/time_picker.dart b/packages/flutter/lib/src/material/time_picker.dart index e795bbdeb9..db391f0538 100644 --- a/packages/flutter/lib/src/material/time_picker.dart +++ b/packages/flutter/lib/src/material/time_picker.dart @@ -2422,27 +2422,29 @@ class _TimePickerDialogState extends State 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: [ - 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: [ + 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), + ), + ], + ), ), ), ), diff --git a/packages/flutter/lib/src/widgets/autocomplete.dart b/packages/flutter/lib/src/widgets/autocomplete.dart index bda02c2a7f..b79bf9e44e 100644 --- a/packages/flutter/lib/src/widgets/autocomplete.dart +++ b/packages/flutter/lib/src/widgets/autocomplete.dart @@ -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 extends State> controller: _optionsViewController, overlayChildBuilder: _buildOptionsView, child: TextFieldTapRegion( - child: Container( + child: SizedBox( key: _fieldKey, child: Shortcuts( shortcuts: _shortcuts, diff --git a/packages/flutter/lib/src/widgets/text_selection.dart b/packages/flutter/lib/src/widgets/text_selection.dart index 9718a0cb42..fd0e854182 100644 --- a/packages/flutter/lib/src/widgets/text_selection.dart +++ b/packages/flutter/lib/src/widgets/text_selection.dart @@ -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: { - PanGestureRecognizer: GestureRecognizerFactoryWithHandlers( - () => PanGestureRecognizer( - debugOwner: this, - // Mouse events select the text and do not drag the cursor. - supportedDevices: { - PointerDeviceKind.touch, - PointerDeviceKind.stylus, - PointerDeviceKind.unknown, + child: Align( + alignment: Alignment.topLeft, + child: RawGestureDetector( + behavior: HitTestBehavior.translucent, + gestures: { + PanGestureRecognizer: GestureRecognizerFactoryWithHandlers( + () => PanGestureRecognizer( + debugOwner: this, + // Mouse events select the text and do not drag the cursor. + supportedDevices: { + 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, + ), ), ), ), diff --git a/packages/flutter/test/material/navigation_drawer_test.dart b/packages/flutter/test/material/navigation_drawer_test.dart index d38a064469..494d74dd20 100644 --- a/packages/flutter/test/material/navigation_drawer_test.dart +++ b/packages/flutter/test/material/navigation_drawer_test.dart @@ -112,9 +112,11 @@ void main() { scaffoldKey.currentState!.openDrawer(); await tester.pump(const Duration(seconds: 1)); // animation done - final Container destinationColor = tester.firstWidget( + final ColoredBox destinationColor = tester.firstWidget( find.descendant( - of: find.byType(NavigationDrawerDestination), matching: find.byType(Container)), + of: find.byType(NavigationDrawerDestination), + matching: find.byType(ColoredBox), + ), ); expect(destinationColor.color, equals(color)); diff --git a/packages/flutter/test/material/popup_menu_test.dart b/packages/flutter/test/material/popup_menu_test.dart index cedfa6ec46..6d4ddde2f2 100644 --- a/packages/flutter/test/material/popup_menu_test.dart +++ b/packages/flutter/test/material/popup_menu_test.dart @@ -1667,8 +1667,17 @@ void main() { await tester.tap(find.byKey(popupMenuButtonKey)); await tester.pumpAndSettle(); - expect(tester.widget(find.widgetWithText(Container, 'Item 0')).padding, const EdgeInsets.symmetric(horizontal: 12.0)); - expect(tester.widget(find.widgetWithText(Container, 'Item 1')).padding, const EdgeInsets.symmetric(horizontal: 12.0)); + EdgeInsetsGeometry paddingFor(String text) { + return tester.widget( + 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(find.widgetWithText(Container, 'Item 0')).padding, const EdgeInsets.symmetric(horizontal: 16.0)); - expect(tester.widget(find.widgetWithText(Container, 'Item 1')).padding, const EdgeInsets.symmetric(horizontal: 16.0)); + EdgeInsetsGeometry paddingFor(String text) { + return tester.widget( + 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(find.widgetWithText(Container, 'Item 0')).padding, EdgeInsets.zero); - expect(tester.widget(find.widgetWithText(Container, 'Item 1')).padding, EdgeInsets.zero); - expect(tester.widget(find.widgetWithText(Container, 'Item 2')).padding, const EdgeInsets.all(20)); - expect(tester.widget(find.widgetWithText(Container, 'Item 3')).padding, const EdgeInsets.all(20)); + EdgeInsetsGeometry paddingFor(String text) { + final ConstrainedBox widget = tester.widget( + 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(find.widgetWithText(Container, 'Item 0')).padding, EdgeInsets.zero); - expect(tester.widget(find.widgetWithText(Container, 'Item 1')).padding, EdgeInsets.zero); - expect(tester.widget(find.widgetWithText(Container, 'Item 2')).padding, const EdgeInsets.all(20)); - expect(tester.widget(find.widgetWithText(Container, 'Item 3')).padding, const EdgeInsets.all(20)); + EdgeInsetsGeometry paddingFor(String text) { + final ConstrainedBox widget = tester.widget( + 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 { diff --git a/packages/flutter/test/material/progress_indicator_test.dart b/packages/flutter/test/material/progress_indicator_test.dart index 98bcad2d0f..735faff2a2 100644 --- a/packages/flutter/test/material/progress_indicator_test.dart +++ b/packages/flutter/test/material/progress_indicator_test.dart @@ -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); }); } diff --git a/packages/flutter/test/material/stepper_test.dart b/packages/flutter/test/material/stepper_test.dart index 2ce124ff5f..ee2052413a 100644 --- a/packages/flutter/test/material/stepper_test.dart +++ b/packages/flutter/test/material/stepper_test.dart @@ -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(find.byKey(Key(keyStep))).color; + Color lineColor(String keyStep) { + return tester.widget( + 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( - find.descendant(of: find.byType(PositionedDirectional), - matching: find.byType(SizedBox).last, - )); + final SizedBox lastConnector = tester.widget
( + find.descendant( + of: find.byType(PositionedDirectional), + matching: find.byType(Center).last, + ), + ).child! as SizedBox; + expect(lastConnector.width, equals(0.0)); }); diff --git a/packages/flutter/test/widgets/interactive_viewer_test.dart b/packages/flutter/test/widgets/interactive_viewer_test.dart index f13a125df6..00ce4f3187 100644 --- a/packages/flutter/test/widgets/interactive_viewer_test.dart +++ b/packages/flutter/test/widgets/interactive_viewer_test.dart @@ -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,