这是indexloc提供的服务,不要输入任何密码
Skip to content

Fix broken text field with set hint and min and max lines(#153183) #153235

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Sep 26, 2024
49 changes: 41 additions & 8 deletions packages/flutter/lib/src/material/input_decorator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2163,20 +2163,37 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat

final TextStyle hintStyle = _getInlineHintStyle(themeData, defaults);
final String? hintText = decoration.hintText;
final Widget? hint = hintText == null ? null : AnimatedOpacity(
opacity: (isEmpty && !_hasInlineLabel) ? 1.0 : 0.0,
duration: decoration.hintFadeDuration ?? _kHintFadeTransitionDuration,
curve: _kTransitionCurve,
child: Text(
final bool maintainHintHeight = decoration.maintainHintHeight;
Widget? hint;
if (hintText != null) {
final bool showHint = isEmpty && !_hasInlineLabel;
final Text hintTextWidget = Text(
hintText,
style: hintStyle,
textDirection: decoration.hintTextDirection,
overflow: hintStyle.overflow ?? (decoration.hintMaxLines == null ? null : TextOverflow.ellipsis),
textAlign: textAlign,
maxLines: decoration.hintMaxLines,
),
);

);
hint = maintainHintHeight ? AnimatedOpacity(
opacity: showHint ? 1.0 : 0.0,
duration: decoration.hintFadeDuration ?? _kHintFadeTransitionDuration,
curve: _kTransitionCurve,
child: hintTextWidget,
) : AnimatedSwitcher(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The animation logic seems complicated. I don't know what the expectation is here, but we probably want to test any non-trivial behavior. (Consider AnimationSheetBuilder)

Copy link
Contributor Author

@zeqinjie zeqinjie Sep 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dkwingsmt Sorry, I don't know much about the AnimationSheetBuilder component, so I refer to testWidgets('InputDecoration.collapsed accepts hintFadeDuration') to write tests

duration: decoration.hintFadeDuration ?? _kHintFadeTransitionDuration,
transitionBuilder: (Widget child, Animation<double> animation) {
return FadeTransition(
opacity: CurvedAnimation(
parent: animation,
curve: _kTransitionCurve,
),
child: child,
);
},
child: showHint ? hintTextWidget : const SizedBox.shrink(),
);
}
InputBorder? border;
if (!decoration.enabled) {
border = _hasError ? decoration.errorBorder : decoration.disabledBorder;
Expand Down Expand Up @@ -2574,6 +2591,7 @@ class InputDecoration {
this.hintTextDirection,
this.hintMaxLines,
this.hintFadeDuration,
this.maintainHintHeight = true,
this.error,
this.errorText,
this.errorStyle,
Expand Down Expand Up @@ -2643,6 +2661,7 @@ class InputDecoration {
this.hintTextDirection,
this.hintMaxLines,
this.hintFadeDuration,
this.maintainHintHeight = true,
this.filled = false,
this.fillColor,
this.focusColor,
Expand Down Expand Up @@ -2904,6 +2923,15 @@ class InputDecoration {
/// If [InputDecorationTheme.hintFadeDuration] is null defaults to 20ms.
final Duration? hintFadeDuration;

/// Whether the input field's height should always be greater than or equal to
/// the height of the [hintText], even if the [hintText] is not visible.
///
/// The [InputDecorator] widget ignores [hintText] during layout when
/// it's not visible, if this flag is set to false.
///
/// Defaults to true.
final bool maintainHintHeight;

/// Optional widget that appears below the [InputDecorator.child] and the border.
///
/// If non-null, the border's color animates to red and the [helperText] is not shown.
Expand Down Expand Up @@ -3578,6 +3606,7 @@ class InputDecoration {
TextDirection? hintTextDirection,
Duration? hintFadeDuration,
int? hintMaxLines,
bool? maintainHintHeight,
Widget? error,
String? errorText,
TextStyle? errorStyle,
Expand Down Expand Up @@ -3633,6 +3662,7 @@ class InputDecoration {
hintTextDirection: hintTextDirection ?? this.hintTextDirection,
hintMaxLines: hintMaxLines ?? this.hintMaxLines,
hintFadeDuration: hintFadeDuration ?? this.hintFadeDuration,
maintainHintHeight: maintainHintHeight ?? this.maintainHintHeight,
error: error ?? this.error,
errorText: errorText ?? this.errorText,
errorStyle: errorStyle ?? this.errorStyle,
Expand Down Expand Up @@ -3741,6 +3771,7 @@ class InputDecoration {
&& other.hintTextDirection == hintTextDirection
&& other.hintMaxLines == hintMaxLines
&& other.hintFadeDuration == hintFadeDuration
&& other.maintainHintHeight == maintainHintHeight
&& other.error == error
&& other.errorText == errorText
&& other.errorStyle == errorStyle
Expand Down Expand Up @@ -3799,6 +3830,7 @@ class InputDecoration {
hintTextDirection,
hintMaxLines,
hintFadeDuration,
maintainHintHeight,
error,
errorText,
errorStyle,
Expand Down Expand Up @@ -3855,6 +3887,7 @@ class InputDecoration {
if (hintText != null) 'hintText: "$hintText"',
if (hintMaxLines != null) 'hintMaxLines: "$hintMaxLines"',
if (hintFadeDuration != null) 'hintFadeDuration: "$hintFadeDuration"',
if (!maintainHintHeight) 'maintainHintHeight: false',
if (error != null) 'error: "$error"',
if (errorText != null) 'errorText: "$errorText"',
if (errorStyle != null) 'errorStyle: "$errorStyle"',
Expand Down
114 changes: 114 additions & 0 deletions packages/flutter/test/material/input_decorator_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4505,6 +4505,120 @@ void main() {
final Text hintTextWidget = tester.widget(hintTextFinder);
expect(hintTextWidget.style!.overflow, decoration.hintStyle!.overflow);
});

testWidgets('Widget height collapses from hint height when maintainHintHeight is false', (WidgetTester tester) async {
final String hintText = 'hint' * 20;
final InputDecoration decoration = InputDecoration(
hintText: hintText,
hintMaxLines: 3,
maintainHintHeight: false,
);

await tester.pumpWidget(
buildInputDecorator(
decoration: decoration,
),
);
expect(tester.getSize(find.byType(InputDecorator)).height, 48.0);
});

testWidgets('Widget height stays at hint height by default', (WidgetTester tester) async {
final String hintText = 'hint' * 20;
final InputDecoration decoration = InputDecoration(
hintMaxLines: 3,
hintText: hintText,
);

await tester.pumpWidget(
buildInputDecorator(
decoration: decoration,
),
);
final double hintHeight = tester.getSize(find.text(hintText)).height;
final double inputHeight = tester.getSize(find.byType(InputDecorator)).height;
expect(inputHeight, hintHeight + 16.0);
});

testWidgets('hintFadeDuration applies to hint fade-in when maintainHintHeight is false', (WidgetTester tester) async {
const InputDecoration decoration = InputDecoration(
hintText: hintText,
hintMaxLines: 3,
hintFadeDuration: Duration(milliseconds: 120),
maintainHintHeight: false,
);

// Build once with empty content.
await tester.pumpWidget(
buildInputDecorator(
decoration: decoration,
),
);

// Hint is not exist.
expect(find.text(hintText), findsNothing);

// Rebuild with empty content.
await tester.pumpWidget(
buildInputDecorator(
isEmpty: true,
decoration: decoration,
),
);

// The hint's opacity animates from 0.0 to 1.0.
// The animation's default duration is 20ms.
await tester.pump(const Duration(milliseconds: 50));
final double hintOpacity50ms = getHintOpacity(tester);
expect(hintOpacity50ms, inExclusiveRange(0.0, 1.0));
await tester.pump(const Duration(milliseconds: 50));
final double hintOpacity100ms = getHintOpacity(tester);
expect(hintOpacity100ms, inExclusiveRange(hintOpacity50ms, 1.0));
await tester.pump(const Duration(milliseconds: 20));
final double hintOpacity120ms = getHintOpacity(tester);
expect(hintOpacity120ms, 1.0);
});

testWidgets('hintFadeDuration applies to hint fade-out when maintainHintHeight is false', (WidgetTester tester) async {
const InputDecoration decoration = InputDecoration(
hintText: hintText,
hintMaxLines: 3,
hintFadeDuration: Duration(milliseconds: 120),
maintainHintHeight: false,
);

// Build once with empty content.
await tester.pumpWidget(
buildInputDecorator(
isEmpty: true,
decoration: decoration,
),
);

// Hint is visible (opacity 1.0).
expect(getHintOpacity(tester), 1.0);

// Rebuild with non-empty content.
await tester.pumpWidget(
buildInputDecorator(
decoration: decoration,
),
);

// The hint's opacity animates from 1.0 to 0.0.
// The animation's default duration is 20ms.
await tester.pump(const Duration(milliseconds: 50));
final double hintOpacity50ms = getHintOpacity(tester);
expect(hintOpacity50ms, inExclusiveRange(0.0, 1.0));
await tester.pump(const Duration(milliseconds: 50));
final double hintOpacity100ms = getHintOpacity(tester);
expect(hintOpacity100ms, inExclusiveRange(0.0, hintOpacity50ms));
await tester.pump(const Duration(milliseconds: 20));
final double hintOpacity120ms = getHintOpacity(tester);
expect(hintOpacity120ms, 0);
await tester.pump(const Duration(milliseconds: 1));
// The hintText replaced with SizeBox.
expect(find.text(hintText), findsNothing);
});
});

group('Material3 - InputDecoration helper/counter/error', () {
Expand Down