Introduce avatarBoxConstraints & deleteIconBoxConstraints for the chips (#143302)

fixes [Chip widget's avatar padding changing if label text is more than 1 line](https://github.com/flutter/flutter/issues/136892)

### Code sample

<details>
<summary>expand to view the code sample</summary> 

```dart
import 'package:flutter/material.dart';

List<String> strings = [
  'hello good morning',
  'hello good morning hello good morning',
  'hello good morning hello good morning hello good morning'
];

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        body: Center(
          child: Row(
            mainAxisAlignment: MainAxisAlignment.spaceEvenly,
            children: [
              Column(
                mainAxisSize: MainAxisSize.min,
                crossAxisAlignment: CrossAxisAlignment.center,
                children: [
                  const Text(
                      'avatarBoxConstraints: null \ndeleteIconBoxConstraints: null',
                      textAlign: TextAlign.center),
                  for (String string in strings)
                    Padding(
                      padding: const EdgeInsets.all(8.0),
                      child: RawChip(
                        label: Container(
                          width: 150,
                          color: Colors.amber,
                          child: Text(
                            string,
                            maxLines: 3,
                            overflow: TextOverflow.ellipsis,
                          ),
                        ),
                        avatar: const Icon(Icons.settings),
                        onDeleted: () {},
                      ),
                    ),
                ],
              ),
              Column(
                mainAxisSize: MainAxisSize.min,
                crossAxisAlignment: CrossAxisAlignment.center,
                children: [
                  const Text(
                      'avatarBoxConstraints: BoxConstraints.tightForFinite() \ndeleteIconBoxConstraints: BoxConstraints.tightForFinite()',
                      textAlign: TextAlign.center),
                  for (String string in strings)
                    Padding(
                      padding: const EdgeInsets.all(8.0),
                      child: RawChip(
                        avatarBoxConstraints:
                            const BoxConstraints.tightForFinite(),
                        deleteIconBoxConstraints:
                            const BoxConstraints.tightForFinite(),
                        label: Container(
                          width: 150,
                          color: Colors.amber,
                          child: Text(
                            string,
                            maxLines: 3,
                            overflow: TextOverflow.ellipsis,
                          ),
                        ),
                        avatar: const Icon(Icons.settings),
                        onDeleted: () {},
                      ),
                    ),
                ],
              ),
            ],
          ),
        ),
      ),
    );
  }
}

```

</details>

### Preview
![Screenshot 2024-02-12 at 14 58 35](https://github.com/flutter/flutter/assets/48603081/5724bd07-7ac7-4987-b992-fa3ab8488273)

# Example previews
![Screenshot 2024-02-12 at 22 15 14](https://github.com/flutter/flutter/assets/48603081/33af472d-3561-47d4-8d0d-e1628de1e0aa)
![Screenshot 2024-02-12 at 22 15 46](https://github.com/flutter/flutter/assets/48603081/3de78b59-5cb6-4fd8-879b-8e204aacb069)
This commit is contained in:
Taha Tesser 2024-02-13 22:30:53 +02:00 committed by GitHub
parent a8e9f209a1
commit ccf42dde88
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 1079 additions and 13 deletions

View file

@ -0,0 +1,75 @@
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
/// Flutter code sample for [ChipAttributes.avatarBoxConstraints].
void main() => runApp(const AvatarBoxConstraintsApp());
class AvatarBoxConstraintsApp extends StatelessWidget {
const AvatarBoxConstraintsApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: Scaffold(
body: Center(
child: AvatarBoxConstraintsExample(),
),
),
);
}
}
class AvatarBoxConstraintsExample extends StatelessWidget {
const AvatarBoxConstraintsExample({super.key});
@override
Widget build(BuildContext context) {
return const Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
RawChip(
avatarBoxConstraints: BoxConstraints.tightForFinite(),
avatar: Icon(Icons.star),
label: SizedBox(
width: 150,
child: Text(
'One line text.',
maxLines: 3,
overflow: TextOverflow.ellipsis,
),
),
),
SizedBox(height: 10),
RawChip(
avatarBoxConstraints: BoxConstraints.tightForFinite(),
avatar: Icon(Icons.star),
label: SizedBox(
width: 150,
child: Text(
'This text will wrap into two lines.',
maxLines: 3,
overflow: TextOverflow.ellipsis,
),
),
),
SizedBox(height: 10),
RawChip(
avatarBoxConstraints: BoxConstraints.tightForFinite(),
avatar: Icon(Icons.star),
label: SizedBox(
width: 150,
child: Text(
'This is a very long text that will wrap into three lines.',
maxLines: 3,
overflow: TextOverflow.ellipsis,
),
),
),
],
);
}
}

View file

@ -0,0 +1,75 @@
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
/// Flutter code sample for [DeletableChipAttributes.deleteIconBoxConstraints].
void main() => runApp(const DeleteIconBoxConstraintsApp());
class DeleteIconBoxConstraintsApp extends StatelessWidget {
const DeleteIconBoxConstraintsApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: Scaffold(
body: Center(
child: DeleteIconBoxConstraintsExample(),
),
),
);
}
}
class DeleteIconBoxConstraintsExample extends StatelessWidget {
const DeleteIconBoxConstraintsExample({super.key});
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
RawChip(
deleteIconBoxConstraints: const BoxConstraints.tightForFinite(),
onDeleted: () {},
label: const SizedBox(
width: 150,
child: Text(
'One line text.',
maxLines: 3,
overflow: TextOverflow.ellipsis,
),
),
),
const SizedBox(height: 10),
RawChip(
deleteIconBoxConstraints: const BoxConstraints.tightForFinite(),
onDeleted: () {},
label: const SizedBox(
width: 150,
child: Text(
'This text will wrap into two lines.',
maxLines: 3,
overflow: TextOverflow.ellipsis,
),
),
),
const SizedBox(height: 10),
RawChip(
deleteIconBoxConstraints: const BoxConstraints.tightForFinite(),
onDeleted: () {},
label: const SizedBox(
width: 150,
child: Text(
'This is a very long text that will wrap into three lines.',
maxLines: 3,
overflow: TextOverflow.ellipsis,
),
),
),
],
);
}
}

View file

@ -0,0 +1,55 @@
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
import 'package:flutter_api_samples/material/chip/chip_attributes.avatar_box_constraints.0.dart' as example;
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('RawChip.avatarBoxConstraints updates avatar size constraints', (WidgetTester tester) async {
const double border = 1.0;
const double iconSize = 18.0;
const double padding = 8.0;
await tester.pumpWidget(
const example.AvatarBoxConstraintsApp(),
);
expect(tester.getSize(find.byType(RawChip).at(0)).width, equals(202.0));
expect(tester.getSize(find.byType(RawChip).at(0)).height, equals(58.0));
Offset chipTopLeft = tester.getTopLeft(find.byWidget(tester.widget<Material>(
find.descendant(
of: find.byType(RawChip).at(0),
matching: find.byType(Material),
),
)));
Offset avatarCenter = tester.getCenter(find.byIcon(Icons.star).at(0));
expect(chipTopLeft.dx, avatarCenter.dx - (iconSize / 2) - padding - border);
expect(tester.getSize(find.byType(RawChip).at(1)).width, equals(202.0));
expect(tester.getSize(find.byType(RawChip).at(1)).height, equals(78.0));
chipTopLeft = tester.getTopLeft(find.byWidget(tester.widget<Material>(
find.descendant(
of: find.byType(RawChip).at(1),
matching: find.byType(Material),
),
)));
avatarCenter = tester.getCenter(find.byIcon(Icons.star).at(1));
expect(chipTopLeft.dx, avatarCenter.dx - (iconSize / 2) - padding - border);
expect(tester.getSize(find.byType(RawChip).at(2)).width, equals(202.0));
expect(tester.getSize(find.byType(RawChip).at(2)).height, equals(78.0));
chipTopLeft = tester.getTopLeft(find.byWidget(tester.widget<Material>(
find.descendant(
of: find.byType(RawChip).at(2),
matching: find.byType(Material),
),
)));
avatarCenter = tester.getCenter(find.byIcon(Icons.star).at(2));
expect(chipTopLeft.dx, avatarCenter.dx - (iconSize / 2) - padding - border);
});
}

