Commit ad163749 authored by Christian Mürtz's avatar Christian Mürtz Committed by Hans Muller

Extend InputDecoration with prefix/suffix padding (#19514)

Add widget valued prefix and suffix properties to InputDecoration.  These new properties are alternatives to prefixText and suffixText.
parent 8dd06a11
...@@ -24,4 +24,5 @@ Tetsuhiro Ueda <najeira@gmail.com> ...@@ -24,4 +24,5 @@ Tetsuhiro Ueda <najeira@gmail.com>
Dan Field <dfield@gmail.com> Dan Field <dfield@gmail.com>
Noah Groß <gross@ngsger.de> Noah Groß <gross@ngsger.de>
Victor Choueiri <victor@ctrlanddev.com> Victor Choueiri <victor@ctrlanddev.com>
Christian Mürtz <teraarts@t-online.de>
Lukasz Piliszczuk <lukasz@intheloup.io> Lukasz Piliszczuk <lukasz@intheloup.io>
...@@ -1376,6 +1376,33 @@ class _Decorator extends RenderObjectWidget { ...@@ -1376,6 +1376,33 @@ class _Decorator extends RenderObjectWidget {
} }
} }
class _AffixText extends StatelessWidget {
const _AffixText({
this.labelIsFloating,
this.text,
this.style,
this.child
});
final bool labelIsFloating;
final String text;
final TextStyle style;
final Widget child;
@override
Widget build(BuildContext context) {
return DefaultTextStyle.merge(
style: style,
child: new AnimatedOpacity(
duration: _kTransitionDuration,
curve: _kTransitionCurve,
opacity: labelIsFloating ? 1.0 : 0.0,
child: child ?? new Text(text, style: style,),
),
);
}
}
/// Defines the appearance of a Material Design text field. /// Defines the appearance of a Material Design text field.
/// ///
/// [InputDecorator] displays the visual elements of a Material Design text /// [InputDecorator] displays the visual elements of a Material Design text
...@@ -1711,26 +1738,20 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat ...@@ -1711,26 +1738,20 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
), ),
); );
final Widget prefix = decoration.prefixText == null ? null : final Widget prefix = decoration.prefix == null && decoration.prefixText == null ? null :
new AnimatedOpacity( new _AffixText(
duration: _kTransitionDuration, labelIsFloating: widget._labelIsFloating,
curve: _kTransitionCurve, text: decoration.prefixText,
opacity: widget._labelIsFloating ? 1.0 : 0.0, style: decoration.prefixStyle ?? hintStyle,
child: new Text( child: decoration.prefix,
decoration.prefixText,
style: decoration.prefixStyle ?? hintStyle
),
); );
final Widget suffix = decoration.suffixText == null ? null : final Widget suffix = decoration.suffix == null && decoration.suffixText == null ? null :
new AnimatedOpacity( new _AffixText(
duration: _kTransitionDuration, labelIsFloating: widget._labelIsFloating,
curve: _kTransitionCurve, text: decoration.suffixText,
opacity: widget._labelIsFloating ? 1.0 : 0.0, style: decoration.suffixStyle ?? hintStyle,
child: new Text( child: decoration.suffix,
decoration.suffixText,
style: decoration.suffixStyle ?? hintStyle
),
); );
final Color activeColor = _getActiveColor(themeData); final Color activeColor = _getActiveColor(themeData);
...@@ -1884,6 +1905,9 @@ class InputDecoration { ...@@ -1884,6 +1905,9 @@ class InputDecoration {
/// no border is drawn. /// no border is drawn.
/// ///
/// The [enabled] argument must not be null. /// The [enabled] argument must not be null.
///
/// Only [prefix] or [prefixText] can be specified.
/// The same applies for [suffix] and [suffixText].
const InputDecoration({ const InputDecoration({
this.icon, this.icon,
this.labelText, this.labelText,
...@@ -1898,10 +1922,12 @@ class InputDecoration { ...@@ -1898,10 +1922,12 @@ class InputDecoration {
this.isDense, this.isDense,
this.contentPadding, this.contentPadding,
this.prefixIcon, this.prefixIcon,
this.prefix,
this.prefixText, this.prefixText,
this.prefixStyle, this.prefixStyle,
this.suffixText,
this.suffixIcon, this.suffixIcon,
this.suffix,
this.suffixText,
this.suffixStyle, this.suffixStyle,
this.counterText, this.counterText,
this.counterStyle, this.counterStyle,
...@@ -1914,7 +1940,10 @@ class InputDecoration { ...@@ -1914,7 +1940,10 @@ class InputDecoration {
this.enabledBorder, this.enabledBorder,
this.border, this.border,
this.enabled = true, this.enabled = true,
}) : assert(enabled != null), isCollapsed = false; }) : assert(enabled != null),
assert(!(prefix != null && prefixText != null), 'Declaring both prefix and prefixText is not allowed'),
assert(!(suffix != null && suffixText != null), 'Declaring both suffix and suffixText is not allowed'),
isCollapsed = false;
/// Defines an [InputDecorator] that is the same size as the input field. /// Defines an [InputDecorator] that is the same size as the input field.
/// ///
...@@ -1941,8 +1970,10 @@ class InputDecoration { ...@@ -1941,8 +1970,10 @@ class InputDecoration {
contentPadding = EdgeInsets.zero, contentPadding = EdgeInsets.zero,
isCollapsed = true, isCollapsed = true,
prefixIcon = null, prefixIcon = null,
prefix = null,
prefixText = null, prefixText = null,
prefixStyle = null, prefixStyle = null,
suffix = null,
suffixIcon = null, suffixIcon = null,
suffixText = null, suffixText = null,
suffixStyle = null, suffixStyle = null,
...@@ -2096,6 +2127,14 @@ class InputDecoration { ...@@ -2096,6 +2127,14 @@ class InputDecoration {
/// See [Icon], [ImageIcon]. /// See [Icon], [ImageIcon].
final Widget prefixIcon; final Widget prefixIcon;
/// Optional widget to place on the line before the input.
/// Can be used to add some padding to the [prefixText] or to
/// add a custom widget in front of the input. The widget's baseline
/// is lined up with the input baseline.
///
/// Only one of [prefix] and [prefixText] can be specified.
final Widget prefix;
/// Optional text prefix to place on the line before the input. /// Optional text prefix to place on the line before the input.
/// ///
/// Uses the [prefixStyle]. Uses [hintStyle] if [prefixStyle] isn't /// Uses the [prefixStyle]. Uses [hintStyle] if [prefixStyle] isn't
...@@ -2135,6 +2174,14 @@ class InputDecoration { ...@@ -2135,6 +2174,14 @@ class InputDecoration {
/// See [Icon], [ImageIcon]. /// See [Icon], [ImageIcon].
final Widget suffixIcon; final Widget suffixIcon;
/// Optional widget to place on the line after the input.
/// Can be used to add some padding to the [suffixText] or to
/// add a custom widget after the input. The widget's baseline
/// is lined up with the input baseline.
///
/// Only one of [suffix] and [suffixText] can be specified.
final Widget suffix;
/// Optional text suffix to place on the line after the input. /// Optional text suffix to place on the line after the input.
/// ///
/// Uses the [suffixStyle]. Uses [hintStyle] if [suffixStyle] isn't /// Uses the [suffixStyle]. Uses [hintStyle] if [suffixStyle] isn't
...@@ -2351,9 +2398,11 @@ class InputDecoration { ...@@ -2351,9 +2398,11 @@ class InputDecoration {
bool isDense, bool isDense,
EdgeInsetsGeometry contentPadding, EdgeInsetsGeometry contentPadding,
Widget prefixIcon, Widget prefixIcon,
Widget prefix,
String prefixText, String prefixText,
TextStyle prefixStyle, TextStyle prefixStyle,
Widget suffixIcon, Widget suffixIcon,
Widget suffix,
String suffixText, String suffixText,
TextStyle suffixStyle, TextStyle suffixStyle,
String counterText, String counterText,
...@@ -2382,9 +2431,11 @@ class InputDecoration { ...@@ -2382,9 +2431,11 @@ class InputDecoration {
isDense: isDense ?? this.isDense, isDense: isDense ?? this.isDense,
contentPadding: contentPadding ?? this.contentPadding, contentPadding: contentPadding ?? this.contentPadding,
prefixIcon: prefixIcon ?? this.prefixIcon, prefixIcon: prefixIcon ?? this.prefixIcon,
prefix: prefix ?? this.prefix,
prefixText: prefixText ?? this.prefixText, prefixText: prefixText ?? this.prefixText,
prefixStyle: prefixStyle ?? this.prefixStyle, prefixStyle: prefixStyle ?? this.prefixStyle,
suffixIcon: suffixIcon ?? this.suffixIcon, suffixIcon: suffixIcon ?? this.suffixIcon,
suffix: suffix ?? this.suffix,
suffixText: suffixText ?? this.suffixText, suffixText: suffixText ?? this.suffixText,
suffixStyle: suffixStyle ?? this.suffixStyle, suffixStyle: suffixStyle ?? this.suffixStyle,
counterText: counterText ?? this.counterText, counterText: counterText ?? this.counterText,
...@@ -2450,9 +2501,11 @@ class InputDecoration { ...@@ -2450,9 +2501,11 @@ class InputDecoration {
&& typedOther.contentPadding == contentPadding && typedOther.contentPadding == contentPadding
&& typedOther.isCollapsed == isCollapsed && typedOther.isCollapsed == isCollapsed
&& typedOther.prefixIcon == prefixIcon && typedOther.prefixIcon == prefixIcon
&& typedOther.prefix == prefix
&& typedOther.prefixText == prefixText && typedOther.prefixText == prefixText
&& typedOther.prefixStyle == prefixStyle && typedOther.prefixStyle == prefixStyle
&& typedOther.suffixIcon == suffixIcon && typedOther.suffixIcon == suffixIcon
&& typedOther.suffix == suffix
&& typedOther.suffixText == suffixText && typedOther.suffixText == suffixText
&& typedOther.suffixStyle == suffixStyle && typedOther.suffixStyle == suffixStyle
&& typedOther.counterText == counterText && typedOther.counterText == counterText
...@@ -2470,6 +2523,8 @@ class InputDecoration { ...@@ -2470,6 +2523,8 @@ class InputDecoration {
@override @override
int get hashCode { int get hashCode {
// Split into multiple hashValues calls
// because the hashValues function is limited to 20 parameters.
return hashValues( return hashValues(
icon, icon,
labelText, labelText,
...@@ -2482,14 +2537,22 @@ class InputDecoration { ...@@ -2482,14 +2537,22 @@ class InputDecoration {
errorStyle, errorStyle,
errorMaxLines, errorMaxLines,
isDense, isDense,
hashValues( // Over 20 fields... hashValues(
contentPadding, contentPadding,
isCollapsed, isCollapsed,
filled,
fillColor,
border,
enabled,
prefixIcon, prefixIcon,
prefix,
prefixText, prefixText,
prefixStyle, prefixStyle,
suffixIcon, suffixIcon,
suffix,
suffixText, suffixText,
),
hashValues(
suffixStyle, suffixStyle,
counterText, counterText,
counterStyle, counterStyle,
...@@ -2531,12 +2594,16 @@ class InputDecoration { ...@@ -2531,12 +2594,16 @@ class InputDecoration {
description.add('isCollapsed: $isCollapsed'); description.add('isCollapsed: $isCollapsed');
if (prefixIcon != null) if (prefixIcon != null)
description.add('prefixIcon: $prefixIcon'); description.add('prefixIcon: $prefixIcon');
if (prefix != null)
description.add('prefix: $prefix');
if (prefixText != null) if (prefixText != null)
description.add('prefixText: $prefixText'); description.add('prefixText: $prefixText');
if (prefixStyle != null) if (prefixStyle != null)
description.add('prefixStyle: $prefixStyle'); description.add('prefixStyle: $prefixStyle');
if (suffixIcon != null) if (suffixIcon != null)
description.add('suffixIcon: $suffixIcon'); description.add('suffixIcon: $suffixIcon');
if (suffix != null)
description.add('suffix: $suffix');
if (suffixText != null) if (suffixText != null)
description.add('suffixText: $suffixText'); description.add('suffixText: $suffixText');
if (suffixStyle != null) if (suffixStyle != null)
......
...@@ -678,7 +678,7 @@ void main() { ...@@ -678,7 +678,7 @@ void main() {
expect(tester.getBottomLeft(find.text(kError1)), const Offset(12.0, 76.0)); expect(tester.getBottomLeft(find.text(kError1)), const Offset(12.0, 76.0));
}); });
testWidgets('InputDecorator prefix/suffix', (WidgetTester tester) async { testWidgets('InputDecorator prefix/suffix texts', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
buildInputDecorator( buildInputDecorator(
// isEmpty: false (default) // isEmpty: false (default)
...@@ -754,6 +754,57 @@ void main() { ...@@ -754,6 +754,57 @@ void main() {
expect(tester.getTopRight(find.text('text')).dx, lessThanOrEqualTo(tester.getTopLeft(find.text('s')).dx)); expect(tester.getTopRight(find.text('text')).dx, lessThanOrEqualTo(tester.getTopLeft(find.text('s')).dx));
}); });
testWidgets('InputDecorator prefix/suffix widgets', (WidgetTester tester) async {
const Key pKey = Key('p');
const Key sKey = Key('s');
await tester.pumpWidget(
buildInputDecorator(
// isEmpty: false (default)
// isFocused: false (default)
decoration: const InputDecoration(
prefix: Padding(
key: pKey,
padding: EdgeInsets.all(4.0),
child: Text('p'),
),
suffix: Padding(
key: sKey,
padding: EdgeInsets.all(4.0),
child: Text('s'),
),
filled: true,
),
),
);
// Overall height for this InputDecorator is 48dps because
// the prefix and the suffix widget is surrounded with padding:
// 12 - top padding
// 4 - top prefix/suffix padding
// 16 - input text (ahem font size 16dps)
// 4 - bottom prefix/suffix padding
// 12 - bottom padding
expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 48.0));
expect(tester.getSize(find.text('text')).height, 16.0);
expect(tester.getSize(find.byKey(pKey)).height, 24.0);
expect(tester.getSize(find.text('p')).height, 16.0);
expect(tester.getSize(find.byKey(sKey)).height, 24.0);
expect(tester.getSize(find.text('s')).height, 16.0);
expect(tester.getTopLeft(find.text('text')).dy, 16.0);
expect(tester.getTopLeft(find.byKey(pKey)).dy, 12.0);
expect(tester.getTopLeft(find.text('p')).dy, 16.0);
expect(tester.getTopLeft(find.byKey(sKey)).dy, 12.0);
expect(tester.getTopLeft(find.text('s')).dy, 16.0);
expect(tester.getTopRight(find.byKey(sKey)).dx, 788.0);
expect(tester.getTopRight(find.text('s')).dx, 784.0);
// layout is a row: [prefix text suffix]
expect(tester.getTopLeft(find.byKey(pKey)).dx, 12.0);
expect(tester.getTopRight(find.byKey(pKey)).dx, tester.getTopLeft(find.text('text')).dx);
expect(tester.getTopRight(find.text('text')).dx, lessThanOrEqualTo(tester.getTopRight(find.byKey(sKey)).dx));
});
testWidgets('InputDecorator prefixIcon/suffixIcon', (WidgetTester tester) async { testWidgets('InputDecorator prefixIcon/suffixIcon', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
buildInputDecorator( buildInputDecorator(
......
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