InputDecorator prefixIconConstraints and suffixIconConstraints (#50058)
diff --git a/packages/flutter/lib/src/material/input_decorator.dart b/packages/flutter/lib/src/material/input_decorator.dart index fd32717..da2b951 100644 --- a/packages/flutter/lib/src/material/input_decorator.dart +++ b/packages/flutter/lib/src/material/input_decorator.dart
@@ -1923,7 +1923,7 @@ InputDecoration _effectiveDecoration; InputDecoration get decoration { _effectiveDecoration ??= widget.decoration.applyDefaults( - Theme.of(context).inputDecorationTheme + Theme.of(context).inputDecorationTheme, ); return _effectiveDecoration; } @@ -2200,7 +2200,7 @@ widthFactor: 1.0, heightFactor: 1.0, child: ConstrainedBox( - constraints: const BoxConstraints( + constraints: decoration.prefixIconConstraints ?? const BoxConstraints( minWidth: kMinInteractiveDimension, minHeight: kMinInteractiveDimension, ), @@ -2219,7 +2219,7 @@ widthFactor: 1.0, heightFactor: 1.0, child: ConstrainedBox( - constraints: const BoxConstraints( + constraints: decoration.suffixIconConstraints ?? const BoxConstraints( minWidth: kMinInteractiveDimension, minHeight: kMinInteractiveDimension, ), @@ -2463,6 +2463,7 @@ this.isDense, this.contentPadding, this.prefixIcon, + this.prefixIconConstraints, this.prefix, this.prefixText, this.prefixStyle, @@ -2470,6 +2471,7 @@ this.suffix, this.suffixText, this.suffixStyle, + this.suffixIconConstraints, this.counter, this.counterText, this.counterStyle, @@ -2533,10 +2535,12 @@ prefix = null, prefixText = null, prefixStyle = null, + prefixIconConstraints = null, suffix = null, suffixIcon = null, suffixText = null, suffixStyle = null, + suffixIconConstraints = null, counter = null, counterText = null, counterStyle = null, @@ -2754,6 +2758,58 @@ /// * [suffixIcon], which is the same but on the trailing edge. final Widget prefixIcon; + /// The constraints for the prefix icon. + /// + /// This can be used to modify the [BoxConstraints] surrounding [prefixIcon]. + /// + /// This property is particularly useful for getting the decoration's height + /// less than 48px. This can be achieved by setting [isDense] to true and + /// setting the constraints' minimum height and width to a value lower than + /// 48px. + /// + /// {@tool dartpad --template=stateless_widget_scaffold} + /// This example shows the differences between two `TextField` widgets when + /// [prefixIconConstraints] is set to the default value and when one is not. + /// + /// Note that [isDense] must be set to true to be able to + /// set the constraints smaller than 48px. + /// + /// If null, [BoxConstraints] with a minimum width and height of 48px is + /// used. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Padding( + /// padding: const EdgeInsets.symmetric(horizontal: 8.0), + /// child: Column( + /// mainAxisAlignment: MainAxisAlignment.center, + /// children: <Widget>[ + /// TextField( + /// decoration: InputDecoration( + /// hintText: 'Normal Icon Constraints', + /// prefixIcon: Icon(Icons.search), + /// ), + /// ), + /// SizedBox(height: 10), + /// TextField( + /// decoration: InputDecoration( + /// isDense: true, + /// hintText:'Smaller Icon Constraints', + /// prefixIcon: Icon(Icons.search), + /// prefixIconConstraints: BoxConstraints( + /// minHeight: 32, + /// minWidth: 32, + /// ), + /// ), + /// ), + /// ], + /// ), + /// ); + /// } + /// ``` + /// {@end-tool} + final BoxConstraints prefixIconConstraints; + /// Optional widget to place on the line before the input. /// /// This can be used, for example, to add some padding to text that would @@ -2867,6 +2923,61 @@ /// * [prefixStyle], the equivalent but on the leading edge. final TextStyle suffixStyle; + /// The constraints for the suffix icon. + /// + /// This can be used to modify the [BoxConstraints] surrounding [suffixIcon]. + /// + /// This property is particularly useful for getting the decoration's height + /// less than 48px. This can be achieved by setting [isDense] to true and + /// setting the constraints' minimum height and width to a value lower than + /// 48px. + /// + /// If null, a [BoxConstraints] with a minimum width and height of 48px is + /// used. + /// + /// {@tool dartpad --template=stateless_widget_scaffold} + /// This example shows the differences between two `TextField` widgets when + /// [suffixIconConstraints] is set to the default value and when one is not. + /// + /// Note that [isDense] must be set to true to be able to + /// set the constraints smaller than 48px. + /// + /// If null, [BoxConstraints] with a minimum width and height of 48px is + /// used. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Padding( + /// padding: const EdgeInsets.symmetric(horizontal: 8.0), + /// child: Column( + /// mainAxisAlignment: MainAxisAlignment.center, + /// children: <Widget>[ + /// TextField( + /// decoration: InputDecoration( + /// hintText: 'Normal Icon Constraints', + /// suffixIcon: Icon(Icons.search), + /// ), + /// ), + /// SizedBox(height: 10), + /// TextField( + /// decoration: InputDecoration( + /// isDense: true, + /// hintText:'Smaller Icon Constraints', + /// suffixIcon: Icon(Icons.search), + /// suffixIconConstraints: BoxConstraints( + /// minHeight: 32, + /// minWidth: 32, + /// ), + /// ), + /// ), + /// ], + /// ), + /// ); + /// } + /// ``` + /// {@end-tool} + final BoxConstraints suffixIconConstraints; + /// Optional text to place below the line as a character count. /// /// Rendered using [counterStyle]. Uses [helperStyle] if [counterStyle] is @@ -3151,11 +3262,13 @@ Widget prefixIcon, Widget prefix, String prefixText, + BoxConstraints prefixIconConstraints, TextStyle prefixStyle, Widget suffixIcon, Widget suffix, String suffixText, TextStyle suffixStyle, + BoxConstraints suffixIconConstraints, Widget counter, String counterText, TextStyle counterStyle, @@ -3195,10 +3308,12 @@ prefix: prefix ?? this.prefix, prefixText: prefixText ?? this.prefixText, prefixStyle: prefixStyle ?? this.prefixStyle, + prefixIconConstraints: prefixIconConstraints ?? this.prefixIconConstraints, suffixIcon: suffixIcon ?? this.suffixIcon, suffix: suffix ?? this.suffix, suffixText: suffixText ?? this.suffixText, suffixStyle: suffixStyle ?? this.suffixStyle, + suffixIconConstraints: suffixIconConstraints ?? this.suffixIconConstraints, counter: counter ?? this.counter, counterText: counterText ?? this.counterText, counterStyle: counterStyle ?? this.counterStyle, @@ -3282,10 +3397,12 @@ && other.prefix == prefix && other.prefixText == prefixText && other.prefixStyle == prefixStyle + && other.prefixIconConstraints == prefixIconConstraints && other.suffixIcon == suffixIcon && other.suffix == suffix && other.suffixText == suffixText && other.suffixStyle == suffixStyle + && other.suffixIconConstraints == suffixIconConstraints && other.counter == counter && other.counterText == counterText && other.counterStyle == counterStyle @@ -3334,10 +3451,12 @@ prefix, prefixText, prefixStyle, + prefixIconConstraints, suffixIcon, suffix, suffixText, suffixStyle, + suffixIconConstraints, counter, counterText, counterStyle, @@ -3376,10 +3495,12 @@ if (prefix != null) 'prefix: $prefix', if (prefixText != null) 'prefixText: $prefixText', if (prefixStyle != null) 'prefixStyle: $prefixStyle', + if (prefixIconConstraints != null) 'prefixIconConstraints: $prefixIconConstraints', if (suffixIcon != null) 'suffixIcon: $suffixIcon', if (suffix != null) 'suffix: $suffix', if (suffixText != null) 'suffixText: $suffixText', if (suffixStyle != null) 'suffixStyle: $suffixStyle', + if (suffixIconConstraints != null) 'suffixIconConstraints: $suffixIconConstraints', if (counter != null) 'counter: $counter', if (counterText != null) 'counterText: $counterText', if (counterStyle != null) 'counterStyle: $counterStyle',
diff --git a/packages/flutter/test/material/input_decorator_test.dart b/packages/flutter/test/material/input_decorator_test.dart index 39f50c7..36a80b1 100644 --- a/packages/flutter/test/material/input_decorator_test.dart +++ b/packages/flutter/test/material/input_decorator_test.dart
@@ -1433,6 +1433,43 @@ expect(tester.getTopRight(find.text('text')).dx, lessThanOrEqualTo(tester.getTopLeft(find.byIcon(Icons.satellite)).dx)); }); + testWidgets('InputDecorator prefixIconConstraints/suffixIconConstraints', (WidgetTester tester) async { + await tester.pumpWidget( + buildInputDecorator( + // isEmpty: false (default) + // isFocused: false (default) + decoration: const InputDecoration( + prefixIcon: Icon(Icons.pages), + prefixIconConstraints: BoxConstraints( + minWidth: 32, + minHeight: 32, + ), + suffixIcon: Icon(Icons.satellite), + suffixIconConstraints: BoxConstraints( + minWidth: 25, + minHeight: 25, + ), + isDense: true, // has to be true to go below 48px height + ), + ), + ); + + // Overall height for this InputDecorator is 32px because the prefix icon + // is now a custom value + expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 32.0)); + expect(tester.getSize(find.text('text')).height, 16.0); + expect(tester.getSize(find.byIcon(Icons.pages)).height, 32.0); + expect(tester.getSize(find.byIcon(Icons.satellite)).height, 25.0); + + // (InputDecorator height - Text widget height) / 2 + expect(tester.getTopLeft(find.text('text')).dy, (32.0 - 16.0) / 2); + // prefixIcon should take up the entire height of InputDecorator + expect(tester.getTopLeft(find.byIcon(Icons.pages)).dy, 0.0); + // (InputDecorator height - suffixIcon height) / 2 + expect(tester.getTopLeft(find.byIcon(Icons.satellite)).dy, (32.0 - 25.0) / 2); + expect(tester.getTopRight(find.byIcon(Icons.satellite)).dx, 800.0); + }); + testWidgets('prefix/suffix icons are centered when smaller than 48 by 48', (WidgetTester tester) async { const Key prefixKey = Key('prefix'); await tester.pumpWidget(