View file

@ -0,0 +1,55 @@
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
import 'package:flutter_api_samples/material/chip/deletable_chip_attributes.delete_icon_box_constraints.0.dart' as example;
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('RawChip.deleteIconBoxConstraints updates delete icon size constraints', (WidgetTester tester) async {
const double border = 1.0;
const double iconSize = 18.0;
const double padding = 8.0;
await tester.pumpWidget(
const example.DeleteIconBoxConstraintsApp(),
);
expect(tester.getSize(find.byType(RawChip).at(0)).width, equals(202.0));
expect(tester.getSize(find.byType(RawChip).at(0)).height, equals(58.0));
Offset chipToRight = tester.getTopRight(find.byWidget(tester.widget<Material>(
find.descendant(
of: find.byType(RawChip).at(0),
matching: find.byType(Material),
),
)));
Offset deleteIconCenter = tester.getCenter(find.byIcon(Icons.cancel).at(0));
expect(chipToRight.dx, deleteIconCenter.dx + (iconSize / 2) + padding + border);
expect(tester.getSize(find.byType(RawChip).at(1)).width, equals(202.0));
expect(tester.getSize(find.byType(RawChip).at(1)).height, equals(78.0));
chipToRight = tester.getTopRight(find.byWidget(tester.widget<Material>(
find.descendant(
of: find.byType(RawChip).at(1),
matching: find.byType(Material),
),
)));
deleteIconCenter = tester.getCenter(find.byIcon(Icons.cancel).at(1));
expect(chipToRight.dx, deleteIconCenter.dx + (iconSize / 2) + padding + border);
expect(tester.getSize(find.byType(RawChip).at(2)).width, equals(202.0));
expect(tester.getSize(find.byType(RawChip).at(2)).height, equals(78.0));
chipToRight = tester.getTopRight(find.byWidget(tester.widget<Material>(
find.descendant(
of: find.byType(RawChip).at(2),
matching: find.byType(Material),
),
)));
deleteIconCenter = tester.getCenter(find.byIcon(Icons.cancel).at(2));
expect(chipToRight.dx, deleteIconCenter.dx + (iconSize / 2) + padding + border);
});
}

View file

@ -109,6 +109,7 @@ class ActionChip extends StatelessWidget implements ChipAttributes, TappableChip
this.shadowColor,
this.surfaceTintColor,
this.iconTheme,
this.avatarBoxConstraints,
}) : assert(pressElevation == null || pressElevation >= 0.0),
assert(elevation == null || elevation >= 0.0),
_chipVariant = _ChipVariant.flat;
@ -143,6 +144,7 @@ class ActionChip extends StatelessWidget implements ChipAttributes, TappableChip
this.shadowColor,
this.surfaceTintColor,
this.iconTheme,
this.avatarBoxConstraints,
}) : assert(pressElevation == null || pressElevation >= 0.0),
assert(elevation == null || elevation >= 0.0),
_chipVariant = _ChipVariant.elevated;
@ -191,6 +193,8 @@ class ActionChip extends StatelessWidget implements ChipAttributes, TappableChip
final Color? surfaceTintColor;
@override
final IconThemeData? iconTheme;
@override
final BoxConstraints? avatarBoxConstraints;
@override
bool get isEnabled => onPressed != null;
@ -228,6 +232,7 @@ class ActionChip extends StatelessWidget implements ChipAttributes, TappableChip
shadowColor: shadowColor,
surfaceTintColor: surfaceTintColor,
iconTheme: iconTheme,
avatarBoxConstraints: avatarBoxConstraints,
);
}
}

View file

