Commit c8832045 authored by Tianguang's avatar Tianguang Committed by Flutter GitHub Bot

Allow IconButton to have smaller sizes (#47457)

parent 5d37de26
......@@ -366,10 +366,7 @@ class _RawMaterialButtonState extends State<RawMaterialButton> {
final Color effectiveTextColor = MaterialStateProperty.resolveAs<Color>(widget.textStyle?.color, _states);
final ShapeBorder effectiveShape = MaterialStateProperty.resolveAs<ShapeBorder>(widget.shape, _states);
final Offset densityAdjustment = widget.visualDensity.baseSizeAdjustment;
final BoxConstraints effectiveConstraints = widget.constraints.copyWith(
minWidth: widget.constraints.minWidth != null ? (widget.constraints.minWidth + densityAdjustment.dx).clamp(0.0, double.infinity) as double : null,
minHeight: widget.constraints.minWidth != null ? (widget.constraints.minHeight + densityAdjustment.dy).clamp(0.0, double.infinity) as double : null,
);
final BoxConstraints effectiveConstraints = widget.visualDensity.effectiveConstraints(widget.constraints);
final EdgeInsetsGeometry padding = widget.padding.add(
EdgeInsets.only(
left: densityAdjustment.dx,
......
......@@ -154,6 +154,7 @@ class IconButton extends StatelessWidget {
this.autofocus = false,
this.tooltip,
this.enableFeedback = true,
this.constraints,
}) : assert(iconSize != null),
assert(padding != null),
assert(alignment != null),
......@@ -288,6 +289,26 @@ class IconButton extends StatelessWidget {
/// * [Feedback] for providing platform-specific feedback to certain actions.
final bool enableFeedback;
/// Optional size constraints for the button.
///
/// When unspecified, defaults to:
/// ```dart
/// const BoxConstraints(
/// minWidth: kMinInteractiveDimension,
/// minHeight: kMinInteractiveDimension,
/// )
/// ```
/// where [kMinInteractiveDimension] is 48.0, and then with visual density
/// applied.
///
/// The default constraints ensure that the button is accessible.
/// Specifying this parameter enables creation of buttons smaller than
/// the minimum size, but it is not recommended.
///
/// The visual density uses the [visualDensity] parameter if specified,
/// and `Theme.of(context).visualDensity` otherwise.
final BoxConstraints constraints;
@override
Widget build(BuildContext context) {
assert(debugCheckHasMaterial(context));
......@@ -298,9 +319,16 @@ class IconButton extends StatelessWidget {
else
currentColor = disabledColor ?? theme.disabledColor;
final Offset densityAdjustment = (visualDensity ?? theme.visualDensity).baseSizeAdjustment;
final VisualDensity effectiveVisualDensity = visualDensity ?? theme.visualDensity;
final BoxConstraints unadjustedConstraints = constraints ?? const BoxConstraints(
minWidth: _kMinButtonSize,
minHeight: _kMinButtonSize,
);
final BoxConstraints adjustedConstraints = effectiveVisualDensity.effectiveConstraints(unadjustedConstraints);
Widget result = ConstrainedBox(
constraints: BoxConstraints(minWidth: _kMinButtonSize + densityAdjustment.dx, minHeight: _kMinButtonSize + densityAdjustment.dy),
constraints: adjustedConstraints,
child: Padding(
padding: padding,
child: SizedBox(
......
......@@ -1815,6 +1815,16 @@ class VisualDensity extends Diagnosticable {
);
}
/// Return a copy of [constraints] whose minimum width and height have been
/// updated with the [baseSizeAdjustment].
BoxConstraints effectiveConstraints(BoxConstraints constraints){
assert(constraints != null && constraints.debugAssertIsValid());
return constraints.copyWith(
minWidth: (constraints.minWidth + baseSizeAdjustment.dx).clamp(0.0, double.infinity).toDouble(),
minHeight: (constraints.minHeight + baseSizeAdjustment.dy).clamp(0.0, double.infinity).toDouble(),
);
}
@override
bool operator ==(Object other) {
if (other.runtimeType != runtimeType) {
......
......@@ -75,6 +75,69 @@ void main() {
expect(iconButton.size, const Size(70.0, 70.0));
});
testWidgets('Small icons with non-null constraints can be <48dp', (WidgetTester tester) async {
await tester.pumpWidget(
wrap(
child: IconButton(
iconSize: 10.0,
onPressed: mockOnPressedFunction,
icon: const Icon(Icons.link),
constraints: const BoxConstraints(),
),
),
);
final RenderBox iconButton = tester.renderObject(find.byType(IconButton));
// By default IconButton has a padding of 8.0 on all sides, so both
// width and height are 10.0 + 2 * 8.0 = 26.0
expect(iconButton.size, const Size(26.0, 26.0));
});
testWidgets('Small icons with non-null constraints and custom padding can be <48dp', (WidgetTester tester) async {
await tester.pumpWidget(
wrap(
child: IconButton(
iconSize: 10.0,
padding: const EdgeInsets.all(3.0),
onPressed: mockOnPressedFunction,
icon: const Icon(Icons.link),
constraints: const BoxConstraints(),
),
),
);
final RenderBox iconButton = tester.renderObject(find.byType(IconButton));
// This IconButton has a padding of 3.0 on all sides, so both
// width and height are 10.0 + 2 * 3.0 = 16.0
expect(iconButton.size, const Size(16.0, 16.0));
});
testWidgets('Small icons comply with VisualDensity requirements', (WidgetTester tester) async {
await tester.pumpWidget(
wrap(
child: Theme(
data: ThemeData(visualDensity: const VisualDensity(horizontal: 1, vertical: -1)),
child: IconButton(
iconSize: 10.0,
onPressed: mockOnPressedFunction,
icon: const Icon(Icons.link),
constraints: const BoxConstraints(minWidth: 32.0, minHeight: 32.0),
),
),
),
);
final RenderBox iconButton = tester.renderObject(find.byType(IconButton));
// VisualDensity(horizontal: 1, vertical: -1) increases the icon's
// width by 4 pixels and decreases its height by 4 pixels, giving
// final width 32.0 + 4.0 = 36.0 and
// final height 32.0 - 4.0 = 28.0
expect(iconButton.size, const Size(36.0, 28.0));
});
testWidgets('test default icon buttons are constrained', (WidgetTester tester) async {
await tester.pumpWidget(
wrap(
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment