Fix InputDecorator label position ignore visual density (#146488)

## Description

This PRs makes the label vertical position depend on visual density for filled text field.
Previously, for M2 and M3, the label vertical offset was always the same (12 on M2, 8 and M3) despite different visual density configuration.
This was noticable on desktop and can lead to weird rendering especially on M3 where line height makes the cursor taller.

Screenshots for a filled text field:

| | Before | After |
|--------|--------|--------|
|M3 macOs| ![image](https://github.com/flutter/flutter/assets/840911/bd9bdb6e-477c-4db0-ae8f-74e18d19f29e) | ![image](https://github.com/flutter/flutter/assets/840911/25e59c44-0139-4813-be28-472302d6970e) | 
|M2 macOs| ![image](https://github.com/flutter/flutter/assets/840911/1c52493b-b17b-407b-93cc-69120207b716) | ![image](https://github.com/flutter/flutter/assets/840911/1fc4a8b6-429b-476c-b5bf-ff2934bf5293) | 

</details> 

## Related Issue

Fixes https://github.com/flutter/flutter/issues/141354

## Tests

Adds 2 tests, updates 2 tests.
This commit is contained in:
Bruno Leroux 2024-04-09 23:11:08 +02:00 committed by GitHub
parent 207e78ec9d
commit 906ce36b8d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 159 additions and 9 deletions

View File

@ -1451,7 +1451,9 @@ class _RenderDecoration extends RenderBox with SlottedContainerRenderObjectMixin
final bool isOutlineBorder = decoration.border.isOutline;
// Temporary opt-in fix for https://github.com/flutter/flutter/issues/54028
// Center the scaled label relative to the border.
final double floatingY = isOutlineBorder ? (-labelHeight * _kFinalLabelScale) / 2.0 + borderWeight / 2.0 : contentPadding.top;
final double outlinedFloatingY = (-labelHeight * _kFinalLabelScale) / 2.0 + borderWeight / 2.0;
final Offset densityOffset = decoration.visualDensity.baseSizeAdjustment;
final double floatingY = isOutlineBorder ? outlinedFloatingY : contentPadding.top + densityOffset.dy / 2;
final double scale = lerpDouble(1.0, _kFinalLabelScale, t)!;
final double centeredFloatX = _boxParentData(container!).offset.dx +
_boxSize(container).width / 2.0 - floatWidth / 2.0;

View File

@ -1901,6 +1901,154 @@ void main() {
// labelY = -floatingLabelHeight/2 + borderWidth/2
expect(getLabelRect(tester).top, -4.0);
});
testWidgets('InputDecorator respects reduced theme visualDensity', (WidgetTester tester) async {
await tester.pumpWidget(
buildInputDecorator(
isEmpty: true,
visualDensity: VisualDensity.compact,
decoration: const InputDecoration(
labelText: labelText,
hintText: hintText,
),
),
);
// Overall height for this InputDecorator is 48dp:
// 4 - top padding (8 minus 4 due to reduced visual density)
// 12 - floating label (font size = 16 * 0.75, line height is forced to 1.0)
// 4 - gap between label and input (this is not part of the M3 spec)
// 24 - input text (font size = 16, line height = 1.5)
// 4 - bottom padding (8 minus 4 due to reduced visual density)
expect(getDecoratorRect(tester).size, const Size(800.0, 48.0));
// The decorator is empty, label is not floating and is vertically centered.
expect(getLabelRect(tester).top, 16.0);
expect(getLabelRect(tester).bottom, 32.0);
expect(getHintOpacity(tester), 0.0);
expect(getBorderBottom(tester), 48.0);
expect(getBorderWeight(tester), 1.0);
// When the decorator is focused, label moves upwards, hint is visible (opacity 1.0).
await tester.pumpWidget(
buildInputDecorator(
isEmpty: true,
isFocused: true,
visualDensity: VisualDensity.compact,
decoration: const InputDecoration(
labelText: labelText,
hintText: hintText,
),
),
);
await tester.pump(kTransitionDuration);
// The decorator is empty and focused, label and hint are visible.
expect(getDecoratorRect(tester).size, const Size(800.0, 48.0));
expect(getLabelRect(tester).top, 4.0);
expect(getLabelRect(tester).bottom, 16.0);
expect(getHintRect(tester).top, 20.0);
expect(getHintRect(tester).bottom, 44.0);
expect(getHintOpacity(tester), 1.0);
expect(getBorderBottom(tester), 48.0);
expect(getBorderWeight(tester), 2.0);
await tester.pumpWidget(
buildInputDecorator(
isFocused: true,
visualDensity: VisualDensity.compact,
decoration: const InputDecoration(
labelText: labelText,
hintText: hintText,
),
),
);
await tester.pump(kTransitionDuration);
// The decorator is focused and not empty, label and input are visible.
expect(getDecoratorRect(tester).size, const Size(800.0, 48.0));
expect(getLabelRect(tester).top, 4.0);
expect(getLabelRect(tester).bottom, 16.0);
expect(getInputRect(tester).top, 20.0);
expect(getInputRect(tester).bottom, 44.0);
expect(getHintOpacity(tester), 0.0);
expect(getBorderBottom(tester), 48.0);
expect(getBorderWeight(tester), 2.0);
});
testWidgets('InputDecorator respects increased theme visualDensity', (WidgetTester tester) async {
await tester.pumpWidget(
buildInputDecorator(
isEmpty: true,
visualDensity: const VisualDensity(horizontal: 2.0, vertical: 2.0),
decoration: const InputDecoration(
labelText: labelText,
hintText: hintText,
),
),
);
// Overall height for this InputDecorator is 64dp:
// 12 - top padding (8 plus 4 due to increased visual density)
// 12 - floating label (font size = 16 * 0.75, line height is forced to 1.0)
// 4 - gap between label and input (this is not part of the M3 spec)
// 24 - input text (font size = 16, line height = 1.5)
// 12 - bottom padding (8 plus 4 due to increased visual density)
expect(getDecoratorRect(tester).size, const Size(800.0, 64.0));
// The decorator is empty, label is not floating and is vertically centered.
expect(getLabelRect(tester).top, 24.0);
expect(getLabelRect(tester).bottom, 40.0);
expect(getHintOpacity(tester), 0.0);
expect(getBorderBottom(tester), 64.0);
expect(getBorderWeight(tester), 1.0);
// When the decorator is focused, label moves upwards, hint is visible (opacity 1.0).
await tester.pumpWidget(
buildInputDecorator(
isEmpty: true,
isFocused: true,
visualDensity: const VisualDensity(horizontal: 2.0, vertical: 2.0),
decoration: const InputDecoration(
labelText: labelText,
hintText: hintText,
),
),
);
await tester.pump(kTransitionDuration);
// The decorator is empty and focused, label and hint are visible.
expect(getDecoratorRect(tester).size, const Size(800.0, 64.0));
expect(getLabelRect(tester).top, 12.0);
expect(getLabelRect(tester).bottom, 24.0);
expect(getHintRect(tester).top, 28.0);
expect(getHintRect(tester).bottom, 52.0);
expect(getHintOpacity(tester), 1.0);
expect(getBorderBottom(tester), 64.0);
expect(getBorderWeight(tester), 2.0);
await tester.pumpWidget(
buildInputDecorator(
isFocused: true,
visualDensity: const VisualDensity(horizontal: 2.0, vertical: 2.0),
decoration: const InputDecoration(
labelText: labelText,
hintText: hintText,
),
),
);
await tester.pump(kTransitionDuration);
// The decorator is focused and not empty, label and input are visible.
expect(getDecoratorRect(tester).size, const Size(800.0, 64.0));
expect(getLabelRect(tester).top, 12.0);
expect(getLabelRect(tester).bottom, 24.0);
expect(getInputRect(tester).top, 28.0);
expect(getInputRect(tester).bottom, 52.0);
expect(getHintOpacity(tester), 0.0);
expect(getBorderBottom(tester), 64.0);
expect(getBorderWeight(tester), 2.0);
});
});
group('Material3 - InputDecoration label layout', () {
@ -7216,8 +7364,8 @@ void main() {
expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 48.0));
expect(tester.getTopLeft(find.text('text')).dy, 24.0);
expect(tester.getBottomLeft(find.text('text')).dy, 40.0);
expect(tester.getTopLeft(find.text('label')).dy, 12.0);
expect(tester.getBottomLeft(find.text('label')).dy, 24.0);
expect(tester.getTopLeft(find.text('label')).dy, 8.0);
expect(tester.getBottomLeft(find.text('label')).dy, 20.0);
expect(tester.getTopLeft(find.text('hint')).dy, 24.0);
expect(tester.getBottomLeft(find.text('hint')).dy, 40.0);
expect(getOpacity(tester, 'hint'), 1.0);
@ -7250,8 +7398,8 @@ void main() {
expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 48.0));
expect(tester.getTopLeft(find.text('text')).dy, 24.0);
expect(tester.getBottomLeft(find.text('text')).dy,40.0);
expect(tester.getTopLeft(find.text('label')).dy, 12.0);
expect(tester.getBottomLeft(find.text('label')).dy, 24.0);
expect(tester.getTopLeft(find.text('label')).dy, 8.0);
expect(tester.getBottomLeft(find.text('label')).dy, 20.0);
expect(tester.getTopLeft(find.text('hint')).dy, 24.0);
expect(tester.getBottomLeft(find.text('hint')).dy,40.0);
expect(getOpacity(tester, 'hint'), 0.0);
@ -7310,8 +7458,8 @@ void main() {
expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 64.0));
expect(tester.getTopLeft(find.text('text')).dy, 32.0);
expect(tester.getBottomLeft(find.text('text')).dy, 48.0);
expect(tester.getTopLeft(find.text('label')).dy, 12.0);
expect(tester.getBottomLeft(find.text('label')).dy, 24.0);
expect(tester.getTopLeft(find.text('label')).dy, 16.0);
expect(tester.getBottomLeft(find.text('label')).dy, 28.0);
expect(tester.getTopLeft(find.text('hint')).dy, 32.0);
expect(tester.getBottomLeft(find.text('hint')).dy, 48.0);
expect(getOpacity(tester, 'hint'), 1.0);
@ -7344,8 +7492,8 @@ void main() {
expect(tester.getSize(find.byType(InputDecorator)), const Size(800.0, 64.0));
expect(tester.getTopLeft(find.text('text')).dy, 32.0);
expect(tester.getBottomLeft(find.text('text')).dy, 48.0);
expect(tester.getTopLeft(find.text('label')).dy, 12.0);
expect(tester.getBottomLeft(find.text('label')).dy, 24.0);
expect(tester.getTopLeft(find.text('label')).dy, 16.0);
expect(tester.getBottomLeft(find.text('label')).dy, 28.0);
expect(tester.getTopLeft(find.text('hint')).dy, 32.0);
expect(tester.getBottomLeft(find.text('hint')).dy, 48.0);
expect(getOpacity(tester, 'hint'), 0.0);