@ -225,6 +225,22 @@ abstract interface class ChipAttributes {
/// color and a size of 18.0 is used when the chip is disabled. Otherwise,
/// it defaults to null.
IconThemeData? get iconTheme;
/// Optional size constraints for the avatar.
///
/// When unspecified, defaults to a minimum size of chip height or label height
/// (whichever is greater) and a padding of 8.0 pixels on all sides.
///
/// The default constraints ensure that the avatar is accessible.
/// Specifying this parameter enables creation of avatar smaller than
/// the minimum size, but it is not recommended.
///
/// {@tool dartpad}
/// This sample shows how to use [avatarBoxConstraints] to adjust avatar size constraints
///
/// ** See code in examples/api/lib/material/chip/chip_attributes.avatar_box_constraints.0.dart **
/// {@end-tool}
BoxConstraints? get avatarBoxConstraints;
}
/// An interface for Material Design chips that can be deleted.
@ -283,6 +299,23 @@ abstract interface class DeletableChipAttributes {
///
/// If the chip is disabled, the delete button tooltip will not be shown.
String? get deleteButtonTooltipMessage;
/// Optional size constraints for the delete icon.
///
/// When unspecified, defaults to a minimum size of chip height or label height
/// (whichever is greater) and a padding of 8.0 pixels on all sides.
///
/// The default constraints ensure that the delete icon is accessible.
/// Specifying this parameter enables creation of delete icon smaller than
/// the minimum size, but it is not recommended.
///
/// {@tool dartpad}
/// This sample shows how to use [deleteIconBoxConstraints] to adjust delete icon
/// size constraints.
///
/// ** See code in examples/api/lib/material/chip/deletable_chip_attributes.delete_icon_box_constraints.0.dart **
/// {@end-tool}
BoxConstraints? get deleteIconBoxConstraints;
}
/// An interface for Material Design chips that can have check marks.
@ -590,6 +623,8 @@ class Chip extends StatelessWidget implements ChipAttributes, DeletableChipAttri
this.shadowColor,
this.surfaceTintColor,
this.iconTheme,
this.avatarBoxConstraints,
this.deleteIconBoxConstraints,
}) : assert(elevation == null || elevation >= 0.0);
@override
@ -636,6 +671,10 @@ class Chip extends StatelessWidget implements ChipAttributes, DeletableChipAttri
final Color? surfaceTintColor;
@override
final IconThemeData? iconTheme;
@override
final BoxConstraints? avatarBoxConstraints;
@override
final BoxConstraints? deleteIconBoxConstraints;
@override
Widget build(BuildContext context) {
@ -664,6 +703,8 @@ class Chip extends StatelessWidget implements ChipAttributes, DeletableChipAttri
shadowColor: shadowColor,
surfaceTintColor: surfaceTintColor,
iconTheme: iconTheme,
avatarBoxConstraints: avatarBoxConstraints,
deleteIconBoxConstraints: deleteIconBoxConstraints,
);
}
}
@ -751,6 +792,8 @@ class RawChip extends StatefulWidget
this.showCheckmark,
this.checkmarkColor,
this.avatarBorder = const CircleBorder(),
this.avatarBoxConstraints,
this.deleteIconBoxConstraints,
}) : assert(pressElevation == null || pressElevation >= 0.0),
assert(elevation == null || elevation >= 0.0),
deleteIcon = deleteIcon ?? _kDefaultDeleteIcon;
@ -830,6 +873,10 @@ class RawChip extends StatefulWidget
final Color? checkmarkColor;
@override
final ShapeBorder avatarBorder;
@override
final BoxConstraints? avatarBoxConstraints;
@override
final BoxConstraints? deleteIconBoxConstraints;
/// If set, this indicates that the chip should be disabled if all of the
/// tap callbacks ([onSelected], [onPressed]) are null.
@ -1205,6 +1252,10 @@ class _RawChipState extends State<RawChip> with MaterialStateMixin, TickerProvid
final IconThemeData? iconTheme = widget.iconTheme
?? chipTheme.iconTheme
?? chipDefaults.iconTheme;
final BoxConstraints? avatarBoxConstraints = widget.avatarBoxConstraints
?? chipTheme.avatarBoxConstraints;
final BoxConstraints? deleteIconBoxConstraints = widget.deleteIconBoxConstraints
?? chipTheme.deleteIconBoxConstraints;
final TextStyle effectiveLabelStyle = labelStyle.merge(widget.labelStyle);
final Color? resolvedLabelColor = MaterialStateProperty.resolveAs<Color?>(effectiveLabelStyle.color, materialStates);
@ -1300,6 +1351,8 @@ class _RawChipState extends State<RawChip> with MaterialStateMixin, TickerProvid
deleteDrawerAnimation: deleteDrawerAnimation,
isEnabled: widget.isEnabled,
avatarBorder: widget.avatarBorder,
avatarBoxConstraints: avatarBoxConstraints,
deleteIconBoxConstraints: deleteIconBoxConstraints,
),
),
),
@ -1423,6 +1476,8 @@ class _ChipRenderWidget extends SlottedMultiChildRenderObjectWidget<_ChipSlot, R
required this.deleteDrawerAnimation,
required this.enableAnimation,
this.avatarBorder,
this.avatarBoxConstraints,
this.deleteIconBoxConstraints,
});
final _ChipRenderTheme theme;
@ -1433,6 +1488,8 @@ class _ChipRenderWidget extends SlottedMultiChildRenderObjectWidget<_ChipSlot, R
final Animation<double> deleteDrawerAnimation;
final Animation<double> enableAnimation;
final ShapeBorder? avatarBorder;
final BoxConstraints? avatarBoxConstraints;
final BoxConstraints? deleteIconBoxConstraints;
@override
Iterable<_ChipSlot> get slots => _ChipSlot.values;
@ -1457,7 +1514,9 @@ class _ChipRenderWidget extends SlottedMultiChildRenderObjectWidget<_ChipSlot, R
..avatarDrawerAnimation = avatarDrawerAnimation
..deleteDrawerAnimation = deleteDrawerAnimation
..enableAnimation = enableAnimation
..avatarBorder = avatarBorder;
..avatarBorder = avatarBorder
..avatarBoxConstraints = avatarBoxConstraints
..deleteIconBoxConstraints = deleteIconBoxConstraints;
}
@override
@ -1472,6 +1531,8 @@ class _ChipRenderWidget extends SlottedMultiChildRenderObjectWidget<_ChipSlot, R
deleteDrawerAnimation: deleteDrawerAnimation,
enableAnimation: enableAnimation,
avatarBorder: avatarBorder,
avatarBoxConstraints: avatarBoxConstraints,
deleteIconBoxConstraints: deleteIconBoxConstraints,
);
}
}
@ -1557,8 +1618,12 @@ class _RenderChip extends RenderBox with SlottedContainerRenderObjectMixin<_Chip
required this.deleteDrawerAnimation,
required this.enableAnimation,
this.avatarBorder,
BoxConstraints? avatarBoxConstraints,
BoxConstraints? deleteIconBoxConstraints,
}) : _theme = theme,
_textDirection = textDirection;
_textDirection = textDirection,
_avatarBoxConstraints = avatarBoxConstraints,
_deleteIconBoxConstraints = deleteIconBoxConstraints;
bool? value;
bool? isEnabled;
@ -1594,6 +1659,26 @@ class _RenderChip extends RenderBox with SlottedContainerRenderObjectMixin<_Chip
markNeedsLayout();
}
BoxConstraints? get avatarBoxConstraints => _avatarBoxConstraints;
BoxConstraints? _avatarBoxConstraints;
set avatarBoxConstraints(BoxConstraints? value) {
if (_avatarBoxConstraints == value) {
return;
}
_avatarBoxConstraints = value;
markNeedsLayout();
}
BoxConstraints? get deleteIconBoxConstraints => _deleteIconBoxConstraints;
BoxConstraints? _deleteIconBoxConstraints;
set deleteIconBoxConstraints(BoxConstraints? value) {
if (_deleteIconBoxConstraints == value) {
return;
}
_deleteIconBoxConstraints = value;
markNeedsLayout();
}
// The returned list is ordered for hit testing.
@override
Iterable<RenderBox> get children {
@ -1712,9 +1797,9 @@ class _RenderChip extends RenderBox with SlottedContainerRenderObjectMixin<_Chip
);
}
Size _layoutAvatar(BoxConstraints contentConstraints, double contentSize, [ChildLayouter layoutChild = ChildLayoutHelper.layoutChild]) {
Size _layoutAvatar(double contentSize, [ChildLayouter layoutChild = ChildLayoutHelper.layoutChild]) {
final double requestedSize = math.max(0.0, contentSize);
final BoxConstraints avatarConstraints = BoxConstraints.tightFor(
final BoxConstraints avatarConstraints = avatarBoxConstraints ?? BoxConstraints.tightFor(
width: requestedSize,
height: requestedSize,
);
@ -1733,9 +1818,9 @@ class _RenderChip extends RenderBox with SlottedContainerRenderObjectMixin<_Chip
return Size(avatarWidth, avatarHeight);
}
Size _layoutDeleteIcon(BoxConstraints contentConstraints, double contentSize, [ChildLayouter layoutChild = ChildLayoutHelper.layoutChild]) {
Size _layoutDeleteIcon(double contentSize, [ChildLayouter layoutChild = ChildLayoutHelper.layoutChild]) {
final double requestedSize = math.max(0.0, contentSize);
final BoxConstraints deleteIconConstraints = BoxConstraints.tightFor(
final BoxConstraints deleteIconConstraints = deleteIconBoxConstraints ?? BoxConstraints.tightFor(
width: requestedSize,
height: requestedSize,
);
@ -1795,8 +1880,8 @@ class _RenderChip extends RenderBox with SlottedContainerRenderObjectMixin<_Chip
_kChipHeight - theme.padding.vertical + theme.labelPadding.vertical,
rawLabelSize.height + theme.labelPadding.vertical,
);
final Size avatarSize = _layoutAvatar(contentConstraints, contentSize, layoutChild);
final Size deleteIconSize = _layoutDeleteIcon(contentConstraints, contentSize, layoutChild);
final Size avatarSize = _layoutAvatar(contentSize, layoutChild);
final Size deleteIconSize = _layoutDeleteIcon(contentSize, layoutChild);
final Size labelSize = _layoutLabel(
contentConstraints,
avatarSize.width + deleteIconSize.width,

View file

@ -198,6 +198,8 @@ class ChipThemeData with Diagnosticable {
this.elevation,
this.pressElevation,
this.iconTheme,
this.avatarBoxConstraints,
this.deleteIconBoxConstraints,
});
/// Generates a ChipThemeData from a brightness, a primary color, and a text
@ -436,6 +438,19 @@ class ChipThemeData with Diagnosticable {
/// [FilterChip], [InputChip], [RawChip].
final IconThemeData? iconTheme;
/// Overrides the default for [ChipAttributes.avatarBoxConstraints],
/// the size constraints for the avatar widget.
///
/// This property applies to [ActionChip], [Chip], [ChoiceChip],
/// [FilterChip], [InputChip], [RawChip].
final BoxConstraints? avatarBoxConstraints;
/// Overrides the default for [DeletableChipAttributes.deleteIconBoxConstraints].
/// the size constraints for the delete icon widget.
///
/// This property applies to [Chip], [FilterChip], [InputChip], [RawChip].
final BoxConstraints? deleteIconBoxConstraints;
/// Creates a copy of this object but with the given fields replaced with the
/// new values.
ChipThemeData copyWith({
@ -460,6 +475,8 @@ class ChipThemeData with Diagnosticable {
double? elevation,
double? pressElevation,
IconThemeData? iconTheme,
BoxConstraints? avatarBoxConstraints,
BoxConstraints? deleteIconBoxConstraints,
}) {
return ChipThemeData(
color: color ?? this.color,
@ -483,6 +500,8 @@ class ChipThemeData with Diagnosticable {
elevation: elevation ?? this.elevation,
pressElevation: pressElevation ?? this.pressElevation,
iconTheme: iconTheme ?? this.iconTheme,
avatarBoxConstraints: avatarBoxConstraints ?? this.avatarBoxConstraints,
deleteIconBoxConstraints: deleteIconBoxConstraints ?? this.deleteIconBoxConstraints,
);
}
@ -517,6 +536,8 @@ class ChipThemeData with Diagnosticable {
iconTheme: a?.iconTheme != null || b?.iconTheme != null
? IconThemeData.lerp(a?.iconTheme, b?.iconTheme, t)
: null,
avatarBoxConstraints: BoxConstraints.lerp(a?.avatarBoxConstraints, b?.avatarBoxConstraints, t),
deleteIconBoxConstraints: BoxConstraints.lerp(a?.deleteIconBoxConstraints, b?.deleteIconBoxConstraints, t),
);
}
@ -565,6 +586,8 @@ class ChipThemeData with Diagnosticable {
elevation,
pressElevation,
iconTheme,
avatarBoxConstraints,
deleteIconBoxConstraints,
]);
@override
@ -596,7 +619,9 @@ class ChipThemeData with Diagnosticable {
&& other.brightness == brightness
&& other.elevation == elevation
&& other.pressElevation == pressElevation
&& other.iconTheme == iconTheme;
&& other.iconTheme == iconTheme
&& other.avatarBoxConstraints == avatarBoxConstraints
&& other.deleteIconBoxConstraints == deleteIconBoxConstraints;
}
@override
@ -623,5 +648,7 @@ class ChipThemeData with Diagnosticable {
properties.add(DoubleProperty('elevation', elevation, defaultValue: null));
properties.add(DoubleProperty('pressElevation', pressElevation, defaultValue: null));
properties.add(DiagnosticsProperty<IconThemeData>('iconTheme', iconTheme, defaultValue: null));
properties.add(DiagnosticsProperty<BoxConstraints>('avatarBoxConstraints', avatarBoxConstraints, defaultValue: null));
properties.add(DiagnosticsProperty<BoxConstraints>('deleteIconBoxConstraints', deleteIconBoxConstraints, defaultValue: null));
}
}

View file

@ -92,6 +92,7 @@ class ChoiceChip extends StatelessWidget
this.showCheckmark,
this.checkmarkColor,
this.avatarBorder = const CircleBorder(),
this.avatarBoxConstraints,
}) : assert(pressElevation == null || pressElevation >= 0.0),
assert(elevation == null || elevation >= 0.0),
_chipVariant = _ChipVariant.flat;
@ -132,6 +133,7 @@ class ChoiceChip extends StatelessWidget
this.showCheckmark,
this.checkmarkColor,
this.avatarBorder = const CircleBorder(),
this.avatarBoxConstraints,
}) : assert(pressElevation == null || pressElevation >= 0.0),
assert(elevation == null || elevation >= 0.0),
_chipVariant = _ChipVariant.elevated;
@ -192,6 +194,8 @@ class ChoiceChip extends StatelessWidget
final ShapeBorder avatarBorder;
@override
final IconThemeData? iconTheme;
@override
final BoxConstraints? avatarBoxConstraints;
@override
bool get isEnabled => onSelected != null;
@ -236,6 +240,7 @@ class ChoiceChip extends StatelessWidget
selectedShadowColor: selectedShadowColor,
avatarBorder: avatarBorder,
iconTheme: iconTheme,
avatarBoxConstraints: avatarBoxConstraints,
);
}
}

