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