Unverified Commit cf150f99 authored by Hans Muller's avatar Hans Muller Committed by GitHub

Fine tune the Y offset of OutlineInputBorder's floating label (#55260)

parent 36767d01
......@@ -514,10 +514,12 @@ class _Decoration {
this.helperError,
this.counter,
this.container,
this.fixTextFieldOutlineLabel = false,
}) : assert(contentPadding != null),
assert(isCollapsed != null),
assert(floatingLabelHeight != null),
assert(floatingLabelProgress != null);
assert(floatingLabelProgress != null),
assert(fixTextFieldOutlineLabel != null);
final EdgeInsetsGeometry contentPadding;
final bool isCollapsed;
......@@ -539,6 +541,7 @@ class _Decoration {
final Widget helperError;
final Widget counter;
final Widget container;
final bool fixTextFieldOutlineLabel;
@override
bool operator ==(Object other) {
......@@ -566,7 +569,8 @@ class _Decoration {
&& other.suffixIcon == suffixIcon
&& other.helperError == helperError
&& other.counter == counter
&& other.container == container;
&& other.container == container
&& other.fixTextFieldOutlineLabel == fixTextFieldOutlineLabel;
}
@override
......@@ -591,6 +595,7 @@ class _Decoration {
helperError,
counter,
container,
fixTextFieldOutlineLabel,
);
}
}
......@@ -1433,12 +1438,18 @@ class _RenderDecoration extends RenderBox {
if (label != null) {
final Offset labelOffset = _boxParentData(label).offset;
final double labelHeight = label.size.height;
final double borderWeight = decoration.border.borderSide.width;
final double t = decoration.floatingLabelProgress;
// The center of the outline border label ends up a little below the
// center of the top border line.
final bool isOutlineBorder = decoration.border != null && decoration.border.isOutline;
final double floatingY = isOutlineBorder ? -labelHeight * 0.25 : contentPadding.top;
final double scale = lerpDouble(1.0, 0.75, t);
// Temporary opt-in fix for https://github.com/flutter/flutter/issues/54028
// Center the scaled label relative to the border.
const double finalLabelScale = 0.75;
final double floatingY = decoration.fixTextFieldOutlineLabel
? isOutlineBorder ? (-labelHeight * finalLabelScale) / 2.0 + borderWeight / 2.0 : contentPadding.top
: isOutlineBorder ? -labelHeight * 0.25 : contentPadding.top;
final double scale = lerpDouble(1.0, finalLabelScale, t);
double dx;
switch (textDirection) {
case TextDirection.rtl:
......@@ -2073,9 +2084,17 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
? decoration.errorStyle?.color ?? themeData.errorColor
: _getActiveColor(themeData);
final TextStyle style = themeData.textTheme.subtitle1.merge(widget.baseStyle);
return style
.copyWith(color: decoration.enabled ? color : themeData.disabledColor)
.merge(decoration.labelStyle);
// Temporary opt-in fix for https://github.com/flutter/flutter/issues/54028
// Setting TextStyle.height to 1 ensures that the label's height will equal
// its font size.
return themeData.fixTextFieldOutlineLabel
? style
.copyWith(height: 1, color: decoration.enabled ? color : themeData.disabledColor)
.merge(decoration.labelStyle)
: style
.copyWith(color: decoration.enabled ? color : themeData.disabledColor)
.merge(decoration.labelStyle);
}
TextStyle _getHelperStyle(ThemeData themeData) {
......@@ -2154,7 +2173,12 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
isHovering: isHovering,
);
final TextStyle inlineLabelStyle = inlineStyle.merge(decoration.labelStyle);
// Temporary opt-in fix for https://github.com/flutter/flutter/issues/54028
// Setting TextStyle.height to 1 ensures that the label's height will equal
// its font size.
final TextStyle inlineLabelStyle = themeData.fixTextFieldOutlineLabel
? inlineStyle.merge(decoration.labelStyle).copyWith(height: 1)
: inlineStyle.merge(decoration.labelStyle);
final Widget label = decoration.labelText == null ? null : _Shaker(
animation: _shakingLabelController.view,
child: AnimatedOpacity(
......@@ -2331,6 +2355,7 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat
helperError: helperError,
counter: counter,
container: container,
fixTextFieldOutlineLabel: themeData.fixTextFieldOutlineLabel,
),
textDirection: textDirection,
textBaseline: textBaseline,
......
......@@ -268,6 +268,7 @@ class ThemeData with Diagnosticable {
DividerThemeData dividerTheme,
ButtonBarThemeData buttonBarTheme,
BottomNavigationBarThemeData bottomNavigationBarTheme,
bool fixTextFieldOutlineLabel,
}) {
brightness ??= Brightness.light;
final bool isDark = brightness == Brightness.dark;
......@@ -378,6 +379,8 @@ class ThemeData with Diagnosticable {
buttonBarTheme ??= const ButtonBarThemeData();
bottomNavigationBarTheme ??= const BottomNavigationBarThemeData();
fixTextFieldOutlineLabel ??= false;
return ThemeData.raw(
brightness: brightness,
visualDensity: visualDensity,
......@@ -444,6 +447,7 @@ class ThemeData with Diagnosticable {
dividerTheme: dividerTheme,
buttonBarTheme: buttonBarTheme,
bottomNavigationBarTheme: bottomNavigationBarTheme,
fixTextFieldOutlineLabel: fixTextFieldOutlineLabel,
);
}
......@@ -523,6 +527,7 @@ class ThemeData with Diagnosticable {
@required this.dividerTheme,
@required this.buttonBarTheme,
@required this.bottomNavigationBarTheme,
@required this.fixTextFieldOutlineLabel,
}) : assert(brightness != null),
assert(visualDensity != null),
assert(primaryColor != null),
......@@ -584,7 +589,8 @@ class ThemeData with Diagnosticable {
assert(bannerTheme != null),
assert(dividerTheme != null),
assert(buttonBarTheme != null),
assert(bottomNavigationBarTheme != null);
assert(bottomNavigationBarTheme != null),
assert(fixTextFieldOutlineLabel != null);
/// Create a [ThemeData] based on the colors in the given [colorScheme] and
/// text styles of the optional [textTheme].
......@@ -1035,6 +1041,18 @@ class ThemeData with Diagnosticable {
/// widgets.
final BottomNavigationBarThemeData bottomNavigationBarTheme;
/// A temporary flag to allow apps to opt-in to a
/// [small fix](https://github.com/flutter/flutter/issues/54028) for the Y
/// coordinate of the floating label in a [TextField] [OutlineInputBorder].
///
/// Setting this flag to true causes the floating label to be more precisely
/// vertically centered relative to the border's outline.
///
/// The flag is currently false by default. It will be default true and
/// deprecated before the next beta release (1.18), and removed before the next
/// stable release (1.19).
final bool fixTextFieldOutlineLabel;
/// Creates a copy of this theme but with the given fields replaced with the new values.
ThemeData copyWith({
Brightness brightness,
......@@ -1102,6 +1120,7 @@ class ThemeData with Diagnosticable {
DividerThemeData dividerTheme,
ButtonBarThemeData buttonBarTheme,
BottomNavigationBarThemeData bottomNavigationBarTheme,
bool fixTextFieldOutlineLabel,
}) {
cupertinoOverrideTheme = cupertinoOverrideTheme?.noDefault();
return ThemeData.raw(
......@@ -1170,6 +1189,7 @@ class ThemeData with Diagnosticable {
dividerTheme: dividerTheme ?? this.dividerTheme,
buttonBarTheme: buttonBarTheme ?? this.buttonBarTheme,
bottomNavigationBarTheme: bottomNavigationBarTheme ?? this.bottomNavigationBarTheme,
fixTextFieldOutlineLabel: fixTextFieldOutlineLabel ?? this.fixTextFieldOutlineLabel,
);
}
......@@ -1316,6 +1336,7 @@ class ThemeData with Diagnosticable {
dividerTheme: DividerThemeData.lerp(a.dividerTheme, b.dividerTheme, t),
buttonBarTheme: ButtonBarThemeData.lerp(a.buttonBarTheme, b.buttonBarTheme, t),
bottomNavigationBarTheme: BottomNavigationBarThemeData.lerp(a.bottomNavigationBarTheme, b.bottomNavigationBarTheme, t),
fixTextFieldOutlineLabel: t < 0.5 ? a.fixTextFieldOutlineLabel : b.fixTextFieldOutlineLabel,
);
}
......@@ -1389,7 +1410,8 @@ class ThemeData with Diagnosticable {
&& other.bannerTheme == bannerTheme
&& other.dividerTheme == dividerTheme
&& other.buttonBarTheme == buttonBarTheme
&& other.bottomNavigationBarTheme == bottomNavigationBarTheme;
&& other.bottomNavigationBarTheme == bottomNavigationBarTheme
&& other.fixTextFieldOutlineLabel == fixTextFieldOutlineLabel;
}
@override
......@@ -1463,6 +1485,7 @@ class ThemeData with Diagnosticable {
dividerTheme,
buttonBarTheme,
bottomNavigationBarTheme,
fixTextFieldOutlineLabel,
];
return hashList(values);
}
......
......@@ -21,6 +21,7 @@ Widget buildInputDecorator({
TextStyle baseStyle,
TextAlignVertical textAlignVertical,
VisualDensity visualDensity,
bool fixTextFieldOutlineLabel = false,
Widget child = const Text(
'text',
style: TextStyle(fontFamily: 'Ahem', fontSize: 16.0),
......@@ -34,6 +35,7 @@ Widget buildInputDecorator({
data: Theme.of(context).copyWith(
inputDecorationTheme: inputDecorationTheme,
visualDensity: visualDensity,
fixTextFieldOutlineLabel: fixTextFieldOutlineLabel,
),
child: Align(
alignment: Alignment.topLeft,
......@@ -3881,4 +3883,31 @@ void main() {
// Ideographic (incorrect) value is 50.299999713897705
expect(tester.getBottomLeft(find.text('hint')).dy, isBrowser ? 45.75 : 47.75);
});
testWidgets('InputDecorator floating label Y coordinate', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/54028
await tester.pumpWidget(
buildInputDecorator(
// Temporary opt-in fix for https://github.com/flutter/flutter/issues/54028
// Ensures that the floating label is vertically centered relative to
// center of the top edge of the InputDecorator's outline border.
fixTextFieldOutlineLabel: true,
isEmpty: true,
decoration: const InputDecoration(
labelText: 'label',
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(width: 4),
),
floatingLabelBehavior: FloatingLabelBehavior.always,
),
),
);
await tester.pumpAndSettle();
// floatingLabelGeight = 12 (ahem font size 16dps * 0.75 = 12)
// labelY = -floatingLabelHeight/2 + borderWidth/2
expect(tester.getTopLeft(find.text('label')).dy, -4.0);
});
}
......@@ -281,6 +281,7 @@ void main() {
dividerTheme: const DividerThemeData(color: Colors.black),
buttonBarTheme: const ButtonBarThemeData(alignment: MainAxisAlignment.start),
bottomNavigationBarTheme: const BottomNavigationBarThemeData(type: BottomNavigationBarType.fixed),
fixTextFieldOutlineLabel: false,
);
final SliderThemeData otherSliderTheme = SliderThemeData.fromPrimaryColors(
......@@ -362,6 +363,7 @@ void main() {
dividerTheme: const DividerThemeData(color: Colors.white),
buttonBarTheme: const ButtonBarThemeData(alignment: MainAxisAlignment.end),
bottomNavigationBarTheme: const BottomNavigationBarThemeData(type: BottomNavigationBarType.shifting),
fixTextFieldOutlineLabel: true,
);
final ThemeData themeDataCopy = theme.copyWith(
......@@ -429,6 +431,7 @@ void main() {
dividerTheme: otherTheme.dividerTheme,
buttonBarTheme: otherTheme.buttonBarTheme,
bottomNavigationBarTheme: otherTheme.bottomNavigationBarTheme,
fixTextFieldOutlineLabel: otherTheme.fixTextFieldOutlineLabel,
);
expect(themeDataCopy.brightness, equals(otherTheme.brightness));
......@@ -497,6 +500,7 @@ void main() {
expect(themeDataCopy.dividerTheme, equals(otherTheme.dividerTheme));
expect(themeDataCopy.buttonBarTheme, equals(otherTheme.buttonBarTheme));
expect(themeDataCopy.bottomNavigationBarTheme, equals(otherTheme.bottomNavigationBarTheme));
expect(themeDataCopy.fixTextFieldOutlineLabel, equals(otherTheme.fixTextFieldOutlineLabel));
});
testWidgets('ThemeData.toString has less than 200 characters output', (WidgetTester tester) async {
......
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