View file

@ -101,6 +101,8 @@ class FilterChip extends StatelessWidget
this.showCheckmark,
this.checkmarkColor,
this.avatarBorder = const CircleBorder(),
this.avatarBoxConstraints,
this.deleteIconBoxConstraints,
}) : assert(pressElevation == null || pressElevation >= 0.0),
assert(elevation == null || elevation >= 0.0),
_chipVariant = _ChipVariant.flat;
@ -145,6 +147,8 @@ class FilterChip extends StatelessWidget
this.showCheckmark,
this.checkmarkColor,
this.avatarBorder = const CircleBorder(),
this.avatarBoxConstraints,
this.deleteIconBoxConstraints,
}) : assert(pressElevation == null || pressElevation >= 0.0),
assert(elevation == null || elevation >= 0.0),
_chipVariant = _ChipVariant.elevated;
@ -213,6 +217,10 @@ class FilterChip extends StatelessWidget
final ShapeBorder avatarBorder;
@override
final IconThemeData? iconTheme;
@override
final BoxConstraints? avatarBoxConstraints;
@override
final BoxConstraints? deleteIconBoxConstraints;
@override
bool get isEnabled => onSelected != null;
@ -262,6 +270,8 @@ class FilterChip extends StatelessWidget
checkmarkColor: checkmarkColor,
avatarBorder: avatarBorder,
iconTheme: iconTheme,
avatarBoxConstraints: avatarBoxConstraints,
deleteIconBoxConstraints: deleteIconBoxConstraints,
);
}
}

View file

@ -122,6 +122,8 @@ class InputChip extends StatelessWidget
this.showCheckmark,
this.checkmarkColor,
this.avatarBorder = const CircleBorder(),
this.avatarBoxConstraints,
this.deleteIconBoxConstraints,
}) : assert(pressElevation == null || pressElevation >= 0.0),
assert(elevation == null || elevation >= 0.0);
@ -193,6 +195,10 @@ class InputChip extends StatelessWidget
final ShapeBorder avatarBorder;
@override
final IconThemeData? iconTheme;
@override
final BoxConstraints? avatarBoxConstraints;
@override
final BoxConstraints? deleteIconBoxConstraints;
@override
Widget build(BuildContext context) {
@ -238,6 +244,8 @@ class InputChip extends StatelessWidget
isEnabled: isEnabled && (onSelected != null || onDeleted != null || onPressed != null),
avatarBorder: avatarBorder,
iconTheme: iconTheme,
avatarBoxConstraints: avatarBoxConstraints,
deleteIconBoxConstraints: deleteIconBoxConstraints,
);
}
}

View file

