From c7d982eba3a159105eacd37b7b2c48ae1fa32a06 Mon Sep 17 00:00:00 2001 From: Hans Muller Date: Thu, 5 Apr 2018 12:08:11 -0700 Subject: [PATCH] Updated appearance of filled TextFields - added UnderlineInputBorder.borderRadius (#16272) --- .../lib/src/material/input_border.dart | 43 +++++++++++++++---- .../test/material/input_decorator_test.dart | 34 ++++++++++++++- 2 files changed, 68 insertions(+), 9 deletions(-) diff --git a/packages/flutter/lib/src/material/input_border.dart b/packages/flutter/lib/src/material/input_border.dart index 72df100a1f..71b66e45a9 100644 --- a/packages/flutter/lib/src/material/input_border.dart +++ b/packages/flutter/lib/src/material/input_border.dart @@ -116,7 +116,8 @@ class _NoInputBorder extends InputBorder { } } -/// Draws a horizontal line at the bottom of an [InputDecorator]'s container. +/// Draws a horizontal line at the bottom of an [InputDecorator]'s container and +/// defines the container's shape. /// /// The input decorator's "container" is the optionally filled area above the /// decorator's helper, error, and counter. @@ -133,16 +134,39 @@ class UnderlineInputBorder extends InputBorder { /// null). Applications typically do not specify a [borderSide] parameter /// because the input decorator substitutes its own, using [copyWith], based /// on the current theme and [InputDecorator.isFocused]. + /// + /// The [borderRadius] parameter defaults to a value where the top left + /// and right corners have a circular radius of 4.0. The [borderRadius] + /// parameter must not be null. const UnderlineInputBorder({ BorderSide borderSide: BorderSide.none, - }) : super(borderSide: borderSide); + this.borderRadius: const BorderRadius.only( + topLeft: const Radius.circular(4.0), + topRight: const Radius.circular(4.0), + ), + }) : assert(borderRadius != null), + super(borderSide: borderSide); + + /// The radii of the border's rounded rectangle corners. + /// + /// When this border is used with a filled input decorator, see + /// [InputDecoration.filled], the border radius defines the shape + /// of the background fill as well as the bottom left and right + /// edges of the underline itself. + /// + /// By default the top right and top left corners have a circular radius + /// of 4.0. + final BorderRadius borderRadius; @override bool get isOutline => false; @override - UnderlineInputBorder copyWith({ BorderSide borderSide }) { - return new UnderlineInputBorder(borderSide: borderSide ?? this.borderSide); + UnderlineInputBorder copyWith({ BorderSide borderSide, BorderRadius borderRadius }) { + return new UnderlineInputBorder( + borderSide: borderSide ?? this.borderSide, + borderRadius: borderRadius ?? this.borderRadius, + ); } @override @@ -163,7 +187,7 @@ class UnderlineInputBorder extends InputBorder { @override Path getOuterPath(Rect rect, { TextDirection textDirection }) { - return new Path()..addRect(rect); + return new Path()..addRRect(borderRadius.resolve(textDirection).toRRect(rect)); } @override @@ -197,6 +221,8 @@ class UnderlineInputBorder extends InputBorder { double gapPercentage: 0.0, TextDirection textDirection, }) { + if (borderRadius.bottomLeft != Radius.zero || borderRadius.bottomRight != Radius.zero) + canvas.clipPath(getOuterPath(rect, textDirection: textDirection)); canvas.drawLine(rect.bottomLeft, rect.bottomRight, borderSide.toPaint()); } @@ -235,9 +261,10 @@ class OutlineInputBorder extends InputBorder { /// because the input decorator substitutes its own, using [copyWith], based /// on the current theme and [InputDecorator.isFocused]. /// - /// If [borderRadius] is null (the default) then the border's corners - /// are drawn with a radius of 4 logical pixels. The corner radii must be - /// circular, i.e. their [Radius.x] and [Radius.y] values must be the same. + /// The [borderRadius] parameter defaults to a value where all four + /// corners have a circular radius of 4.0. The [borderRadius] parameter + /// must not be null and the corner radii must be circular, i.e. their + /// [Radius.x] and [Radius.y] values must be the same. const OutlineInputBorder({ BorderSide borderSide: BorderSide.none, this.borderRadius: const BorderRadius.all(const Radius.circular(4.0)), diff --git a/packages/flutter/test/material/input_decorator_test.dart b/packages/flutter/test/material/input_decorator_test.dart index f7b339ee02..b9d3dbcdf4 100644 --- a/packages/flutter/test/material/input_decorator_test.dart +++ b/packages/flutter/test/material/input_decorator_test.dart @@ -1217,7 +1217,7 @@ void main() { expect(decoration.border, const OutlineInputBorder()); }); - testWidgets('InputDecorator fillColor is clipped by border', (WidgetTester tester) async { + testWidgets('InputDecorator OutlineInputBorder fillColor is clipped by border', (WidgetTester tester) async { // This is a regression test for https://github.com/flutter/flutter/issues/15742 await tester.pumpWidget( @@ -1256,4 +1256,36 @@ void main() { rrect: new RRect.fromLTRBR(0.5, 0.5, 799.5, 55.5, const Radius.circular(11.5)), )); }); + + testWidgets('InputDecorator UnderlineInputBorder fillColor is clipped by border', (WidgetTester tester) async { + await tester.pumpWidget( + buildInputDecorator( + // isEmpty: false (default) + // isFocused: false (default) + decoration: const InputDecoration( + filled: true, + fillColor: const Color(0xFF00FF00), + border: const UnderlineInputBorder( + borderRadius: const BorderRadius.only( + bottomLeft: const Radius.circular(12.0), + bottomRight: const Radius.circular(12.0), + ), + ), + ), + ), + ); + + final RenderBox box = tester.renderObject(find.byType(InputDecorator)); + + // Fill is the border's outer path, a rounded rectangle + expect(box, paints..path( + style: PaintingStyle.fill, + color: const Color(0xFF00FF00), + includes: [const Offset(800.0/2.0, 56/2.0)], + excludes: [ + const Offset(1.0, 56.0 - 6.0), // bottom left + const Offset(800 - 1.0, 56.0 - 6.0), // bottom right + ], + )); + }); }