diff --git a/packages/flutter/lib/src/material/chip.dart b/packages/flutter/lib/src/material/chip.dart index 95a9338b4f..c3bfbc3f82 100644 --- a/packages/flutter/lib/src/material/chip.dart +++ b/packages/flutter/lib/src/material/chip.dart @@ -1728,9 +1728,6 @@ class _RenderChip extends RenderBox with SlottedContainerRenderObjectMixin<_Chip bool get isDrawingCheckmark => theme.showCheckmark && !checkmarkAnimation.isDismissed; bool get deleteIconShowing => !deleteDrawerAnimation.isDismissed; - @override - bool get sizedByParent => false; - static Rect _boxRect(RenderBox box) => _boxParentData(box).offset & box.size; static BoxParentData _boxParentData(RenderBox box) => box.parentData! as BoxParentData; @@ -1775,29 +1772,22 @@ class _RenderChip extends RenderBox with SlottedContainerRenderObjectMixin<_Chip return (BaselineOffset(label.getDistanceToActualBaseline(baseline)) + _boxParentData(label).offset.dy).offset; } - Size _layoutLabel(BoxConstraints contentConstraints, double iconSizes, Size size, Size rawSize, [ChildLayouter layoutChild = ChildLayoutHelper.layoutChild]) { + BoxConstraints _labelConstraintsFrom(BoxConstraints contentConstraints, double iconWidth, double contentSize, Size rawLabelSize) { // Now that we know the label height and the width of the icons, we can // determine how much to shrink the width constraints for the "real" layout. - final double maxLabelWidth = contentConstraints.maxWidth.isFinite - ? math.max( - 0.0, - contentConstraints.maxWidth - iconSizes - theme.labelPadding.horizontal - theme.padding.horizontal, - ) - : size.width; - final BoxConstraints labelConstraints = BoxConstraints( - minHeight: rawSize.height, - maxHeight: size.height, - maxWidth: maxLabelWidth, + final double freeSpace = contentConstraints.maxWidth - iconWidth - theme.labelPadding.horizontal - theme.padding.horizontal; + final double maxLabelWidth = math.max(0.0, freeSpace); + return BoxConstraints( + minHeight: rawLabelSize.height, + maxHeight: contentSize, + maxWidth: maxLabelWidth.isFinite ? maxLabelWidth : rawLabelSize.width, ); - final Size updatedSize = layoutChild(label, labelConstraints); - return theme.labelPadding.inflateSize(updatedSize); } Size _layoutAvatar(double contentSize, [ChildLayouter layoutChild = ChildLayoutHelper.layoutChild]) { - final double requestedSize = math.max(0.0, contentSize); final BoxConstraints avatarConstraints = avatarBoxConstraints ?? BoxConstraints.tightFor( - width: requestedSize, - height: requestedSize, + width: contentSize, + height: contentSize, ); final Size avatarBoxSize = layoutChild(avatar, avatarConstraints); if (!theme.showCheckmark && !theme.showAvatar) { @@ -1808,10 +1798,9 @@ class _RenderChip extends RenderBox with SlottedContainerRenderObjectMixin<_Chip } Size _layoutDeleteIcon(double contentSize, [ChildLayouter layoutChild = ChildLayoutHelper.layoutChild]) { - final double requestedSize = math.max(0.0, contentSize); final BoxConstraints deleteIconConstraints = deleteIconBoxConstraints ?? BoxConstraints.tightFor( - width: requestedSize, - height: requestedSize, + width: contentSize, + height: contentSize, ); final Size boxSize = layoutChild(deleteIcon, deleteIconConstraints); if (!deleteIconShowing) { @@ -1851,25 +1840,36 @@ class _RenderChip extends RenderBox with SlottedContainerRenderObjectMixin<_Chip return _computeSizes(constraints, ChildLayoutHelper.dryLayoutChild).size; } + @override + double? computeDryBaseline(BoxConstraints constraints, TextBaseline baseline) { + final _ChipSizes sizes = _computeSizes(constraints, ChildLayoutHelper.dryLayoutChild); + final BaselineOffset labelBaseline = BaselineOffset(label.getDryBaseline(sizes.labelConstraints, baseline)) + + (sizes.content - sizes.label.height + sizes.densityAdjustment.dy) / 2 + + theme.padding.top + theme.labelPadding.top; + return labelBaseline.offset; + } + _ChipSizes _computeSizes(BoxConstraints constraints, ChildLayouter layoutChild) { final BoxConstraints contentConstraints = constraints.loosen(); // Find out the height of the label within the constraints. - final Offset densityAdjustment = Offset(0.0, theme.visualDensity.baseSizeAdjustment.dy / 2.0); - final Size rawLabelSize = layoutChild(label, contentConstraints); + final Size rawLabelSize = label.getDryLayout(contentConstraints); final double contentSize = math.max( _kChipHeight - theme.padding.vertical + theme.labelPadding.vertical, rawLabelSize.height + theme.labelPadding.vertical, ); + assert(contentSize >= rawLabelSize.height); final Size avatarSize = _layoutAvatar(contentSize, layoutChild); final Size deleteIconSize = _layoutDeleteIcon(contentSize, layoutChild); - final Size labelSize = _layoutLabel( + + final BoxConstraints labelConstraints = _labelConstraintsFrom( contentConstraints, avatarSize.width + deleteIconSize.width, - Size(rawLabelSize.width, contentSize), + contentSize, rawLabelSize, - layoutChild, ); + final Size labelSize = theme.labelPadding.inflateSize(layoutChild(label, labelConstraints)); + final Offset densityAdjustment = Offset(0.0, theme.visualDensity.baseSizeAdjustment.dy / 2.0); // This is the overall size of the content: it doesn't include // theme.padding, that is added in at the end. final Size overallSize = Size( @@ -1887,6 +1887,7 @@ class _RenderChip extends RenderBox with SlottedContainerRenderObjectMixin<_Chip content: contentSize, densityAdjustment: densityAdjustment, avatar: avatarSize, + labelConstraints: labelConstraints, label: labelSize, deleteIcon: deleteIconSize, ); @@ -2232,6 +2233,7 @@ class _ChipSizes { required this.overall, required this.content, required this.avatar, + required this.labelConstraints, required this.label, required this.deleteIcon, required this.densityAdjustment, @@ -2240,6 +2242,7 @@ class _ChipSizes { final Size overall; final double content; final Size avatar; + final BoxConstraints labelConstraints; final Size label; final Size deleteIcon; final Offset densityAdjustment; diff --git a/packages/flutter/test/material/chip_test.dart b/packages/flutter/test/material/chip_test.dart index 70fbff590b..8a08a44511 100644 --- a/packages/flutter/test/material/chip_test.dart +++ b/packages/flutter/test/material/chip_test.dart @@ -5781,6 +5781,18 @@ void main() { // Test rendered icon color. expect(getIconStyle(tester, deleteIcon)?.color, deleteIconColor); }); + + testWidgets('Chip label only does layout once', (WidgetTester tester) async { + final RenderLayoutCount renderLayoutCount = RenderLayoutCount(); + final Widget layoutCounter = Center( + key: GlobalKey(), + child: WidgetToRenderBoxAdapter(renderBox: renderLayoutCount), + ); + + await tester.pumpWidget(wrapForChip(child: RawChip(label: layoutCounter))); + + expect(renderLayoutCount.layoutCount, 1); + }); } class _MaterialStateOutlinedBorder extends StadiumBorder implements MaterialStateOutlinedBorder { @@ -5800,3 +5812,16 @@ class _MaterialStateBorderSide extends MaterialStateBorderSide { @override BorderSide? resolve(Set states) => resolver(states); } + +class RenderLayoutCount extends RenderBox { + int layoutCount = 0; + + @override + Size computeDryLayout(covariant BoxConstraints constraints) => constraints.biggest; + + @override + void performLayout() { + layoutCount += 1; + size = constraints.biggest; + } +}