@ -437,4 +437,60 @@ void main() {
expect(getIconData(tester).color, const Color(0xff00ff00));
});
testWidgets('ActionChip avatar layout constraints can be customized', (WidgetTester tester) async {
const double border = 1.0;
const double iconSize = 18.0;
const double labelPadding = 8.0;
const double padding = 8.0;
const Size labelSize = Size(100, 100);
Widget buildChip({BoxConstraints? avatarBoxConstraints}) {
return wrapForChip(
child: Center(
child: ActionChip(
avatarBoxConstraints: avatarBoxConstraints,
avatar: const Icon(Icons.favorite),
label: Container(
width: labelSize.width,
height: labelSize.width,
color: const Color(0xFFFF0000),
),
),
),
);
}
// Test default avatar layout constraints.
await tester.pumpWidget(buildChip());
expect(tester.getSize(find.byType(ActionChip)).width, equals(234.0));
expect(tester.getSize(find.byType(ActionChip)).height, equals(118.0));
// Calculate the distance between avatar and chip edges.
Offset chipTopLeft = tester.getTopLeft(find.byWidget(getMaterial(tester)));
final Offset avatarCenter = tester.getCenter(find.byIcon(Icons.favorite));
expect(chipTopLeft.dx, avatarCenter.dx - (labelSize.width / 2) - padding - border);
expect(chipTopLeft.dy, avatarCenter.dy - (labelSize.width / 2) - padding - border);
// Calculate the distance between avatar and label.
Offset labelTopLeft = tester.getTopLeft(find.byType(Container));
expect(labelTopLeft.dx, avatarCenter.dx + (labelSize.width / 2) + labelPadding);
// Test custom avatar layout constraints.
await tester.pumpWidget(buildChip(avatarBoxConstraints: const BoxConstraints.tightForFinite()));
await tester.pump();
expect(tester.getSize(find.byType(ActionChip)).width, equals(152.0));
expect(tester.getSize(find.byType(ActionChip)).height, equals(118.0));
// Calculate the distance between avatar and chip edges.
chipTopLeft = tester.getTopLeft(find.byWidget(getMaterial(tester)));
expect(chipTopLeft.dx, avatarCenter.dx - (iconSize / 2) - padding - border);
expect(chipTopLeft.dy, avatarCenter.dy - (labelSize.width / 2) - padding - border);
// Calculate the distance between avatar and label.
labelTopLeft = tester.getTopLeft(find.byType(Container));
expect(labelTopLeft.dx, avatarCenter.dx + (iconSize / 2) + labelPadding);
});
}

View file

