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.
This commit is contained in:
parent
8dd06a1190
commit
ad163749b7
1
AUTHORS
1
AUTHORS
@ -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 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
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 {
|
|||||||
/// 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 {
|
|||||||
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 {
|
|||||||
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 {
|
|||||||
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 {
|
|||||||
/// 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 {
|
|||||||
/// 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 {
|
|||||||
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 {
|
|||||||
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 {
|
|||||||
&& 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 {
|
|||||||
|
|
||||||
@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 {
|
|||||||
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 {
|
|||||||
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() {
|
|||||||
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() {
|
|||||||
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(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user