mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Allow label to be used to compute InputDecorator Intrinsic width (#178101)
## Description This PR adds `InputDecorator.maintainLabelSize` (similar to `InputDecorator.maintainHintSize`) to allow the label to be used in the intrinsic width calculation (if could be used for the intrinsic height calculation later if needed). I opted for this flag (and defaulting to false) because changing the default calculation would probably break various usages. See https://github.com/flutter/flutter/issues/178099#issuecomment-3496116095 for why this change will be helpful to simplify and fix DropdownMenu implementation. ## Before The label might be cut off: <img width="126" height="71" alt="Screenshot 2025-11-05 at 20 16 43" src="https://github.com/user-attachments/assets/61d9f817-5c58-43f9-9307-976f9c124ec7" /> ## After The label is entirely visible because it is part of the intrinsic width calculation: <img width="126" height="71" alt="Screenshot 2025-11-05 at 20 16 09" src="https://github.com/user-attachments/assets/47360e17-3cde-4f05-8a6b-cc9e86644ffc" /> ## Related Issue Fixes [DropdownMenu menu panel does not close when pressing ESC and requestFocusOnTap is false](https://github.com/flutter/flutter/issues/177993) Part of https://github.com/flutter/flutter/issues/123797 ## Tests - Adds 4 tests. - Updates 1 non-related test where I spotted some nits.
This commit is contained in:
parent
767fac1b8d
commit
411566a2e2
@ -596,6 +596,7 @@ class _Decoration {
|
||||
required this.visualDensity,
|
||||
required this.inputGap,
|
||||
required this.maintainHintSize,
|
||||
required this.maintainLabelSize,
|
||||
this.icon,
|
||||
this.input,
|
||||
this.label,
|
||||
@ -622,6 +623,7 @@ class _Decoration {
|
||||
final VisualDensity visualDensity;
|
||||
final double inputGap;
|
||||
final bool maintainHintSize;
|
||||
final bool maintainLabelSize;
|
||||
final Widget? icon;
|
||||
final Widget? input;
|
||||
final Widget? label;
|
||||
@ -656,6 +658,7 @@ class _Decoration {
|
||||
other.visualDensity == visualDensity &&
|
||||
other.inputGap == inputGap &&
|
||||
other.maintainHintSize == maintainHintSize &&
|
||||
other.maintainLabelSize == maintainLabelSize &&
|
||||
other.icon == icon &&
|
||||
other.input == input &&
|
||||
other.label == label &&
|
||||
@ -683,14 +686,14 @@ class _Decoration {
|
||||
visualDensity,
|
||||
inputGap,
|
||||
maintainHintSize,
|
||||
maintainLabelSize,
|
||||
icon,
|
||||
input,
|
||||
label,
|
||||
hint,
|
||||
prefix,
|
||||
suffix,
|
||||
prefixIcon,
|
||||
Object.hash(suffixIcon, helperError, counter, container),
|
||||
Object.hash(prefixIcon, suffixIcon, helperError, counter, container),
|
||||
);
|
||||
}
|
||||
|
||||
@ -1212,9 +1215,12 @@ class _RenderDecoration extends RenderBox
|
||||
|
||||
@override
|
||||
double computeMinIntrinsicWidth(double height) {
|
||||
final double contentWidth = decoration.isEmpty || decoration.maintainHintSize
|
||||
final double inputWidth = decoration.isEmpty || decoration.maintainHintSize
|
||||
? math.max(_minWidth(input, height), _minWidth(hint, height))
|
||||
: _minWidth(input, height);
|
||||
final double contentWidth = decoration.maintainLabelSize
|
||||
? math.max(inputWidth, _minWidth(label, height))
|
||||
: inputWidth;
|
||||
return _minWidth(icon, height) +
|
||||
(prefixIcon != null ? prefixToInputGap : contentPadding.start + decoration.inputGap) +
|
||||
_minWidth(prefixIcon, height) +
|
||||
@ -1227,9 +1233,12 @@ class _RenderDecoration extends RenderBox
|
||||
|
||||
@override
|
||||
double computeMaxIntrinsicWidth(double height) {
|
||||
final double contentWidth = decoration.isEmpty || decoration.maintainHintSize
|
||||
final double inputWidth = decoration.isEmpty || decoration.maintainHintSize
|
||||
? math.max(_maxWidth(input, height), _maxWidth(hint, height))
|
||||
: _maxWidth(input, height);
|
||||
final double contentWidth = decoration.maintainLabelSize
|
||||
? math.max(inputWidth, _maxWidth(label, height))
|
||||
: inputWidth;
|
||||
return _maxWidth(icon, height) +
|
||||
(prefixIcon != null ? prefixToInputGap : contentPadding.start + decoration.inputGap) +
|
||||
_maxWidth(prefixIcon, height) +
|
||||
@ -2651,6 +2660,7 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
|
||||
isEmpty: isEmpty,
|
||||
visualDensity: visualDensity,
|
||||
maintainHintSize: maintainHintSize,
|
||||
maintainLabelSize: decoration.maintainLabelSize,
|
||||
icon: icon,
|
||||
input: input,
|
||||
label: label,
|
||||
@ -2787,6 +2797,7 @@ class InputDecoration {
|
||||
)
|
||||
this.maintainHintHeight = true,
|
||||
this.maintainHintSize = true,
|
||||
this.maintainLabelSize = false,
|
||||
this.error,
|
||||
this.errorText,
|
||||
this.errorStyle,
|
||||
@ -2884,6 +2895,7 @@ class InputDecoration {
|
||||
)
|
||||
this.maintainHintHeight = true,
|
||||
this.maintainHintSize = true,
|
||||
this.maintainLabelSize = false,
|
||||
this.filled = false,
|
||||
this.fillColor,
|
||||
this.focusColor,
|
||||
@ -3174,14 +3186,23 @@ class InputDecoration {
|
||||
final bool maintainHintHeight;
|
||||
|
||||
/// Whether the input field's size should always be greater than or equal to
|
||||
/// the size of the [hintText], even if the [hintText] is not visible.
|
||||
/// the size of the [hint] or [hintText], even if the [hint] or [hintText] are not visible.
|
||||
///
|
||||
/// The [InputDecorator] widget ignores [hintText] during layout when
|
||||
/// it's not visible, if this flag is set to false.
|
||||
/// The [InputDecorator] widget ignores [hint] and [hintText] during layout when
|
||||
/// they are not visible, if this flag is set to false.
|
||||
///
|
||||
/// Defaults to true.
|
||||
final bool maintainHintSize;
|
||||
|
||||
/// Whether the input field's size should always be greater than or equal to
|
||||
/// the size of the [label] or [labelText], even if the [label] or [labelText] are not visible.
|
||||
///
|
||||
/// The [InputDecorator] widget ignores [label] and [labelText] during layout when
|
||||
/// this flag is set to false.
|
||||
///
|
||||
/// Defaults to false for compatibility reason.
|
||||
final bool maintainLabelSize;
|
||||
|
||||
/// 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.
|
||||
@ -3893,6 +3914,7 @@ class InputDecoration {
|
||||
int? hintMaxLines,
|
||||
bool? maintainHintHeight,
|
||||
bool? maintainHintSize,
|
||||
bool? maintainLabelSize,
|
||||
Widget? error,
|
||||
String? errorText,
|
||||
TextStyle? errorStyle,
|
||||
@ -3953,6 +3975,7 @@ class InputDecoration {
|
||||
hintFadeDuration: hintFadeDuration ?? this.hintFadeDuration,
|
||||
maintainHintHeight: maintainHintHeight ?? this.maintainHintHeight,
|
||||
maintainHintSize: maintainHintSize ?? this.maintainHintSize,
|
||||
maintainLabelSize: maintainLabelSize ?? this.maintainLabelSize,
|
||||
error: error ?? this.error,
|
||||
errorText: errorText ?? this.errorText,
|
||||
errorStyle: errorStyle ?? this.errorStyle,
|
||||
@ -4077,6 +4100,7 @@ class InputDecoration {
|
||||
other.hintFadeDuration == hintFadeDuration &&
|
||||
other.maintainHintHeight == maintainHintHeight &&
|
||||
other.maintainHintSize == maintainHintSize &&
|
||||
other.maintainLabelSize == maintainLabelSize &&
|
||||
other.error == error &&
|
||||
other.errorText == errorText &&
|
||||
other.errorStyle == errorStyle &&
|
||||
@ -4139,6 +4163,7 @@ class InputDecoration {
|
||||
hintFadeDuration,
|
||||
maintainHintHeight,
|
||||
maintainHintSize,
|
||||
maintainLabelSize,
|
||||
error,
|
||||
errorText,
|
||||
errorStyle,
|
||||
@ -4199,6 +4224,7 @@ class InputDecoration {
|
||||
if (hintFadeDuration != null) 'hintFadeDuration: "$hintFadeDuration"',
|
||||
if (!maintainHintHeight) 'maintainHintHeight: false',
|
||||
if (!maintainHintSize) 'maintainHintSize: false',
|
||||
if (maintainLabelSize) 'maintainLabelSize: true',
|
||||
if (error != null) 'error: "$error"',
|
||||
if (errorText != null) 'errorText: "$errorText"',
|
||||
if (errorStyle != null) 'errorStyle: "$errorStyle"',
|
||||
|
||||
@ -9411,26 +9411,115 @@ void main() {
|
||||
);
|
||||
|
||||
// Regression test for https://github.com/flutter/flutter/issues/93337.
|
||||
testWidgets('depends on hint width when decorator is not empty and maintainHintSize is true', (
|
||||
WidgetTester tester,
|
||||
) async {
|
||||
const InputDecoration decorationWithHint = InputDecoration(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
hintText: 'Hint',
|
||||
);
|
||||
const double contentWidth = 20.0;
|
||||
|
||||
await tester.pumpWidget(
|
||||
buildInputDecorator(
|
||||
decoration: decorationWithHint,
|
||||
useIntrinsicWidth: true,
|
||||
child: const SizedBox(width: contentWidth),
|
||||
),
|
||||
);
|
||||
|
||||
const double hintTextWidth = 66.0;
|
||||
expect(getDecoratorRect(tester).width, hintTextWidth);
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'depends on content width when decorator is not empty and maintainHintSize is true',
|
||||
'does not depend on label width when decorator is empty and maintainLabelSize is false',
|
||||
(WidgetTester tester) async {
|
||||
const InputDecoration decorationWithHint = InputDecoration(
|
||||
const double labelWidth = 30;
|
||||
const InputDecoration decorationWithLabel = InputDecoration(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
hintText: 'Hint',
|
||||
label: SizedBox(width: labelWidth),
|
||||
);
|
||||
const double contentWidth = 20.0;
|
||||
|
||||
await tester.pumpWidget(
|
||||
buildInputDecorator(
|
||||
decoration: decorationWithHint,
|
||||
decoration: decorationWithLabel,
|
||||
useIntrinsicWidth: true,
|
||||
isEmpty: true,
|
||||
child: const SizedBox.shrink(),
|
||||
),
|
||||
);
|
||||
|
||||
// The label width is ignored even if larger than the content width.
|
||||
expect(getDecoratorRect(tester).width, 0);
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets('depends on label width when decorator is empty and maintainLabelSize is true', (
|
||||
WidgetTester tester,
|
||||
) async {
|
||||
const double labelWidth = 30;
|
||||
const InputDecoration decorationWithLabel = InputDecoration(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
label: SizedBox(width: labelWidth),
|
||||
maintainLabelSize: true,
|
||||
);
|
||||
|
||||
await tester.pumpWidget(
|
||||
buildInputDecorator(
|
||||
decoration: decorationWithLabel,
|
||||
useIntrinsicWidth: true,
|
||||
isEmpty: true,
|
||||
child: const SizedBox.shrink(),
|
||||
),
|
||||
);
|
||||
|
||||
expect(getDecoratorRect(tester).width, labelWidth);
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'does not depend on label width when decorator is not empty and maintainLabelSize is false',
|
||||
(WidgetTester tester) async {
|
||||
const double contentWidth = 20.0;
|
||||
const double labelWidth = 30;
|
||||
const InputDecoration decorationWithLabel = InputDecoration(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
label: SizedBox(width: labelWidth),
|
||||
);
|
||||
|
||||
await tester.pumpWidget(
|
||||
buildInputDecorator(
|
||||
decoration: decorationWithLabel,
|
||||
useIntrinsicWidth: true,
|
||||
child: const SizedBox(width: contentWidth),
|
||||
),
|
||||
);
|
||||
|
||||
// The hint width is ignored even if larger than the content width.
|
||||
const double hintTextWidth = 66.0;
|
||||
expect(getDecoratorRect(tester).width, hintTextWidth);
|
||||
// The label width is ignored even if larger than the content width.
|
||||
expect(getDecoratorRect(tester).width, contentWidth);
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets(
|
||||
'depends on label width when decorator is not empty and maintainLabelSize is true',
|
||||
(WidgetTester tester) async {
|
||||
const double contentWidth = 20.0;
|
||||
const double labelWidth = 30;
|
||||
const InputDecoration decorationWithLabel = InputDecoration(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
label: SizedBox(width: labelWidth),
|
||||
maintainLabelSize: true,
|
||||
);
|
||||
|
||||
await tester.pumpWidget(
|
||||
buildInputDecorator(
|
||||
decoration: decorationWithLabel,
|
||||
useIntrinsicWidth: true,
|
||||
child: const SizedBox(width: contentWidth),
|
||||
),
|
||||
);
|
||||
|
||||
expect(getDecoratorRect(tester).width, labelWidth);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user