@ -5088,7 +5088,7 @@ void main() {
});
},
child: Text('${isEnabled ? 'Disable' : 'Enable'} Chip'),
)
),
],
);
},
@ -5119,7 +5119,7 @@ void main() {
isEnabled: false,
label: const Text('Label'),
onDeleted: () { },
)
),
),
);
@ -5134,7 +5134,7 @@ void main() {
isEnabled: enabled,
label: const Text('Label'),
onDeleted: () { },
)
),
);
}
@ -5157,6 +5157,234 @@ void main() {
expect(findTooltipContainer('Delete'), findsNothing);
});
testWidgets('Chip avatar layout constraints can be customized', (WidgetTester tester) async {
const double border = 1.0;
const double iconSize = 18.0;
const double labelPadding = 8.0;
const double padding = 8.0;
const Size labelSize = Size(100, 100);
Widget buildChip({BoxConstraints? avatarBoxConstraints}) {
return wrapForChip(
child: Center(
child: Chip(
avatarBoxConstraints: avatarBoxConstraints,
avatar: const Icon(Icons.favorite),
label: Container(
width: labelSize.width,
height: labelSize.width,
color: const Color(0xFFFF0000),
),
),
),
);
}
// Test default avatar layout constraints.
await tester.pumpWidget(buildChip());
expect(tester.getSize(find.byType(Chip)).width, equals(234.0));
expect(tester.getSize(find.byType(Chip)).height, equals(118.0));
// Calculate the distance between avatar and chip edges.
Offset chipTopLeft = tester.getTopLeft(find.byWidget(getMaterial(tester)));
final Offset avatarCenter = tester.getCenter(find.byIcon(Icons.favorite));
expect(chipTopLeft.dx, avatarCenter.dx - (labelSize.width / 2) - padding - border);
expect(chipTopLeft.dy, avatarCenter.dy - (labelSize.width / 2) - padding - border);
// Calculate the distance between avatar and label.
Offset labelTopLeft = tester.getTopLeft(find.byType(Container));
expect(labelTopLeft.dx, avatarCenter.dx + (labelSize.width / 2) + labelPadding);
// Test custom avatar layout constraints.
await tester.pumpWidget(buildChip(avatarBoxConstraints: const BoxConstraints.tightForFinite()));
await tester.pump();
expect(tester.getSize(find.byType(Chip)).width, equals(152.0));
expect(tester.getSize(find.byType(Chip)).height, equals(118.0));
// Calculate the distance between avatar and chip edges.
chipTopLeft = tester.getTopLeft(find.byWidget(getMaterial(tester)));
expect(chipTopLeft.dx, avatarCenter.dx - (iconSize / 2) - padding - border);
expect(chipTopLeft.dy, avatarCenter.dy - (labelSize.width / 2) - padding - border);
// Calculate the distance between avatar and label.
labelTopLeft = tester.getTopLeft(find.byType(Container));
expect(labelTopLeft.dx, avatarCenter.dx + (iconSize / 2) + labelPadding);
});
testWidgets('RawChip avatar layout constraints can be customized', (WidgetTester tester) async {
const double border = 1.0;
const double iconSize = 18.0;
const double labelPadding = 8.0;
const double padding = 8.0;
const Size labelSize = Size(100, 100);
Widget buildChip({BoxConstraints? avatarBoxConstraints}) {
return wrapForChip(
child: Center(
child: RawChip(
avatarBoxConstraints: avatarBoxConstraints,
avatar: const Icon(Icons.favorite),
label: Container(
width: labelSize.width,
height: labelSize.width,
color: const Color(0xFFFF0000),
),
),
),
);
}
// Test default avatar layout constraints.
await tester.pumpWidget(buildChip());
expect(tester.getSize(find.byType(RawChip)).width, equals(234.0));
expect(tester.getSize(find.byType(RawChip)).height, equals(118.0));
// Calculate the distance between avatar and chip edges.
Offset chipTopLeft = tester.getTopLeft(find.byWidget(getMaterial(tester)));
final Offset avatarCenter = tester.getCenter(find.byIcon(Icons.favorite));
expect(chipTopLeft.dx, avatarCenter.dx - (labelSize.width / 2) - padding - border);
expect(chipTopLeft.dy, avatarCenter.dy - (labelSize.width / 2) - padding - border);
// Calculate the distance between avatar and label.
Offset labelTopLeft = tester.getTopLeft(find.byType(Container));
expect(labelTopLeft.dx, avatarCenter.dx + (labelSize.width / 2) + labelPadding);
// Test custom avatar layout constraints.
await tester.pumpWidget(buildChip(avatarBoxConstraints: const BoxConstraints.tightForFinite()));
await tester.pump();
expect(tester.getSize(find.byType(RawChip)).width, equals(152.0));
expect(tester.getSize(find.byType(RawChip)).height, equals(118.0));
// Calculate the distance between avatar and chip edges.
chipTopLeft = tester.getTopLeft(find.byWidget(getMaterial(tester)));
expect(chipTopLeft.dx, avatarCenter.dx - (iconSize / 2) - padding - border);
expect(chipTopLeft.dy, avatarCenter.dy - (labelSize.width / 2) - padding - border);
// Calculate the distance between avatar and label.
labelTopLeft = tester.getTopLeft(find.byType(Container));
expect(labelTopLeft.dx, avatarCenter.dx + (iconSize / 2) + labelPadding);
});
testWidgets('Chip delete icon layout constraints can be customized', (WidgetTester tester) async {
const double border = 1.0;
const double iconSize = 18.0;
const double labelPadding = 8.0;
const double padding = 8.0;
const Size labelSize = Size(100, 100);
Widget buildChip({BoxConstraints? deleteIconBoxConstraints}) {
return wrapForChip(
child: Center(
child: Chip(
deleteIconBoxConstraints: deleteIconBoxConstraints,
onDeleted: () { },
label: Container(
width: labelSize.width,
height: labelSize.width,
color: const Color(0xFFFF0000),
),
),
),
);
}
// Test default delete icon layout constraints.
await tester.pumpWidget(buildChip());
expect(tester.getSize(find.byType(Chip)).width, equals(234.0));
expect(tester.getSize(find.byType(Chip)).height, equals(118.0));
// Calculate the distance between delete icon and chip edges.
Offset chipTopRight = tester.getTopRight(find.byWidget(getMaterial(tester)));
final Offset deleteIconCenter = tester.getCenter(find.byIcon(Icons.cancel));
expect(chipTopRight.dx, deleteIconCenter.dx + (labelSize.width / 2) + padding + border);
expect(chipTopRight.dy, deleteIconCenter.dy - (labelSize.width / 2) - padding - border);
// Calculate the distance between delete icon and label.
Offset labelTopRight = tester.getTopRight(find.byType(Container));
expect(labelTopRight.dx, deleteIconCenter.dx - (labelSize.width / 2) - labelPadding);
// Test custom avatar layout constraints.
await tester.pumpWidget(buildChip(
deleteIconBoxConstraints: const BoxConstraints.tightForFinite(),
));
await tester.pump();
expect(tester.getSize(find.byType(Chip)).width, equals(152.0));
expect(tester.getSize(find.byType(Chip)).height, equals(118.0));
// Calculate the distance between delete icon and chip edges.
chipTopRight = tester.getTopRight(find.byWidget(getMaterial(tester)));
expect(chipTopRight.dx, deleteIconCenter.dx + (iconSize / 2) + padding + border);
expect(chipTopRight.dy, deleteIconCenter.dy - (labelSize.width / 2) - padding - border);
// Calculate the distance between delete icon and label.
labelTopRight = tester.getTopRight(find.byType(Container));
expect(labelTopRight.dx, deleteIconCenter.dx - (iconSize / 2) - labelPadding);
});
testWidgets('RawChip delete icon layout constraints can be customized', (WidgetTester tester) async {
const double border = 1.0;
const double iconSize = 18.0;
const double labelPadding = 8.0;
const double padding = 8.0;
const Size labelSize = Size(100, 100);
Widget buildChip({BoxConstraints? deleteIconBoxConstraints}) {
return wrapForChip(
child: Center(
child: RawChip(
deleteIconBoxConstraints: deleteIconBoxConstraints,
onDeleted: () { },
label: Container(
width: labelSize.width,
height: labelSize.width,
color: const Color(0xFFFF0000),
),
),
),
);
}
// Test default delete icon layout constraints.
await tester.pumpWidget(buildChip());
expect(tester.getSize(find.byType(RawChip)).width, equals(234.0));
expect(tester.getSize(find.byType(RawChip)).height, equals(118.0));
// Calculate the distance between delete icon and chip edges.
Offset chipTopRight = tester.getTopRight(find.byWidget(getMaterial(tester)));
final Offset deleteIconCenter = tester.getCenter(find.byIcon(Icons.cancel));
expect(chipTopRight.dx, deleteIconCenter.dx + (labelSize.width / 2) + padding + border);
expect(chipTopRight.dy, deleteIconCenter.dy - (labelSize.width / 2) - padding - border);
// Calculate the distance between delete icon and label.
Offset labelTopRight = tester.getTopRight(find.byType(Container));
expect(labelTopRight.dx, deleteIconCenter.dx - (labelSize.width / 2) - labelPadding);
// Test custom avatar layout constraints.
await tester.pumpWidget(buildChip(
deleteIconBoxConstraints: const BoxConstraints.tightForFinite(),
));
await tester.pump();
expect(tester.getSize(find.byType(RawChip)).width, equals(152.0));
expect(tester.getSize(find.byType(RawChip )).height, equals(118.0));
// Calculate the distance between delete icon and chip edges.
chipTopRight = tester.getTopRight(find.byWidget(getMaterial(tester)));
expect(chipTopRight.dx, deleteIconCenter.dx + (iconSize / 2) + padding + border);
expect(chipTopRight.dy, deleteIconCenter.dy - (labelSize.width / 2) - padding - border);
// Calculate the distance between delete icon and label.
labelTopRight = tester.getTopRight(find.byType(Container));
expect(labelTopRight.dx, deleteIconCenter.dx - (iconSize / 2) - labelPadding);
});
group('Material 2', () {
// These tests are only relevant for Material 2. Once Material 2
// support is deprecated and the APIs are removed, these tests

View file

@ -79,6 +79,8 @@ void main() {
expect(themeData.elevation, null);
expect(themeData.pressElevation, null);
expect(themeData.iconTheme, null);
expect(themeData.avatarBoxConstraints, null);
expect(themeData.deleteIconBoxConstraints, null);
});
testWidgets('Default ChipThemeData debugFillProperties', (WidgetTester tester) async {
@ -117,6 +119,8 @@ void main() {
elevation: 5,
pressElevation: 6,
iconTheme: IconThemeData(color: Color(0xffffff10)),
avatarBoxConstraints: BoxConstraints.tightForFinite(),
deleteIconBoxConstraints: BoxConstraints.tightForFinite(),
).debugFillProperties(builder);
final List<String> description = builder.properties
@ -145,7 +149,9 @@ void main() {
'brightness: dark',
'elevation: 5.0',
'pressElevation: 6.0',
'iconTheme: IconThemeData#00000(color: Color(0xffffff10))'
'iconTheme: IconThemeData#00000(color: Color(0xffffff10))',
'avatarBoxConstraints: BoxConstraints(unconstrained)',
'deleteIconBoxConstraints: BoxConstraints(unconstrained)',
]));
});
@ -1314,6 +1320,86 @@ void main() {
expect(getIconData(tester).size, 23.0);
expect(getIconData(tester).color, const Color(0xff112233));
});
testWidgets('ChipThemeData.avatarBoxConstraints updates avatar size constraints', (WidgetTester tester) async {
const double border = 1.0;
const double iconSize = 18.0;
const double labelPadding = 8.0;
const double padding = 8.0;
const Size labelSize = Size(75, 75);
// Test default avatar layout constraints.
await tester.pumpWidget(MaterialApp(
theme: ThemeData(chipTheme: const ChipThemeData(
avatarBoxConstraints: BoxConstraints.tightForFinite(),
)),
home: Material(
child: Center(
child: RawChip(
avatar: const Icon(Icons.favorite),
label: Container(
width: labelSize.width,
height: labelSize.width,
color: const Color(0xFFFF0000),
),
),
),
),
));
expect(tester.getSize(find.byType(RawChip)).width, equals(127.0));
expect(tester.getSize(find.byType(RawChip)).height, equals(93.0));
// Calculate the distance between avatar and chip edges.
final Offset chipTopLeft = tester.getTopLeft(find.byWidget(getMaterial(tester)));
final Offset avatarCenter = tester.getCenter(find.byIcon(Icons.favorite));
expect(chipTopLeft.dx, avatarCenter.dx - (iconSize / 2) - padding - border);
expect(chipTopLeft.dy, avatarCenter.dy - (labelSize.width / 2) - padding - border);
// Calculate the distance between avatar and label.
final Offset labelTopLeft = tester.getTopLeft(find.byType(Container));
expect(labelTopLeft.dx, avatarCenter.dx + (iconSize / 2) + labelPadding);
});
testWidgets('ChipThemeData.deleteIconBoxConstraints updates delete icon size constraints', (WidgetTester tester) async {
const double border = 1.0;
const double iconSize = 18.0;
const double labelPadding = 8.0;
const double padding = 8.0;
const Size labelSize = Size(75, 75);
// Test custom delete layout constraints.
await tester.pumpWidget(MaterialApp(
theme: ThemeData(chipTheme: const ChipThemeData(
deleteIconBoxConstraints: BoxConstraints.tightForFinite(),
)),
home: Material(
child: Center(
child: RawChip(
onDeleted: () { },
label: Container(
width: labelSize.width,
height: labelSize.width,
color: const Color(0xFFFF0000),
),
),
),
),
));
expect(tester.getSize(find.byType(RawChip)).width, equals(127.0));
expect(tester.getSize(find.byType(RawChip)).height, equals(93.0));
// Calculate the distance between delete icon and chip edges.
final Offset chipTopRight = tester.getTopRight(find.byWidget(getMaterial(tester)));
final Offset deleteIconCenter = tester.getCenter(find.byIcon(Icons.cancel));
expect(chipTopRight.dx, deleteIconCenter.dx + (iconSize / 2) + padding + border);
expect(chipTopRight.dy, deleteIconCenter.dy - (labelSize.width / 2) - padding - border);
// Calculate the distance between delete icon and label.
final Offset labelTopRight = tester.getTopRight(find.byType(Container));
expect(labelTopRight.dx, deleteIconCenter.dx - (iconSize / 2) - labelPadding);
});
}
class _MaterialStateOutlinedBorder extends StadiumBorder implements MaterialStateOutlinedBorder {

View file

@ -718,4 +718,61 @@ void main() {
expect(getIconData(tester).color, const Color(0xff00ff00));
});
testWidgets('ChoiceChip avatar layout constraints can be customized', (WidgetTester tester) async {
const double border = 1.0;
const double iconSize = 18.0;
const double labelPadding = 8.0;
const double padding = 8.0;
const Size labelSize = Size(100, 100);
Widget buildChip({BoxConstraints? avatarBoxConstraints}) {
return wrapForChip(
child: Center(
child: ChoiceChip(
avatarBoxConstraints: avatarBoxConstraints,
avatar: const Icon(Icons.favorite),
label: Container(
width: labelSize.width,
height: labelSize.width,
color: const Color(0xFFFF0000),
),
selected: false,
),
),
);
}
// Test default avatar layout constraints.
await tester.pumpWidget(buildChip());
expect(tester.getSize(find.byType(ChoiceChip)).width, equals(234.0));
expect(tester.getSize(find.byType(ChoiceChip)).height, equals(118.0));
// Calculate the distance between avatar and chip edges.
Offset chipTopLeft = tester.getTopLeft(find.byWidget(getMaterial(tester)));
final Offset avatarCenter = tester.getCenter(find.byIcon(Icons.favorite));
expect(chipTopLeft.dx, avatarCenter.dx - (labelSize.width / 2) - padding - border);
expect(chipTopLeft.dy, avatarCenter.dy - (labelSize.width / 2) - padding - border);
// Calculate the distance between avatar and label.
Offset labelTopLeft = tester.getTopLeft(find.byType(Container));
expect(labelTopLeft.dx, avatarCenter.dx + (labelSize.width / 2) + labelPadding);
// Test custom avatar layout constraints.
await tester.pumpWidget(buildChip(avatarBoxConstraints: const BoxConstraints.tightForFinite()));
await tester.pump();
expect(tester.getSize(find.byType(ChoiceChip)).width, equals(152.0));
expect(tester.getSize(find.byType(ChoiceChip)).height, equals(118.0));
// Calculate the distance between avatar and chip edges.
chipTopLeft = tester.getTopLeft(find.byWidget(getMaterial(tester)));
expect(chipTopLeft.dx, avatarCenter.dx - (iconSize / 2) - padding - border);
expect(chipTopLeft.dy, avatarCenter.dy - (labelSize.width / 2) - padding - border);
// Calculate the distance between avatar and label.
labelTopLeft = tester.getTopLeft(find.byType(Container));
expect(labelTopLeft.dx, avatarCenter.dx + (iconSize / 2) + labelPadding);
});
}

View file

@ -1170,4 +1170,120 @@ void main() {
theme.colorScheme.onSecondaryContainer,
);
});
testWidgets('FilterChip avatar layout constraints can be customized', (WidgetTester tester) async {
const double border = 1.0;
const double iconSize = 18.0;
const double labelPadding = 8.0;
const double padding = 8.0;
const Size labelSize = Size(100, 100);
Widget buildChip({BoxConstraints? avatarBoxConstraints}) {
return wrapForChip(
child: Center(
child: FilterChip(
avatarBoxConstraints: avatarBoxConstraints,
avatar: const Icon(Icons.favorite),
label: Container(
width: labelSize.width,
height: labelSize.width,
color: const Color(0xFFFF0000),
),
onSelected: (bool value) { },
),
),
);
}
// Test default avatar layout constraints.
await tester.pumpWidget(buildChip());
expect(tester.getSize(find.byType(FilterChip)).width, equals(234.0));
expect(tester.getSize(find.byType(FilterChip)).height, equals(118.0));
// Calculate the distance between avatar and chip edges.
Offset chipTopLeft = tester.getTopLeft(find.byWidget(getMaterial(tester)));
final Offset avatarCenter = tester.getCenter(find.byIcon(Icons.favorite));
expect(chipTopLeft.dx, avatarCenter.dx - (labelSize.width / 2) - padding - border);
expect(chipTopLeft.dy, avatarCenter.dy - (labelSize.width / 2) - padding - border);
// Calculate the distnance between avatar and label.
Offset labelTopLeft = tester.getTopLeft(find.byType(Container));
expect(labelTopLeft.dx, avatarCenter.dx + (labelSize.width / 2) + labelPadding);
// Test custom avatar layout constraints.
await tester.pumpWidget(buildChip(avatarBoxConstraints: const BoxConstraints.tightForFinite()));
await tester.pump();
expect(tester.getSize(find.byType(FilterChip)).width, equals(152.0));
expect(tester.getSize(find.byType(FilterChip)).height, equals(118.0));
// Calculate the distance between avatar and chip edges.
chipTopLeft = tester.getTopLeft(find.byWidget(getMaterial(tester)));
expect(chipTopLeft.dx, avatarCenter.dx - (iconSize / 2) - padding - border);
expect(chipTopLeft.dy, avatarCenter.dy - (labelSize.width / 2) - padding - border);
// Calculate the distnance between avatar and label.
labelTopLeft = tester.getTopLeft(find.byType(Container));
expect(labelTopLeft.dx, avatarCenter.dx + (iconSize / 2) + labelPadding);
});
testWidgets('FilterChip delete icon layout constraints can be customized', (WidgetTester tester) async {
const double border = 1.0;
const double iconSize = 18.0;
const double labelPadding = 8.0;
const double padding = 8.0;
const Size labelSize = Size(100, 100);
Widget buildChip({BoxConstraints? deleteIconBoxConstraints}) {
return wrapForChip(
child: Center(
child: FilterChip(
deleteIconBoxConstraints: deleteIconBoxConstraints,
onDeleted: () { },
label: Container(
width: labelSize.width,
height: labelSize.width,
color: const Color(0xFFFF0000),
),
onSelected: (bool value) { },
),
),
);
}
// Test default delete icon layout constraints.
await tester.pumpWidget(buildChip());
expect(tester.getSize(find.byType(FilterChip)).width, equals(234.0));
expect(tester.getSize(find.byType(FilterChip)).height, equals(118.0));
// Calculate the distance between delete icon and chip edges.
Offset chipTopRight = tester.getTopRight(find.byWidget(getMaterial(tester)));
final Offset deleteIconCenter = tester.getCenter(find.byIcon(Icons.clear));
expect(chipTopRight.dx, deleteIconCenter.dx + (labelSize.width / 2) + padding + border);
expect(chipTopRight.dy, deleteIconCenter.dy - (labelSize.width / 2) - padding - border);
// Calculate the distance between delete icon and label.
Offset labelTopRight = tester.getTopRight(find.byType(Container));
expect(labelTopRight.dx, deleteIconCenter.dx - (labelSize.width / 2) - labelPadding);
// Test custom avatar layout constraints.
await tester.pumpWidget(buildChip(
deleteIconBoxConstraints: const BoxConstraints.tightForFinite(),
));
await tester.pump();
expect(tester.getSize(find.byType(FilterChip)).width, equals(152.0));
expect(tester.getSize(find.byType(FilterChip )).height, equals(118.0));
// Calculate the distance between delete icon and chip edges.
chipTopRight = tester.getTopRight(find.byWidget(getMaterial(tester)));
expect(chipTopRight.dx, deleteIconCenter.dx + (iconSize / 2) + padding + border);
expect(chipTopRight.dy, deleteIconCenter.dy - (labelSize.width / 2) - padding - border);
// Calculate the distance between delete icon and label.
labelTopRight = tester.getTopRight(find.byType(Container));
expect(labelTopRight.dx, deleteIconCenter.dx - (iconSize / 2) - labelPadding);
});
}

View file

@ -97,6 +97,15 @@ RenderBox getMaterialBox(WidgetTester tester) {
);
}
Material getMaterial(WidgetTester tester) {
return tester.widget<Material>(
find.descendant(
of: find.byType(InputChip),
matching: find.byType(Material),
),
);
}
IconThemeData getIconData(WidgetTester tester) {
final IconTheme iconTheme = tester.firstWidget(
find.descendant(
@ -476,4 +485,118 @@ void main() {
// Delete button tooltip should not be visible.
expect(findTooltipContainer('Delete'), findsNothing);
});
testWidgets('InputChip avatar layout constraints can be customized', (WidgetTester tester) async {
const double border = 1.0;
const double iconSize = 18.0;
const double labelPadding = 8.0;
const double padding = 8.0;
const Size labelSize = Size(100, 100);
Widget buildChip({BoxConstraints? avatarBoxConstraints}) {
return wrapForChip(
child: Center(
child: InputChip(
avatarBoxConstraints: avatarBoxConstraints,
avatar: const Icon(Icons.favorite),
label: Container(
width: labelSize.width,
height: labelSize.width,
color: const Color(0xFFFF0000),
),
),
),
);
}
// Test default avatar layout constraints.
await tester.pumpWidget(buildChip());
expect(tester.getSize(find.byType(InputChip)).width, equals(234.0));
expect(tester.getSize(find.byType(InputChip)).height, equals(118.0));
// Calculate the distance between avatar and chip edges.
Offset chipTopLeft = tester.getTopLeft(find.byWidget(getMaterial(tester)));
final Offset avatarCenter = tester.getCenter(find.byIcon(Icons.favorite));
expect(chipTopLeft.dx, avatarCenter.dx - (labelSize.width / 2) - padding - border);
expect(chipTopLeft.dy, avatarCenter.dy - (labelSize.width / 2) - padding - border);
// Calculate the distnance between avatar and label.
Offset labelTopLeft = tester.getTopLeft(find.byType(Container));
expect(labelTopLeft.dx, avatarCenter.dx + (labelSize.width / 2) + labelPadding);
// Test custom avatar layout constraints.
await tester.pumpWidget(buildChip(avatarBoxConstraints: const BoxConstraints.tightForFinite()));
await tester.pump();
expect(tester.getSize(find.byType(InputChip)).width, equals(152.0));
expect(tester.getSize(find.byType(InputChip)).height, equals(118.0));
// Calculate the distance between avatar and chip edges.
chipTopLeft = tester.getTopLeft(find.byWidget(getMaterial(tester)));
expect(chipTopLeft.dx, avatarCenter.dx - (iconSize / 2) - padding - border);
expect(chipTopLeft.dy, avatarCenter.dy - (labelSize.width / 2) - padding - border);
// Calculate the distnance between avatar and label.
labelTopLeft = tester.getTopLeft(find.byType(Container));
expect(labelTopLeft.dx, avatarCenter.dx + (iconSize / 2) + labelPadding);
});
testWidgets('InputChip delete icon layout constraints can be customized', (WidgetTester tester) async {
const double border = 1.0;
const double iconSize = 18.0;
const double labelPadding = 8.0;
const double padding = 8.0;
const Size labelSize = Size(100, 100);
Widget buildChip({BoxConstraints? deleteIconBoxConstraints}) {
return wrapForChip(
child: Center(
child: InputChip(
deleteIconBoxConstraints: deleteIconBoxConstraints,
onDeleted: () { },
label: Container(
width: labelSize.width,
height: labelSize.width,
color: const Color(0xFFFF0000),
),
),
),
);
}
// Test default delete icon layout constraints.
await tester.pumpWidget(buildChip());
expect(tester.getSize(find.byType(InputChip)).width, equals(234.0));
expect(tester.getSize(find.byType(InputChip)).height, equals(118.0));
// Calculate the distance between delete icon and chip edges.
Offset chipTopRight = tester.getTopRight(find.byWidget(getMaterial(tester)));
final Offset deleteIconCenter = tester.getCenter(find.byIcon(Icons.clear));
expect(chipTopRight.dx, deleteIconCenter.dx + (labelSize.width / 2) + padding + border);
expect(chipTopRight.dy, deleteIconCenter.dy - (labelSize.width / 2) - padding - border);
// Calculate the distance between delete icon and label.
Offset labelTopRight = tester.getTopRight(find.byType(Container));
expect(labelTopRight.dx, deleteIconCenter.dx - (labelSize.width / 2) - labelPadding);
// Test custom avatar layout constraints.
await tester.pumpWidget(buildChip(
deleteIconBoxConstraints: const BoxConstraints.tightForFinite(),
));
await tester.pump();
expect(tester.getSize(find.byType(InputChip)).width, equals(152.0));
expect(tester.getSize(find.byType(InputChip)).height, equals(118.0));
// Calculate the distance between delete icon and chip edges.
chipTopRight = tester.getTopRight(find.byWidget(getMaterial(tester)));
expect(chipTopRight.dx, deleteIconCenter.dx + (iconSize / 2) + padding + border);
expect(chipTopRight.dy, deleteIconCenter.dy - (labelSize.width / 2) - padding - border);
// Calculate the distance between delete icon and label.
labelTopRight = tester.getTopRight(find.byType(Container));
expect(labelTopRight.dx, deleteIconCenter.dx - (iconSize / 2) - labelPadding);
});
}