Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
d188a8ff
Unverified
Commit
d188a8ff
authored
Jan 08, 2018
by
Hans Muller
Committed by
GitHub
Jan 08, 2018
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Update InputDecorator et al (#13734)
parent
c3366a65
Changes
7
Show whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
3087 additions
and
893 deletions
+3087
-893
material.dart
packages/flutter/lib/material.dart
+1
-0
input_border.dart
packages/flutter/lib/src/material/input_border.dart
+413
-0
input_decorator.dart
packages/flutter/lib/src/material/input_decorator.dart
+1834
-466
text_field.dart
packages/flutter/lib/src/material/text_field.dart
+1
-0
shape_decoration.dart
packages/flutter/lib/src/painting/shape_decoration.dart
+9
-4
input_decorator_test.dart
packages/flutter/test/material/input_decorator_test.dart
+788
-386
text_field_test.dart
packages/flutter/test/material/text_field_test.dart
+41
-37
No files found.
packages/flutter/lib/material.dart
View file @
d188a8ff
...
...
@@ -55,6 +55,7 @@ export 'src/material/icons.dart';
export
'src/material/ink_highlight.dart'
;
export
'src/material/ink_splash.dart'
;
export
'src/material/ink_well.dart'
;
export
'src/material/input_border.dart'
;
export
'src/material/input_decorator.dart'
;
export
'src/material/list_tile.dart'
;
export
'src/material/material.dart'
;
...
...
packages/flutter/lib/src/material/input_border.dart
0 → 100644
View file @
d188a8ff
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'dart:math'
as
math
;
import
'dart:ui'
show
lerpDouble
;
import
'package:flutter/widgets.dart'
;
/// Defines the appearance of an [InputDecorator]'s border.
///
/// An input decorator's border is specified by [InputDecoration.border].
///
/// The border is drawn relative to the input decorator's "container" which
/// is the optionally filled area above the decorator's helper, error,
/// and counter.
///
/// Input border's are decorated with a line whose weight and color are defined
/// by [borderSide]. The input decorator's renderer animates the input border's
/// appearance in response to state changes, like gaining or losing the focus,
/// by creating new copies of its input border with [copyWith].
///
/// See also:
///
/// * [UnderlineInputBorder], the default [InputDecorator] border which
/// draws a horizontal line at the bottom of the input decorator's container.
/// * [OutlineInputBorder], an [InputDecorator] border which draws a
/// rounded rectangle around the input decorator's container.
/// * [InputDecoration], which is used to configure an [InputDecorator].
abstract
class
InputBorder
extends
ShapeBorder
{
/// Creates a border for an [InputDecorator].
///
/// The [borderSide] parameter must not be 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].
const
InputBorder
({
this
.
borderSide
:
BorderSide
.
none
,
})
:
assert
(
borderSide
!=
null
);
/// Defines the border line's color and weight.
///
/// The [InputDecorator] creates copies of its input border, using [copyWith],
/// based on the current theme and [InputDecorator.isFocused].
final
BorderSide
borderSide
;
/// Creates a copy of this input border with the specified `borderSide`.
InputBorder
copyWith
({
BorderSide
borderSide
});
/// True if this border will enclose the [InputDecorator]'s container.
///
/// This property affects the alignment of container's contents. For example
/// when an input decorator is configured with an [OutlineInputBorder] its
/// label is centered with its container.
bool
get
isOutline
;
/// Paint this input border on [canvas].
///
/// The [rect] parameter bounds the [InputDecorator]'s container.
///
/// The additional `gap` parameters reflect the state of the [InputDecorator]'s
/// floating label. When an input decorator gains the focus, its label
/// animates upwards, to make room for the input child. The [gapStart] and
/// [gapExtent] parameters define a floating label width interval, and
/// [gapPercentage] defines the animation's progress (0.0 to 1.0).
@override
void
paint
(
Canvas
canvas
,
Rect
rect
,
{
double
gapStart
,
double
gapExtent:
0.0
,
double
gapPercentage:
0.0
,
TextDirection
textDirection
,
});
}
/// Draws a horizontal line at the bottom of an [InputDecorator]'s container.
///
/// The input decorator's "container" is the optionally filled area above the
/// decorator's helper, error, and counter.
///
/// See also:
///
/// * [OutlineInputBorder], an [InputDecorator] border which draws a
/// rounded rectangle around the input decorator's container.
/// * [InputDecoration], which is used to configure an [InputDecorator].
class
UnderlineInputBorder
extends
InputBorder
{
/// Creates an underline border for an [InputDecorator].
///
/// The [borderSide] parameter defaults to [BorderSide.none] (it must not be
/// 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].
const
UnderlineInputBorder
({
BorderSide
borderSide:
BorderSide
.
none
,
})
:
super
(
borderSide:
borderSide
);
@override
bool
get
isOutline
=>
false
;
@override
UnderlineInputBorder
copyWith
({
BorderSide
borderSide
})
{
return
new
UnderlineInputBorder
(
borderSide:
borderSide
??
this
.
borderSide
);
}
@override
EdgeInsetsGeometry
get
dimensions
{
return
new
EdgeInsets
.
only
(
bottom:
borderSide
.
width
);
}
@override
UnderlineInputBorder
scale
(
double
t
)
{
return
new
UnderlineInputBorder
(
borderSide:
borderSide
.
scale
(
t
));
}
@override
Path
getInnerPath
(
Rect
rect
,
{
TextDirection
textDirection
})
{
return
new
Path
()
..
addRect
(
new
Rect
.
fromLTWH
(
rect
.
left
,
rect
.
top
,
rect
.
width
,
math
.
max
(
0.0
,
rect
.
height
-
borderSide
.
width
)));
}
@override
Path
getOuterPath
(
Rect
rect
,
{
TextDirection
textDirection
})
{
return
new
Path
()..
addRect
(
rect
);
}
@override
ShapeBorder
lerpFrom
(
ShapeBorder
a
,
double
t
)
{
if
(
a
is
UnderlineInputBorder
)
{
return
new
UnderlineInputBorder
(
borderSide:
BorderSide
.
lerp
(
a
.
borderSide
,
borderSide
,
t
),
);
}
return
super
.
lerpFrom
(
a
,
t
);
}
@override
ShapeBorder
lerpTo
(
ShapeBorder
b
,
double
t
)
{
if
(
b
is
UnderlineInputBorder
)
{
return
new
UnderlineInputBorder
(
borderSide:
BorderSide
.
lerp
(
borderSide
,
b
.
borderSide
,
t
),
);
}
return
super
.
lerpTo
(
b
,
t
);
}
/// Draw a horizontal line at the bottom of [rect].
///
/// The [borderSide] defines the line's color and weight. The `textDirection`
/// `gap` and `textDirection` parameters are ignored.
@override
void
paint
(
Canvas
canvas
,
Rect
rect
,
{
double
gapStart
,
double
gapExtent:
0.0
,
double
gapPercentage:
0.0
,
TextDirection
textDirection
,
})
{
canvas
.
drawLine
(
rect
.
bottomLeft
,
rect
.
bottomRight
,
borderSide
.
toPaint
());
}
@override
bool
operator
==(
dynamic
other
)
{
if
(
identical
(
this
,
other
))
return
true
;
if
(
runtimeType
!=
other
.
runtimeType
)
return
false
;
final
InputBorder
typedOther
=
other
;
return
typedOther
.
borderSide
==
borderSide
;
}
@override
int
get
hashCode
=>
borderSide
.
hashCode
;
}
/// Draws a rounded rectangle around an [InputDecorator]'s container.
///
/// When the input decorator's label is floating, for example because its
/// input child has the focus, the label appears in a gap in the border outline.
///
/// The input decorator's "container" is the optionally filled area above the
/// decorator's helper, error, and counter.
///
/// See also:
///
/// * [UnderlineInputBorder], the default [InputDecorator] border which
/// draws a horizontal line at the bottom of the input decorator's container.
/// * [InputDecoration], which is used to configure an [InputDecorator].
class
OutlineInputBorder
extends
InputBorder
{
/// Creates a rounded rectangle outline border for an [InputDecorator].
///
/// The [borderSide] parameter defaults to [BorderSide.none] (it must not be
/// 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].
///
/// 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.
const
OutlineInputBorder
({
BorderSide
borderSide:
BorderSide
.
none
,
this
.
borderRadius
:
const
BorderRadius
.
all
(
const
Radius
.
circular
(
4.0
)),
this
.
gapPadding
:
4.0
,
})
:
assert
(
borderRadius
!=
null
),
assert
(
gapPadding
!=
null
&&
gapPadding
>=
0.0
),
super
(
borderSide:
borderSide
);
// The label text's gap can extend into the corners (even both the top left
// and the top right corner). To avoid the more complicated problem of finding
// how far the gap penetrates into an elliptical corner, just require them
// to be circular.
//
// This can't be checked by the constructor because const constructor.
static
bool
_cornersAreCircular
(
BorderRadius
borderRadius
)
{
return
borderRadius
.
topLeft
.
x
==
borderRadius
.
topLeft
.
y
&&
borderRadius
.
bottomLeft
.
x
==
borderRadius
.
bottomLeft
.
y
&&
borderRadius
.
topRight
.
x
==
borderRadius
.
topRight
.
y
&&
borderRadius
.
bottomRight
.
x
==
borderRadius
.
bottomRight
.
y
;
}
/// Horizontal padding on either side of the border's
/// [InputDecoration.labelText] width gap.
///
/// This value is used by the [paint] method to compute the actual gap width.
final
double
gapPadding
;
/// The radii of the border's rounded rectangle corners.
///
/// The corner radii must be circular, i.e. their [Radius.x] and [Radius.y]
/// values must be the same.
final
BorderRadius
borderRadius
;
@override
bool
get
isOutline
=>
true
;
@override
OutlineInputBorder
copyWith
({
BorderSide
borderSide
,
BorderRadius
borderRadius
,
double
gapPadding
,
})
{
return
new
OutlineInputBorder
(
borderSide:
borderSide
??
this
.
borderSide
,
borderRadius:
borderRadius
??
this
.
borderRadius
,
gapPadding:
gapPadding
??
this
.
gapPadding
,
);
}
@override
EdgeInsetsGeometry
get
dimensions
{
return
new
EdgeInsets
.
all
(
borderSide
.
width
);
}
@override
OutlineInputBorder
scale
(
double
t
)
{
return
new
OutlineInputBorder
(
borderSide:
borderSide
.
scale
(
t
),
borderRadius:
borderRadius
*
t
,
gapPadding:
gapPadding
*
t
,
);
}
@override
ShapeBorder
lerpFrom
(
ShapeBorder
a
,
double
t
)
{
if
(
a
is
OutlineInputBorder
)
{
final
OutlineInputBorder
outline
=
a
;
return
new
OutlineInputBorder
(
borderRadius:
BorderRadius
.
lerp
(
outline
.
borderRadius
,
borderRadius
,
t
),
borderSide:
BorderSide
.
lerp
(
outline
.
borderSide
,
borderSide
,
t
),
gapPadding:
outline
.
gapPadding
,
);
}
return
super
.
lerpFrom
(
a
,
t
);
}
@override
ShapeBorder
lerpTo
(
ShapeBorder
b
,
double
t
)
{
if
(
b
is
OutlineInputBorder
)
{
final
OutlineInputBorder
outline
=
b
;
return
new
OutlineInputBorder
(
borderRadius:
BorderRadius
.
lerp
(
borderRadius
,
outline
.
borderRadius
,
t
),
borderSide:
BorderSide
.
lerp
(
borderSide
,
outline
.
borderSide
,
t
),
gapPadding:
outline
.
gapPadding
,
);
}
return
super
.
lerpTo
(
b
,
t
);
}
@override
Path
getInnerPath
(
Rect
rect
,
{
TextDirection
textDirection
})
{
return
new
Path
()
..
addRRect
(
borderRadius
.
resolve
(
textDirection
).
toRRect
(
rect
).
deflate
(
borderSide
.
width
));
}
@override
Path
getOuterPath
(
Rect
rect
,
{
TextDirection
textDirection
})
{
return
new
Path
()
..
addRRect
(
borderRadius
.
resolve
(
textDirection
).
toRRect
(
rect
));
}
Path
_gapBorderPath
(
Canvas
canvas
,
RRect
center
,
double
start
,
double
extent
)
{
final
Rect
tlCorner
=
new
Rect
.
fromLTWH
(
center
.
left
,
center
.
top
,
center
.
tlRadiusX
*
2.0
,
center
.
tlRadiusY
*
2.0
,
);
final
Rect
trCorner
=
new
Rect
.
fromLTWH
(
center
.
right
-
center
.
trRadiusX
*
2.0
,
center
.
top
,
center
.
trRadiusX
*
2.0
,
center
.
trRadiusY
*
2.0
,
);
final
Rect
brCorner
=
new
Rect
.
fromLTWH
(
center
.
right
-
center
.
brRadiusX
*
2.0
,
center
.
bottom
-
center
.
brRadiusY
*
2.0
,
center
.
brRadiusX
*
2.0
,
center
.
brRadiusY
*
2.0
,
);
final
Rect
blCorner
=
new
Rect
.
fromLTWH
(
center
.
left
,
center
.
bottom
-
center
.
brRadiusY
*
2.0
,
center
.
blRadiusX
*
2.0
,
center
.
blRadiusY
*
2.0
,
);
final
double
cornerArcSweep
=
math
.
PI
/
2.0
;
final
double
tlCornerArcSweep
=
start
<
center
.
tlRadiusX
?
math
.
asin
(
start
/
center
.
tlRadiusX
)
:
math
.
PI
/
2.0
;
final
Path
path
=
new
Path
()
..
addArc
(
tlCorner
,
math
.
PI
,
tlCornerArcSweep
)
..
moveTo
(
center
.
left
+
center
.
tlRadiusX
,
center
.
top
);
if
(
start
>
center
.
tlRadiusX
)
path
.
lineTo
(
center
.
left
+
start
,
center
.
top
);
final
double
trCornerArcStart
=
(
3
*
math
.
PI
)
/
2.0
;
final
double
trCornerArcSweep
=
cornerArcSweep
;
if
(
start
+
extent
<
center
.
width
-
center
.
trRadiusX
)
{
path
..
relativeMoveTo
(
extent
,
0.0
)
..
lineTo
(
center
.
right
-
center
.
trRadiusX
,
center
.
top
)
..
addArc
(
trCorner
,
trCornerArcStart
,
trCornerArcSweep
);
}
else
if
(
start
+
extent
<
center
.
width
)
{
final
double
dx
=
center
.
width
-
(
start
+
extent
);
final
double
sweep
=
math
.
acos
(
dx
/
center
.
trRadiusX
);
path
.
addArc
(
trCorner
,
trCornerArcStart
+
sweep
,
trCornerArcSweep
-
sweep
);
}
return
path
..
moveTo
(
center
.
right
,
center
.
top
+
center
.
trRadiusY
)
..
lineTo
(
center
.
right
,
center
.
bottom
-
center
.
brRadiusY
)
..
addArc
(
brCorner
,
0.0
,
cornerArcSweep
)
..
lineTo
(
center
.
left
+
center
.
blRadiusX
,
center
.
bottom
)
..
addArc
(
blCorner
,
math
.
PI
/
2.0
,
cornerArcSweep
)
..
lineTo
(
center
.
left
,
center
.
top
+
center
.
trRadiusY
);
}
/// Draw a rounded rectangle around [rect] using [borderRadius].
///
/// The [borderSide] defines the line's color and weight.
///
/// The top side of the rounded rectangle may be interrupted by a single gap
/// if [gapExtent] is non-null. In that case the gap begins at
/// `gapStart - gapPadding` (assuming that the [textDirection] is [TextDirection.ltr]).
/// The gap's width is `(gapPadding + gapExtent + gapPadding) * gapPercentage`.
@override
void
paint
(
Canvas
canvas
,
Rect
rect
,
{
double
gapStart
,
double
gapExtent:
0.0
,
double
gapPercentage:
0.0
,
TextDirection
textDirection
,
})
{
assert
(
gapExtent
!=
null
);
assert
(
gapPercentage
>=
0.0
&&
gapPercentage
<=
1.0
);
assert
(
_cornersAreCircular
(
borderRadius
));
final
Paint
paint
=
borderSide
.
toPaint
();
final
RRect
outer
=
borderRadius
.
toRRect
(
rect
);
final
RRect
center
=
outer
.
deflate
(
borderSide
.
width
/
2.0
);
if
(
gapStart
==
null
||
gapExtent
<=
0.0
||
gapPercentage
==
0.0
)
{
canvas
.
drawRRect
(
center
,
paint
);
}
else
{
final
double
extent
=
lerpDouble
(
0.0
,
gapExtent
+
gapPadding
*
2.0
,
gapPercentage
);
switch
(
textDirection
)
{
case
TextDirection
.
rtl
:
{
final
Path
path
=
_gapBorderPath
(
canvas
,
center
,
gapStart
+
gapPadding
-
extent
,
extent
);
canvas
.
drawPath
(
path
,
paint
);
break
;
}
case
TextDirection
.
ltr
:
{
final
Path
path
=
_gapBorderPath
(
canvas
,
center
,
gapStart
-
gapPadding
,
extent
);
canvas
.
drawPath
(
path
,
paint
);
break
;
}
}
}
}
@override
bool
operator
==(
dynamic
other
)
{
if
(
identical
(
this
,
other
))
return
true
;
if
(
runtimeType
!=
other
.
runtimeType
)
return
false
;
final
OutlineInputBorder
typedOther
=
other
;
return
typedOther
.
borderSide
==
borderSide
&&
typedOther
.
borderRadius
==
borderRadius
&&
typedOther
.
gapPadding
==
gapPadding
;
}
@override
int
get
hashCode
=>
hashValues
(
borderSide
,
borderRadius
,
gapPadding
);
}
packages/flutter/lib/src/material/input_decorator.dart
View file @
d188a8ff
...
...
@@ -2,22 +2,1633 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'dart:math'
as
math
;
import
'dart:ui'
show
lerpDouble
;
import
'package:flutter/foundation.dart'
;
import
'package:flutter/rendering.dart'
;
import
'package:flutter/widgets.dart'
;
import
'colors.dart'
;
import
'
debug
.dart'
;
import
'
input_border
.dart'
;
import
'theme.dart'
;
const
Duration
_kTransitionDuration
=
const
Duration
(
milliseconds:
200
);
const
Curve
_kTransitionCurve
=
Curves
.
fastOutSlowIn
;
// See the InputDecorator.build method, where this is used.
class
_InputDecoratorChildGlobalKey
extends
GlobalObjectKey
{
const
_InputDecoratorChildGlobalKey
(
BuildContext
value
)
:
super
(
value
);
// Defines the gap in the InputDecorator's outline border where the
// floating label will appear.
class
_InputBorderGap
extends
ChangeNotifier
{
double
_start
;
double
get
start
=>
_start
;
set
start
(
double
value
)
{
if
(
value
!=
_start
)
{
_start
=
value
;
notifyListeners
();
}
}
double
_extent
=
0.0
;
double
get
extent
=>
_extent
;
set
extent
(
double
value
)
{
if
(
value
!=
_extent
)
{
_extent
=
value
;
notifyListeners
();
}
}
@override
bool
operator
==(
dynamic
other
)
{
if
(
identical
(
this
,
other
))
return
true
;
if
(
runtimeType
!=
other
.
runtimeType
)
return
false
;
final
_InputBorderGap
typedOther
=
other
;
return
typedOther
.
start
==
start
&&
typedOther
.
extent
==
extent
;
}
@override
int
get
hashCode
=>
hashValues
(
start
,
extent
);
}
// Used to interpolate between two InputBorders.
class
_InputBorderTween
extends
Tween
<
InputBorder
>
{
_InputBorderTween
({
InputBorder
begin
,
InputBorder
end
})
:
super
(
begin:
begin
,
end:
end
);
@override
InputBorder
lerp
(
double
t
)
=>
ShapeBorder
.
lerp
(
begin
,
end
,
t
);
}
// Passes the _InputBorderGap parameters along to an InputBorder's paint method.
class
_InputBorderPainter
extends
CustomPainter
{
_InputBorderPainter
({
Listenable
repaint
,
this
.
borderAnimation
,
this
.
border
,
this
.
gapAnimation
,
this
.
gap
,
this
.
textDirection
,
})
:
super
(
repaint:
repaint
);
final
Animation
<
double
>
borderAnimation
;
final
_InputBorderTween
border
;
final
Animation
<
double
>
gapAnimation
;
final
_InputBorderGap
gap
;
final
TextDirection
textDirection
;
@override
void
paint
(
Canvas
canvas
,
Size
size
)
{
border
.
evaluate
(
borderAnimation
).
paint
(
canvas
,
Offset
.
zero
&
size
,
gapStart:
gap
.
start
,
gapExtent:
gap
.
extent
,
gapPercentage:
gapAnimation
.
value
,
textDirection:
textDirection
,
);
}
@override
bool
shouldRepaint
(
_InputBorderPainter
oldPainter
)
{
return
borderAnimation
!=
oldPainter
.
borderAnimation
||
gapAnimation
!=
oldPainter
.
gapAnimation
||
border
!=
oldPainter
.
border
||
gap
!=
oldPainter
.
gap
||
textDirection
!=
oldPainter
.
textDirection
;
}
}
// An analog of AnimatedContainer, which can animate its shaped border, for
// _InputBorder. This specialized animated container is needed because the
// _InputBorderGap, which is computed at layout time, is required by the
// _InputBorder's paint method.
class
_BorderContainer
extends
StatefulWidget
{
const
_BorderContainer
({
Key
key
,
@required
this
.
border
,
@required
this
.
gap
,
@required
this
.
gapAnimation
,
this
.
child
})
:
assert
(
border
!=
null
),
assert
(
gap
!=
null
),
super
(
key:
key
);
final
InputBorder
border
;
final
_InputBorderGap
gap
;
final
Animation
<
double
>
gapAnimation
;
final
Widget
child
;
@override
_BorderContainerState
createState
()
=>
new
_BorderContainerState
();
}
class
_BorderContainerState
extends
State
<
_BorderContainer
>
with
SingleTickerProviderStateMixin
{
AnimationController
_controller
;
Animation
<
double
>
_borderAnimation
;
_InputBorderTween
_border
;
@override
void
initState
()
{
super
.
initState
();
_controller
=
new
AnimationController
(
duration:
_kTransitionDuration
,
vsync:
this
,
);
_borderAnimation
=
new
CurvedAnimation
(
parent:
_controller
,
curve:
_kTransitionCurve
,
);
_border
=
new
_InputBorderTween
(
begin:
widget
.
border
,
end:
widget
.
border
,
);
}
@override
void
dispose
()
{
_controller
.
dispose
();
super
.
dispose
();
}
@override
void
didUpdateWidget
(
_BorderContainer
oldWidget
)
{
super
.
didUpdateWidget
(
oldWidget
);
if
(
widget
.
border
!=
oldWidget
.
border
)
{
_border
=
new
_InputBorderTween
(
begin:
oldWidget
.
border
,
end:
widget
.
border
,
);
_controller
..
value
=
0.0
..
forward
();
}
}
@override
Widget
build
(
BuildContext
context
)
{
return
new
CustomPaint
(
foregroundPainter:
new
_InputBorderPainter
(
repaint:
new
Listenable
.
merge
(<
Listenable
>[
_borderAnimation
,
widget
.
gap
]),
borderAnimation:
_borderAnimation
,
border:
_border
,
gapAnimation:
widget
.
gapAnimation
,
gap:
widget
.
gap
,
textDirection:
Directionality
.
of
(
context
),
),
child:
widget
.
child
,
);
}
}
// Used to "shake" the floating label to the left to the left and right
// when the errorText first appears.
class
_Shaker
extends
AnimatedWidget
{
const
_Shaker
({
Key
key
,
Animation
<
double
>
animation
,
this
.
child
,
})
:
super
(
key:
key
,
listenable:
animation
);
final
Widget
child
;
Animation
<
double
>
get
animation
=>
listenable
;
double
get
translateX
{
const
double
shakeDelta
=
4.0
;
final
double
t
=
animation
.
value
;
if
(
t
<=
0.25
)
return
-
t
*
shakeDelta
;
else
if
(
t
<
0.75
)
return
(
t
-
0.5
)
*
shakeDelta
;
else
return
(
1.0
-
t
)
*
4.0
*
shakeDelta
;
}
@override
Widget
build
(
BuildContext
context
)
{
return
new
Transform
(
transform:
new
Matrix4
.
translationValues
(
translateX
,
0.0
,
0.0
),
child:
child
,
);
}
}
// Display the helper and error text. When the error text appears
// it fades and the helper text fades out. The error text also
// slides upwards a little when it first appears.
class
_HelperError
extends
StatefulWidget
{
const
_HelperError
({
Key
key
,
this
.
textAlign
,
this
.
helperText
,
this
.
helperStyle
,
this
.
errorText
,
this
.
errorStyle
,
})
:
super
(
key:
key
);
final
TextAlign
textAlign
;
final
String
helperText
;
final
TextStyle
helperStyle
;
final
String
errorText
;
final
TextStyle
errorStyle
;
@override
_HelperErrorState
createState
()
=>
new
_HelperErrorState
();
}
class
_HelperErrorState
extends
State
<
_HelperError
>
with
SingleTickerProviderStateMixin
{
// If the height of this widget and the counter are zero ("empty") at
// layout time, no space is allocated for the subtext.
static
const
Widget
empty
=
const
SizedBox
();
AnimationController
_controller
;
Widget
_helper
;
Widget
_error
;
@override
void
initState
()
{
super
.
initState
();
_controller
=
new
AnimationController
(
duration:
_kTransitionDuration
,
vsync:
this
,
);
if
(
widget
.
errorText
!=
null
)
{
_error
=
_buildError
();
_controller
.
value
=
1.0
;
}
else
if
(
widget
.
helperText
!=
null
)
{
_helper
=
_buildHelper
();
}
_controller
.
addListener
(
_handleChange
);
}
@override
void
dispose
()
{
_controller
.
dispose
();
super
.
dispose
();
}
void
_handleChange
()
{
setState
(()
{
// The _controller's value has changed.
});
}
@override
void
didUpdateWidget
(
_HelperError
old
)
{
super
.
didUpdateWidget
(
old
);
final
String
errorText
=
widget
.
errorText
;
final
String
helperText
=
widget
.
helperText
;
final
String
oldErrorText
=
old
.
errorText
;
final
String
oldHelperText
=
old
.
helperText
;
if
((
errorText
??
helperText
)
!=
(
oldErrorText
??
oldHelperText
))
{
if
(
errorText
!=
null
)
{
_error
=
_buildError
();
_controller
.
forward
();
}
else
if
(
helperText
!=
null
)
{
_helper
=
_buildHelper
();
_controller
.
reverse
();
}
else
{
_controller
.
reverse
();
}
}
}
Widget
_buildHelper
()
{
assert
(
widget
.
helperText
!=
null
);
return
new
Opacity
(
opacity:
1.0
-
_controller
.
value
,
child:
new
Text
(
widget
.
helperText
,
style:
widget
.
helperStyle
,
textAlign:
widget
.
textAlign
,
overflow:
TextOverflow
.
ellipsis
,
),
);
}
Widget
_buildError
()
{
assert
(
widget
.
errorText
!=
null
);
return
new
Opacity
(
opacity:
_controller
.
value
,
child:
new
FractionalTranslation
(
translation:
new
Tween
<
Offset
>(
begin:
const
Offset
(
0.0
,
-
0.25
),
end:
const
Offset
(
0.0
,
0.0
),
).
evaluate
(
_controller
.
view
),
child:
new
Text
(
widget
.
errorText
,
style:
widget
.
errorStyle
,
textAlign:
widget
.
textAlign
,
overflow:
TextOverflow
.
ellipsis
,
),
),
);
}
@override
Widget
build
(
BuildContext
context
)
{
if
(
_controller
.
isDismissed
)
{
_error
=
null
;
if
(
widget
.
helperText
!=
null
)
{
return
_helper
=
_buildHelper
();
}
else
{
_helper
=
null
;
return
empty
;
}
}
if
(
_controller
.
isCompleted
)
{
_helper
=
null
;
if
(
widget
.
errorText
!=
null
)
{
return
_error
=
_buildError
();
}
else
{
_error
=
null
;
return
empty
;
}
}
if
(
_helper
==
null
&&
widget
.
errorText
!=
null
)
return
_buildError
();
if
(
_error
==
null
&&
widget
.
helperText
!=
null
)
return
_buildHelper
();
if
(
widget
.
errorText
!=
null
)
{
return
new
Stack
(
children:
<
Widget
>[
new
Opacity
(
opacity:
1.0
-
_controller
.
value
,
child:
_helper
,
),
_buildError
(),
],
);
}
if
(
widget
.
helperText
!=
null
)
{
return
new
Stack
(
children:
<
Widget
>[
_buildHelper
(),
new
Opacity
(
opacity:
_controller
.
value
,
child:
_error
,
),
],
);
}
return
empty
;
}
}
// Identifies the children of a _RenderDecorationElement.
enum
_DecorationSlot
{
icon
,
input
,
label
,
hint
,
prefix
,
suffix
,
prefixIcon
,
suffixIcon
,
helperError
,
counter
,
container
,
}
// An analog of InputDecoration for the _Decorator widget.
class
_Decoration
{
const
_Decoration
({
@required
this
.
contentPadding
,
@required
this
.
floatingLabelHeight
,
@required
this
.
floatingLabelProgress
,
this
.
border
,
this
.
borderGap
,
this
.
icon
,
this
.
input
,
this
.
label
,
this
.
hint
,
this
.
prefix
,
this
.
suffix
,
this
.
prefixIcon
,
this
.
suffixIcon
,
this
.
helperError
,
this
.
counter
,
this
.
container
,
})
:
assert
(
contentPadding
!=
null
),
assert
(
floatingLabelHeight
!=
null
),
assert
(
floatingLabelProgress
!=
null
);
final
EdgeInsets
contentPadding
;
final
double
floatingLabelHeight
;
final
double
floatingLabelProgress
;
final
InputBorder
border
;
final
_InputBorderGap
borderGap
;
final
Widget
icon
;
final
Widget
input
;
final
Widget
label
;
final
Widget
hint
;
final
Widget
prefix
;
final
Widget
suffix
;
final
Widget
prefixIcon
;
final
Widget
suffixIcon
;
final
Widget
helperError
;
final
Widget
counter
;
final
Widget
container
;
@override
bool
operator
==(
dynamic
other
)
{
if
(
identical
(
this
,
other
))
return
true
;
if
(
other
.
runtimeType
!=
runtimeType
)
return
false
;
final
_Decoration
typedOther
=
other
;
return
typedOther
.
contentPadding
==
contentPadding
&&
typedOther
.
floatingLabelHeight
==
floatingLabelHeight
&&
typedOther
.
floatingLabelProgress
==
floatingLabelProgress
&&
typedOther
.
border
==
border
&&
typedOther
.
borderGap
==
borderGap
&&
typedOther
.
icon
==
icon
&&
typedOther
.
input
==
input
&&
typedOther
.
label
==
label
&&
typedOther
.
hint
==
hint
&&
typedOther
.
prefix
==
prefix
&&
typedOther
.
suffix
==
suffix
&&
typedOther
.
prefixIcon
==
prefixIcon
&&
typedOther
.
suffixIcon
==
suffixIcon
&&
typedOther
.
helperError
==
helperError
&&
typedOther
.
counter
==
counter
&&
typedOther
.
container
==
container
;
}
@override
int
get
hashCode
{
return
hashValues
(
contentPadding
,
floatingLabelHeight
,
floatingLabelProgress
,
border
,
borderGap
,
icon
,
input
,
label
,
hint
,
prefix
,
suffix
,
prefixIcon
,
suffixIcon
,
helperError
,
counter
,
container
,
);
}
}
// A container for the layout values computed by _RenderDecoration._layout.
// These values are used by _RenderDecoration.performLayout to position
// all of the renderer children of a _RenderDecoration.
class
_RenderDecorationLayout
{
const
_RenderDecorationLayout
({
this
.
boxToBaseline
,
this
.
inputBaseline
,
// for InputBorderType.underline
this
.
outlineBaseline
,
// for InputBorderType.outline
this
.
subtextBaseline
,
this
.
containerHeight
,
this
.
subtextHeight
,
});
final
Map
<
RenderBox
,
double
>
boxToBaseline
;
final
double
inputBaseline
;
final
double
outlineBaseline
;
final
double
subtextBaseline
;
// helper/error counter
final
double
containerHeight
;
final
double
subtextHeight
;
}
// The workhorse: layout and paint a _Decorator widget's _Decoration.
class
_RenderDecoration
extends
RenderBox
{
_RenderDecoration
({
_Decoration
decoration
,
TextDirection
textDirection
,
})
:
_decoration
=
decoration
,
_textDirection
=
textDirection
;
final
Map
<
_DecorationSlot
,
RenderBox
>
slotToChild
=
<
_DecorationSlot
,
RenderBox
>{};
final
Map
<
RenderBox
,
_DecorationSlot
>
childToSlot
=
<
RenderBox
,
_DecorationSlot
>{};
RenderBox
_updateChild
(
RenderBox
oldChild
,
RenderBox
newChild
,
_DecorationSlot
slot
)
{
if
(
oldChild
!=
null
)
{
dropChild
(
oldChild
);
childToSlot
.
remove
(
oldChild
);
slotToChild
.
remove
(
slot
);
}
if
(
newChild
!=
null
)
{
childToSlot
[
newChild
]
=
slot
;
slotToChild
[
slot
]
=
newChild
;
adoptChild
(
newChild
);
}
return
newChild
;
}
RenderBox
_icon
;
RenderBox
get
icon
=>
_icon
;
set
icon
(
RenderBox
value
)
{
_icon
=
_updateChild
(
_icon
,
value
,
_DecorationSlot
.
icon
);
}
RenderBox
_input
;
RenderBox
get
input
=>
_input
;
set
input
(
RenderBox
value
)
{
_input
=
_updateChild
(
_input
,
value
,
_DecorationSlot
.
input
);
}
RenderBox
_label
;
RenderBox
get
label
=>
_label
;
set
label
(
RenderBox
value
)
{
_label
=
_updateChild
(
_label
,
value
,
_DecorationSlot
.
label
);
}
RenderBox
_hint
;
RenderBox
get
hint
=>
_hint
;
set
hint
(
RenderBox
value
)
{
_hint
=
_updateChild
(
_hint
,
value
,
_DecorationSlot
.
hint
);
}
RenderBox
_prefix
;
RenderBox
get
prefix
=>
_prefix
;
set
prefix
(
RenderBox
value
)
{
_prefix
=
_updateChild
(
_prefix
,
value
,
_DecorationSlot
.
prefix
);
}
RenderBox
_suffix
;
RenderBox
get
suffix
=>
_suffix
;
set
suffix
(
RenderBox
value
)
{
_suffix
=
_updateChild
(
_suffix
,
value
,
_DecorationSlot
.
suffix
);
}
RenderBox
_prefixIcon
;
RenderBox
get
prefixIcon
=>
_prefixIcon
;
set
prefixIcon
(
RenderBox
value
)
{
_prefixIcon
=
_updateChild
(
_prefixIcon
,
value
,
_DecorationSlot
.
prefixIcon
);
}
RenderBox
_suffixIcon
;
RenderBox
get
suffixIcon
=>
_suffixIcon
;
set
suffixIcon
(
RenderBox
value
)
{
_suffixIcon
=
_updateChild
(
_suffixIcon
,
value
,
_DecorationSlot
.
suffixIcon
);
}
RenderBox
_helperError
;
RenderBox
get
helperError
=>
_helperError
;
set
helperError
(
RenderBox
value
)
{
_helperError
=
_updateChild
(
_helperError
,
value
,
_DecorationSlot
.
helperError
);
}
RenderBox
_counter
;
RenderBox
get
counter
=>
_counter
;
set
counter
(
RenderBox
value
)
{
_counter
=
_updateChild
(
_counter
,
value
,
_DecorationSlot
.
counter
);
}
RenderBox
_container
;
RenderBox
get
container
=>
_container
;
set
container
(
RenderBox
value
)
{
_container
=
_updateChild
(
_container
,
value
,
_DecorationSlot
.
container
);
}
// The returned list is ordered for hit testing.
Iterable
<
RenderBox
>
get
_children
sync
*{
if
(
icon
!=
null
)
yield
icon
;
if
(
input
!=
null
)
yield
input
;
if
(
prefixIcon
!=
null
)
yield
prefixIcon
;
if
(
suffixIcon
!=
null
)
yield
suffixIcon
;
if
(
prefix
!=
null
)
yield
prefix
;
if
(
suffix
!=
null
)
yield
suffix
;
if
(
label
!=
null
)
yield
label
;
if
(
hint
!=
null
)
yield
hint
;
if
(
helperError
!=
null
)
yield
helperError
;
if
(
counter
!=
null
)
yield
counter
;
if
(
container
!=
null
)
yield
container
;
}
_Decoration
get
decoration
=>
_decoration
;
_Decoration
_decoration
;
set
decoration
(
_Decoration
value
)
{
if
(
_decoration
==
value
)
return
;
_decoration
=
value
;
markNeedsLayout
();
}
TextDirection
get
textDirection
=>
_textDirection
;
TextDirection
_textDirection
;
set
textDirection
(
TextDirection
value
)
{
if
(
_textDirection
==
value
)
return
;
_textDirection
=
value
;
markNeedsLayout
();
}
@override
void
attach
(
PipelineOwner
owner
)
{
super
.
attach
(
owner
);
for
(
RenderBox
child
in
_children
)
child
.
attach
(
owner
);
}
@override
void
detach
()
{
super
.
detach
();
for
(
RenderBox
child
in
_children
)
child
.
detach
();
}
@override
void
redepthChildren
()
{
_children
.
forEach
(
redepthChild
);
}
@override
void
visitChildren
(
RenderObjectVisitor
visitor
)
{
_children
.
forEach
(
visitor
);
}
@override
List
<
DiagnosticsNode
>
debugDescribeChildren
()
{
final
List
<
DiagnosticsNode
>
value
=
<
DiagnosticsNode
>[];
void
add
(
RenderBox
child
,
String
name
)
{
if
(
child
!=
null
)
value
.
add
(
input
.
toDiagnosticsNode
(
name:
name
));
}
add
(
icon
,
'icon'
);
add
(
input
,
'input'
);
add
(
label
,
'label'
);
add
(
hint
,
'hint'
);
add
(
prefix
,
'prefix'
);
add
(
suffix
,
'suffix'
);
add
(
prefixIcon
,
'prefixIcon'
);
add
(
suffixIcon
,
'suffixIcon'
);
add
(
helperError
,
'helperError'
);
add
(
counter
,
'counter'
);
add
(
container
,
'container'
);
return
value
;
}
@override
bool
get
sizedByParent
=>
false
;
static
double
_minWidth
(
RenderBox
box
,
double
height
)
{
return
box
==
null
?
0.0
:
box
.
getMinIntrinsicWidth
(
height
);
}
static
double
_maxWidth
(
RenderBox
box
,
double
height
)
{
return
box
==
null
?
0.0
:
box
.
getMaxIntrinsicWidth
(
height
);
}
static
double
_minHeight
(
RenderBox
box
,
double
width
)
{
return
box
==
null
?
0.0
:
box
.
getMinIntrinsicWidth
(
width
);
}
static
Size
_boxSize
(
RenderBox
box
)
=>
box
==
null
?
Size
.
zero
:
box
.
size
;
static
BoxParentData
_boxParentData
(
RenderBox
box
)
=>
box
.
parentData
;
EdgeInsets
get
contentPadding
=>
decoration
.
contentPadding
;
// Returns a value used by performLayout to position all
// of the renderers. This method applies layout to all of the renderers
// except the container. For convenience, the container is laid out
// in performLayout().
_RenderDecorationLayout
_layout
(
BoxConstraints
layoutConstraints
)
{
final
Map
<
RenderBox
,
double
>
boxToBaseline
=
<
RenderBox
,
double
>{};
BoxConstraints
boxConstraints
=
layoutConstraints
.
loosen
();
double
aboveBaseline
=
0.0
;
double
belowBaseline
=
0.0
;
void
layoutLineBox
(
RenderBox
box
)
{
if
(
box
==
null
)
return
;
box
.
layout
(
boxConstraints
,
parentUsesSize:
true
);
final
double
baseline
=
box
.
getDistanceToBaseline
(
TextBaseline
.
alphabetic
);
assert
(
baseline
!=
null
&&
baseline
>=
0.0
);
boxToBaseline
[
box
]
=
baseline
;
aboveBaseline
=
math
.
max
(
baseline
,
aboveBaseline
);
belowBaseline
=
math
.
max
(
box
.
size
.
height
-
baseline
,
belowBaseline
);
}
layoutLineBox
(
prefix
);
layoutLineBox
(
suffix
);
if
(
icon
!=
null
)
icon
.
layout
(
boxConstraints
,
parentUsesSize:
true
);
if
(
prefixIcon
!=
null
)
prefixIcon
.
layout
(
boxConstraints
,
parentUsesSize:
true
);
if
(
suffixIcon
!=
null
)
suffixIcon
.
layout
(
boxConstraints
,
parentUsesSize:
true
);
final
double
inputWidth
=
constraints
.
maxWidth
-
(
_boxSize
(
icon
).
width
+
contentPadding
.
left
+
_boxSize
(
prefixIcon
).
width
+
_boxSize
(
prefix
).
width
+
_boxSize
(
suffix
).
width
+
_boxSize
(
suffixIcon
).
width
+
contentPadding
.
right
);
boxConstraints
=
boxConstraints
.
copyWith
(
maxWidth:
inputWidth
);
layoutLineBox
(
hint
);
if
(
label
!=
null
)
// The label is not baseline aligned.
label
.
layout
(
boxConstraints
,
parentUsesSize:
true
);
boxConstraints
=
boxConstraints
.
copyWith
(
minWidth:
inputWidth
);
layoutLineBox
(
input
);
double
inputBaseline
=
contentPadding
.
top
+
aboveBaseline
;
double
containerHeight
=
contentPadding
.
top
+
aboveBaseline
+
belowBaseline
+
contentPadding
.
bottom
;
if
(
label
!=
null
)
{
// floatingLabelHeight includes the vertical gap between the inline
// elements and the floating label.
containerHeight
+=
decoration
.
floatingLabelHeight
;
inputBaseline
+=
decoration
.
floatingLabelHeight
;
}
// Inline text within an outline border is centered within the container
// less 2.0 dps at the top to account for the vertical space occupied
// by the floating label.
final
double
outlineBaseline
=
aboveBaseline
+
(
containerHeight
-
(
2.0
+
aboveBaseline
+
belowBaseline
))
/
2.0
;
double
subtextBaseline
=
0.0
;
double
subtextHeight
=
0.0
;
if
(
helperError
!=
null
||
counter
!=
null
)
{
aboveBaseline
=
0.0
;
belowBaseline
=
0.0
;
layoutLineBox
(
helperError
);
layoutLineBox
(
counter
);
if
(
aboveBaseline
+
belowBaseline
>
0.0
)
{
const
double
subtextGap
=
8.0
;
subtextBaseline
=
containerHeight
+
subtextGap
+
aboveBaseline
;
subtextHeight
=
subtextGap
+
aboveBaseline
+
belowBaseline
;
}
}
return
new
_RenderDecorationLayout
(
boxToBaseline:
boxToBaseline
,
containerHeight:
containerHeight
,
inputBaseline:
inputBaseline
,
outlineBaseline:
outlineBaseline
,
subtextBaseline:
subtextBaseline
,
subtextHeight:
subtextHeight
,
);
}
@override
double
computeMinIntrinsicWidth
(
double
height
)
{
return
_minWidth
(
icon
,
height
)
+
contentPadding
.
left
+
_minWidth
(
prefixIcon
,
height
)
+
_minWidth
(
prefix
,
height
)
+
math
.
max
(
_minWidth
(
input
,
height
),
_minWidth
(
hint
,
height
))
+
_minWidth
(
suffix
,
height
)
+
_minWidth
(
suffixIcon
,
height
)
+
contentPadding
.
right
;
}
@override
double
computeMaxIntrinsicWidth
(
double
height
)
{
return
_maxWidth
(
icon
,
height
)
+
contentPadding
.
left
+
_maxWidth
(
prefixIcon
,
height
)
+
_maxWidth
(
prefix
,
height
)
+
math
.
max
(
_maxWidth
(
input
,
height
),
_maxWidth
(
hint
,
height
))
+
_maxWidth
(
suffix
,
height
)
+
_maxWidth
(
suffixIcon
,
height
)
+
contentPadding
.
right
;
}
double
_lineHeight
(
double
width
,
List
<
RenderBox
>
boxes
)
{
double
height
=
0.0
;
for
(
RenderBox
box
in
boxes
)
{
if
(
box
==
null
)
continue
;
height
=
math
.
max
(
_minHeight
(
box
,
width
),
height
);
}
return
height
;
// TODO(hansmuller): this should compute the overall line height for the
// boxes when they've been baseline-aligned.
// See https://github.com/flutter/flutter/issues/13715
}
@override
double
computeMinIntrinsicHeight
(
double
width
)
{
double
subtextHeight
=
_lineHeight
(
width
,
<
RenderBox
>[
helperError
,
counter
]);
if
(
subtextHeight
>
0.0
)
subtextHeight
+=
8.0
;
return
contentPadding
.
top
+
(
label
==
null
?
0.0
:
decoration
.
floatingLabelHeight
)
+
_lineHeight
(
width
,
<
RenderBox
>[
prefix
,
input
,
suffix
])
+
subtextHeight
+
contentPadding
.
bottom
;
}
@override
double
computeMaxIntrinsicHeight
(
double
width
)
{
return
computeMinIntrinsicHeight
(
width
);
}
@override
double
computeDistanceToActualBaseline
(
TextBaseline
baseline
)
{
assert
(
false
,
'not implemented'
);
return
0.0
;
}
// Records where the label was painted.
Matrix4
_labelTransform
;
@override
void
performLayout
()
{
_labelTransform
=
null
;
final
_RenderDecorationLayout
layout
=
_layout
(
constraints
);
final
double
overallWidth
=
constraints
.
maxWidth
;
final
double
overallHeight
=
layout
.
containerHeight
+
layout
.
subtextHeight
;
if
(
container
!=
null
)
{
final
BoxConstraints
containerConstraints
=
new
BoxConstraints
.
tightFor
(
height:
layout
.
containerHeight
,
width:
overallWidth
-
_boxSize
(
icon
).
width
,
);
container
.
layout
(
containerConstraints
,
parentUsesSize:
true
);
final
double
x
=
textDirection
==
TextDirection
.
rtl
?
0.0
:
_boxSize
(
icon
).
width
;
_boxParentData
(
container
).
offset
=
new
Offset
(
x
,
0.0
);
}
double
height
;
double
centerLayout
(
RenderBox
box
,
double
x
)
{
_boxParentData
(
box
).
offset
=
new
Offset
(
x
,
(
height
-
box
.
size
.
height
)
/
2.0
);
return
box
.
size
.
width
;
}
double
baseline
;
double
baselineLayout
(
RenderBox
box
,
double
x
)
{
_boxParentData
(
box
).
offset
=
new
Offset
(
x
,
baseline
-
layout
.
boxToBaseline
[
box
]);
return
box
.
size
.
width
;
}
final
double
left
=
contentPadding
.
left
;
final
double
right
=
overallWidth
-
contentPadding
.
right
;
height
=
layout
.
containerHeight
;
baseline
=
decoration
.
border
==
null
||
decoration
.
border
.
isOutline
?
layout
.
outlineBaseline
:
layout
.
inputBaseline
;
if
(
icon
!=
null
)
{
final
double
x
=
textDirection
==
TextDirection
.
rtl
?
overallWidth
-
icon
.
size
.
width
:
0.0
;
centerLayout
(
icon
,
x
);
}
switch
(
textDirection
)
{
case
TextDirection
.
rtl
:
{
double
start
=
right
-
_boxSize
(
icon
).
width
;
double
end
=
left
;
if
(
prefixIcon
!=
null
)
start
-=
centerLayout
(
prefixIcon
,
start
-
prefixIcon
.
size
.
width
);
if
(
label
!=
null
)
centerLayout
(
label
,
start
-
label
.
size
.
width
);
if
(
prefix
!=
null
)
start
-=
baselineLayout
(
prefix
,
start
-
prefix
.
size
.
width
);
if
(
input
!=
null
)
baselineLayout
(
input
,
start
-
input
.
size
.
width
);
if
(
hint
!=
null
)
baselineLayout
(
hint
,
start
-
hint
.
size
.
width
);
if
(
suffixIcon
!=
null
)
end
+=
centerLayout
(
suffixIcon
,
end
);
if
(
suffix
!=
null
)
end
+=
baselineLayout
(
suffix
,
end
);
break
;
}
case
TextDirection
.
ltr
:
{
double
start
=
left
+
_boxSize
(
icon
).
width
;
double
end
=
right
;
if
(
prefixIcon
!=
null
)
start
+=
centerLayout
(
prefixIcon
,
start
);
if
(
label
!=
null
)
centerLayout
(
label
,
start
);
if
(
prefix
!=
null
)
start
+=
baselineLayout
(
prefix
,
start
);
if
(
input
!=
null
)
baselineLayout
(
input
,
start
);
if
(
hint
!=
null
)
baselineLayout
(
hint
,
start
);
if
(
suffixIcon
!=
null
)
end
-=
centerLayout
(
suffixIcon
,
end
-
suffixIcon
.
size
.
width
);
if
(
suffix
!=
null
)
end
-=
baselineLayout
(
suffix
,
end
-
suffix
.
size
.
width
);
break
;
}
}
if
(
helperError
!=
null
||
counter
!=
null
)
{
height
=
layout
.
subtextHeight
;
baseline
=
layout
.
subtextBaseline
;
switch
(
textDirection
)
{
case
TextDirection
.
rtl
:
if
(
helperError
!=
null
)
baselineLayout
(
helperError
,
right
-
helperError
.
size
.
width
-
_boxSize
(
icon
).
width
);
if
(
counter
!=
null
)
baselineLayout
(
counter
,
left
);
break
;
case
TextDirection
.
ltr
:
if
(
helperError
!=
null
)
baselineLayout
(
helperError
,
left
+
_boxSize
(
icon
).
width
);
if
(
counter
!=
null
)
baselineLayout
(
counter
,
right
-
counter
.
size
.
width
);
break
;
}
}
if
(
label
!=
null
)
{
decoration
.
borderGap
.
start
=
textDirection
==
TextDirection
.
rtl
?
_boxParentData
(
label
).
offset
.
dx
+
label
.
size
.
width
:
_boxParentData
(
label
).
offset
.
dx
;
decoration
.
borderGap
.
extent
=
label
.
size
.
width
*
0.75
;
}
else
{
decoration
.
borderGap
.
start
=
null
;
decoration
.
borderGap
.
extent
=
0.0
;
}
size
=
constraints
.
constrain
(
new
Size
(
overallWidth
,
overallHeight
));
assert
(
size
.
width
==
constraints
.
constrainWidth
(
overallWidth
));
assert
(
size
.
height
==
constraints
.
constrainHeight
(
overallHeight
));
}
void
_paintLabel
(
PaintingContext
context
,
Offset
offset
)
{
context
.
paintChild
(
label
,
offset
);
}
@override
void
paint
(
PaintingContext
context
,
Offset
offset
)
{
void
doPaint
(
RenderBox
child
)
{
if
(
child
!=
null
)
context
.
paintChild
(
child
,
_boxParentData
(
child
).
offset
+
offset
);
}
doPaint
(
container
);
if
(
label
!=
null
)
{
final
Offset
labelOffset
=
_boxParentData
(
label
).
offset
;
final
double
labelHeight
=
label
.
size
.
height
;
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
double
floatingY
=
decoration
.
border
.
isOutline
?
-
labelHeight
*
0.25
:
contentPadding
.
top
;
final
double
scale
=
lerpDouble
(
1.0
,
0.75
,
t
);
final
double
dx
=
textDirection
==
TextDirection
.
rtl
?
labelOffset
.
dx
+
label
.
size
.
width
*
(
1.0
-
scale
)
// origin is on the right
:
labelOffset
.
dx
;
// origin on the left
final
double
dy
=
lerpDouble
(
0.0
,
floatingY
-
labelOffset
.
dy
,
t
);
_labelTransform
=
new
Matrix4
.
identity
()
..
translate
(
dx
,
labelOffset
.
dy
+
dy
)
..
scale
(
scale
);
context
.
pushTransform
(
needsCompositing
,
offset
,
_labelTransform
,
_paintLabel
);
}
doPaint
(
icon
);
doPaint
(
prefix
);
doPaint
(
suffix
);
doPaint
(
prefixIcon
);
doPaint
(
suffixIcon
);
doPaint
(
hint
);
doPaint
(
input
);
doPaint
(
helperError
);
doPaint
(
counter
);
}
@override
bool
hitTestSelf
(
Offset
position
)
=>
true
;
@override
bool
hitTestChildren
(
HitTestResult
result
,
{
@required
Offset
position
})
{
assert
(
position
!=
null
);
for
(
RenderBox
child
in
_children
)
{
// TODO(hansmuller): label must be handled specially since we've transformed it
if
(
child
.
hitTest
(
result
,
position:
position
-
_boxParentData
(
child
).
offset
))
return
true
;
}
return
false
;
}
@override
void
applyPaintTransform
(
RenderObject
child
,
Matrix4
transform
)
{
if
(
child
==
label
&&
_labelTransform
!=
null
)
{
final
Offset
labelOffset
=
_boxParentData
(
label
).
offset
;
transform
..
multiply
(
_labelTransform
)
..
translate
(-
labelOffset
.
dx
,
-
labelOffset
.
dy
);
}
super
.
applyPaintTransform
(
child
,
transform
);
}
}
class
_RenderDecorationElement
extends
RenderObjectElement
{
_RenderDecorationElement
(
_Decorator
widget
)
:
super
(
widget
);
final
Map
<
_DecorationSlot
,
Element
>
slotToChild
=
<
_DecorationSlot
,
Element
>{};
final
Map
<
Element
,
_DecorationSlot
>
childToSlot
=
<
Element
,
_DecorationSlot
>{};
@override
_Decorator
get
widget
=>
super
.
widget
;
@override
_RenderDecoration
get
renderObject
=>
super
.
renderObject
;
@override
void
visitChildren
(
ElementVisitor
visitor
)
{
slotToChild
.
values
.
forEach
(
visitor
);
}
@override
void
forgetChild
(
Element
child
)
{
assert
(
slotToChild
.
values
.
contains
(
child
));
assert
(
childToSlot
.
keys
.
contains
(
child
));
final
_DecorationSlot
slot
=
childToSlot
[
child
];
childToSlot
.
remove
(
child
);
slotToChild
.
remove
(
slot
);
}
void
_mountChild
(
Widget
widget
,
_DecorationSlot
slot
)
{
final
Element
oldChild
=
slotToChild
[
slot
];
final
Element
newChild
=
updateChild
(
oldChild
,
widget
,
slot
);
if
(
oldChild
!=
null
)
{
slotToChild
.
remove
(
slot
);
childToSlot
.
remove
(
oldChild
);
}
if
(
newChild
!=
null
)
{
slotToChild
[
slot
]
=
newChild
;
childToSlot
[
newChild
]
=
slot
;
}
}
@override
void
mount
(
Element
parent
,
dynamic
newSlot
)
{
super
.
mount
(
parent
,
newSlot
);
_mountChild
(
widget
.
decoration
.
icon
,
_DecorationSlot
.
icon
);
_mountChild
(
widget
.
decoration
.
input
,
_DecorationSlot
.
input
);
_mountChild
(
widget
.
decoration
.
label
,
_DecorationSlot
.
label
);
_mountChild
(
widget
.
decoration
.
hint
,
_DecorationSlot
.
hint
);
_mountChild
(
widget
.
decoration
.
prefix
,
_DecorationSlot
.
prefix
);
_mountChild
(
widget
.
decoration
.
suffix
,
_DecorationSlot
.
suffix
);
_mountChild
(
widget
.
decoration
.
prefixIcon
,
_DecorationSlot
.
prefixIcon
);
_mountChild
(
widget
.
decoration
.
suffixIcon
,
_DecorationSlot
.
suffixIcon
);
_mountChild
(
widget
.
decoration
.
helperError
,
_DecorationSlot
.
helperError
);
_mountChild
(
widget
.
decoration
.
counter
,
_DecorationSlot
.
counter
);
_mountChild
(
widget
.
decoration
.
container
,
_DecorationSlot
.
container
);
}
void
_updateChild
(
Widget
widget
,
_DecorationSlot
slot
)
{
final
Element
oldChild
=
slotToChild
[
slot
];
final
Element
newChild
=
updateChild
(
oldChild
,
widget
,
slot
);
if
(
oldChild
!=
null
)
{
childToSlot
.
remove
(
oldChild
);
slotToChild
.
remove
(
slot
);
}
if
(
newChild
!=
null
)
{
slotToChild
[
slot
]
=
newChild
;
childToSlot
[
newChild
]
=
slot
;
}
}
@override
void
update
(
_Decorator
newWidget
)
{
super
.
update
(
newWidget
);
assert
(
widget
==
newWidget
);
_updateChild
(
widget
.
decoration
.
icon
,
_DecorationSlot
.
icon
);
_updateChild
(
widget
.
decoration
.
input
,
_DecorationSlot
.
input
);
_updateChild
(
widget
.
decoration
.
label
,
_DecorationSlot
.
label
);
_updateChild
(
widget
.
decoration
.
hint
,
_DecorationSlot
.
hint
);
_updateChild
(
widget
.
decoration
.
prefix
,
_DecorationSlot
.
prefix
);
_updateChild
(
widget
.
decoration
.
suffix
,
_DecorationSlot
.
suffix
);
_updateChild
(
widget
.
decoration
.
prefixIcon
,
_DecorationSlot
.
prefixIcon
);
_updateChild
(
widget
.
decoration
.
suffixIcon
,
_DecorationSlot
.
suffixIcon
);
_updateChild
(
widget
.
decoration
.
helperError
,
_DecorationSlot
.
helperError
);
_updateChild
(
widget
.
decoration
.
counter
,
_DecorationSlot
.
counter
);
_updateChild
(
widget
.
decoration
.
container
,
_DecorationSlot
.
container
);
}
void
_updateRenderObject
(
RenderObject
child
,
_DecorationSlot
slot
)
{
switch
(
slot
)
{
case
_DecorationSlot
.
icon
:
renderObject
.
icon
=
child
;
break
;
case
_DecorationSlot
.
input
:
renderObject
.
input
=
child
;
break
;
case
_DecorationSlot
.
label
:
renderObject
.
label
=
child
;
break
;
case
_DecorationSlot
.
hint
:
renderObject
.
hint
=
child
;
break
;
case
_DecorationSlot
.
prefix
:
renderObject
.
prefix
=
child
;
break
;
case
_DecorationSlot
.
suffix
:
renderObject
.
suffix
=
child
;
break
;
case
_DecorationSlot
.
prefixIcon
:
renderObject
.
prefixIcon
=
child
;
break
;
case
_DecorationSlot
.
suffixIcon
:
renderObject
.
suffixIcon
=
child
;
break
;
case
_DecorationSlot
.
helperError
:
renderObject
.
helperError
=
child
;
break
;
case
_DecorationSlot
.
counter
:
renderObject
.
counter
=
child
;
break
;
case
_DecorationSlot
.
container
:
renderObject
.
container
=
child
;
break
;
}
}
@override
void
insertChildRenderObject
(
RenderObject
child
,
dynamic
slotValue
)
{
assert
(
child
is
RenderBox
);
assert
(
slotValue
is
_DecorationSlot
);
final
_DecorationSlot
slot
=
slotValue
;
_updateRenderObject
(
child
,
slot
);
assert
(
renderObject
.
childToSlot
.
keys
.
contains
(
child
));
assert
(
renderObject
.
slotToChild
.
keys
.
contains
(
slot
));
}
@override
void
removeChildRenderObject
(
RenderObject
child
)
{
assert
(
child
is
RenderBox
);
assert
(
renderObject
.
childToSlot
.
keys
.
contains
(
child
));
_updateRenderObject
(
null
,
renderObject
.
childToSlot
[
child
]);
assert
(!
renderObject
.
childToSlot
.
keys
.
contains
(
child
));
assert
(!
renderObject
.
slotToChild
.
keys
.
contains
(
slot
));
}
@override
void
moveChildRenderObject
(
RenderObject
child
,
dynamic
slotValue
)
{
assert
(
false
,
'not reachable'
);
}
}
class
_Decorator
extends
RenderObjectWidget
{
const
_Decorator
({
Key
key
,
this
.
decoration
,
})
:
super
(
key:
key
);
final
_Decoration
decoration
;
@override
_RenderDecorationElement
createElement
()
=>
new
_RenderDecorationElement
(
this
);
@override
_RenderDecoration
createRenderObject
(
BuildContext
context
)
{
return
new
_RenderDecoration
(
decoration:
decoration
,
textDirection:
Directionality
.
of
(
context
),
);
}
@override
void
updateRenderObject
(
BuildContext
context
,
_RenderDecoration
renderObject
)
{
renderObject
..
decoration
=
decoration
..
textDirection
=
Directionality
.
of
(
context
);
}
}
/// Defines the appearance of a Material Design text field.
///
/// [InputDecorator] displays the visual elements of a Material Design text
/// field around its input [child]. The visual elements themselves are defined
/// by an [InputDecoration] object and their layout and appearance depend
/// on the `baseStyle`, `textAlign`, `isFocused`, and `isEmpty` parameters.
///
/// [TextField] uses this widget to decorate its [EditableText] child.
///
/// [InputDecorator] can be used to create widgets that look and behave like a
/// [TextField] but support other kinds of input.
///
/// Requires one of its ancestors to be a [Material] widget.
///
/// See also:
///
/// * [TextField], which uses an [InputDecorator] to display a border,
/// labels, and icons, around its [EditableText] child.
/// * [Decoration] and [DecoratedBox], for drawing arbitrary decorations
/// around other widgets.
class
InputDecorator
extends
StatefulWidget
{
/// Creates a widget that displays a border, labels, and icons,
/// for a [TextField].
///
/// The [isFocused] and [isEmpty] arguments must not be null.
const
InputDecorator
({
Key
key
,
@required
this
.
decoration
,
this
.
baseStyle
,
this
.
textAlign
,
this
.
isFocused
:
false
,
this
.
isEmpty
:
false
,
this
.
child
,
})
:
assert
(
isFocused
!=
null
),
assert
(
isEmpty
!=
null
),
super
(
key:
key
);
/// The text and styles to use when decorating the child.
final
InputDecoration
decoration
;
/// The style on which to base the label, hint, counter, and error styles
/// if the [decoration] does not provide explicit styles.
///
/// If null, defaults to a text style from the current [Theme].
final
TextStyle
baseStyle
;
/// How the text in the decoration should be aligned horizontally.
final
TextAlign
textAlign
;
/// Whether the input field has focus.
///
/// Determines the position of the label text and the color and weight
/// of the border.
///
/// Defaults to false.
final
bool
isFocused
;
/// Whether the input field is empty.
///
/// Determines the position of the label text and whether to display the hint
/// text.
///
/// Defaults to false.
final
bool
isEmpty
;
/// The widget below this widget in the tree.
///
/// Typically an [EditableText], [DropdownButton], or [InkWell].
final
Widget
child
;
bool
get
_labelIsFloating
=>
!
isEmpty
||
isFocused
;
@override
_InputDecoratorState
createState
()
=>
new
_InputDecoratorState
();
/// The RenderBox that defines this decorator's "container". That's the
/// area which is filled if [InputDecoration.isFilled] is true. It's the area
/// adjacent to [InputDecoration.icon] and above the widgets that contain
/// [InputDecoration.helperText], [InputDecoration.errorText], and
/// [InputDecoration.counterText].
///
/// [TextField] renders ink splashes within the container.
static
RenderBox
containerOf
(
BuildContext
context
)
{
final
_RenderDecoration
result
=
context
.
ancestorRenderObjectOfType
(
const
TypeMatcher
<
_RenderDecoration
>());
return
result
?.
container
;
}
@override
void
debugFillProperties
(
DiagnosticPropertiesBuilder
description
)
{
super
.
debugFillProperties
(
description
);
description
.
add
(
new
DiagnosticsProperty
<
InputDecoration
>(
'decoration'
,
decoration
));
description
.
add
(
new
DiagnosticsProperty
<
TextStyle
>(
'baseStyle'
,
baseStyle
,
defaultValue:
null
));
description
.
add
(
new
DiagnosticsProperty
<
bool
>(
'isFocused'
,
isFocused
));
description
.
add
(
new
DiagnosticsProperty
<
bool
>(
'isEmpty'
,
isEmpty
));
}
}
class
_InputDecoratorState
extends
State
<
InputDecorator
>
with
TickerProviderStateMixin
{
AnimationController
_floatingLabelController
;
AnimationController
_shakingLabelController
;
final
_InputBorderGap
_borderGap
=
new
_InputBorderGap
();
@override
void
initState
()
{
super
.
initState
();
_floatingLabelController
=
new
AnimationController
(
duration:
_kTransitionDuration
,
vsync:
this
,
value:
widget
.
_labelIsFloating
?
1.0
:
0.0
,
);
_floatingLabelController
.
addListener
(
_handleChange
);
_shakingLabelController
=
new
AnimationController
(
duration:
_kTransitionDuration
,
vsync:
this
,
);
}
@override
void
dispose
()
{
_floatingLabelController
.
dispose
();
_shakingLabelController
.
dispose
();
super
.
dispose
();
}
void
_handleChange
()
{
setState
(()
{
// The _floatingLabelController's value has changed.
});
}
InputDecoration
get
decoration
=>
widget
.
decoration
;
TextAlign
get
textAlign
=>
widget
.
textAlign
;
bool
get
isFocused
=>
widget
.
isFocused
;
bool
get
isEmpty
=>
widget
.
isEmpty
;
@override
void
didUpdateWidget
(
InputDecorator
old
)
{
super
.
didUpdateWidget
(
old
);
if
(
widget
.
_labelIsFloating
!=
old
.
_labelIsFloating
)
{
if
(
widget
.
_labelIsFloating
)
_floatingLabelController
.
forward
();
else
_floatingLabelController
.
reverse
();
}
final
String
errorText
=
decoration
.
errorText
;
final
String
oldErrorText
=
old
.
decoration
.
errorText
;
if
(
_floatingLabelController
.
isCompleted
&&
errorText
!=
null
&&
errorText
!=
oldErrorText
)
{
_shakingLabelController
..
value
=
0.0
..
forward
();
}
}
Color
_getActiveColor
(
ThemeData
themeData
)
{
if
(
isFocused
)
{
switch
(
themeData
.
brightness
)
{
case
Brightness
.
dark
:
return
themeData
.
accentColor
;
case
Brightness
.
light
:
return
themeData
.
primaryColor
;
}
}
return
themeData
.
hintColor
;
}
Color
_getFillColor
(
ThemeData
themeData
)
{
if
(!
decoration
.
filled
)
return
Colors
.
transparent
;
if
(
decoration
.
fillColor
!=
null
)
return
decoration
.
fillColor
;
// dark theme: 10% white (enabled), 5% white (disabled)
// light theme: 4% black (enabled), 2% black (disabled)
const
Color
darkEnabled
=
const
Color
(
0x1AFFFFFF
);
const
Color
darkDisabled
=
const
Color
(
0x0DFFFFFF
);
const
Color
lightEnabled
=
const
Color
(
0x0A000000
);
const
Color
lightDisabled
=
const
Color
(
0x05000000
);
switch
(
themeData
.
brightness
)
{
case
Brightness
.
dark
:
return
decoration
.
enabled
?
darkEnabled
:
darkDisabled
;
case
Brightness
.
light
:
return
decoration
.
enabled
?
lightEnabled
:
lightDisabled
;
}
return
lightEnabled
;
}
// True if the label will be shown and the hint will not.
// If we're not focused, there's no value, and labelText was provided,
// then the label appears where the hint would.
bool
get
_hasInlineLabel
=>
!
isFocused
&&
isEmpty
&&
decoration
.
labelText
!=
null
;
// The style for the inline label or hint when they're displayed "inline", i.e.
// when they appear in place of the empty text field.
TextStyle
_getInlineLabelStyle
(
ThemeData
themeData
)
{
return
themeData
.
textTheme
.
subhead
.
merge
(
widget
.
baseStyle
)
.
copyWith
(
color:
themeData
.
hintColor
)
.
merge
(
decoration
.
hintStyle
);
}
TextStyle
_getFloatingLabelStyle
(
ThemeData
themeData
)
{
final
Color
color
=
decoration
.
errorText
!=
null
?
decoration
.
errorStyle
?.
color
??
themeData
.
errorColor
:
_getActiveColor
(
themeData
);
final
TextStyle
style
=
themeData
.
textTheme
.
subhead
.
merge
(
widget
.
baseStyle
);
return
style
.
copyWith
(
color:
color
)
.
merge
(
decoration
.
labelStyle
);
}
TextStyle
_getHelperStyle
(
ThemeData
themeData
)
{
return
themeData
.
textTheme
.
caption
.
copyWith
(
color:
themeData
.
hintColor
).
merge
(
decoration
.
helperStyle
);
}
TextStyle
_getErrorStyle
(
ThemeData
themeData
)
{
return
themeData
.
textTheme
.
caption
.
copyWith
(
color:
themeData
.
errorColor
).
merge
(
decoration
.
errorStyle
);
}
double
get
_borderWeight
{
if
(
decoration
.
isCollapsed
||
decoration
.
border
==
null
||
!
decoration
.
enabled
)
return
0.0
;
return
isFocused
?
2.0
:
1.0
;
}
Color
_getBorderColor
(
ThemeData
themeData
)
{
return
decoration
.
errorText
==
null
?
_getActiveColor
(
themeData
)
:
themeData
.
errorColor
;
}
@override
Widget
build
(
BuildContext
context
)
{
final
ThemeData
themeData
=
Theme
.
of
(
context
);
final
TextStyle
inlineStyle
=
_getInlineLabelStyle
(
themeData
);
final
Widget
hint
=
decoration
.
hintText
==
null
?
null
:
new
AnimatedOpacity
(
opacity:
(
isEmpty
&&
!
_hasInlineLabel
)
?
1.0
:
0.0
,
duration:
_kTransitionDuration
,
curve:
_kTransitionCurve
,
child:
new
Text
(
decoration
.
hintText
,
style:
inlineStyle
,
overflow:
TextOverflow
.
ellipsis
,
textAlign:
textAlign
,
),
);
final
InputBorder
border
=
decoration
.
border
==
null
?
null
:
decoration
.
border
.
copyWith
(
borderSide:
new
BorderSide
(
color:
_getBorderColor
(
themeData
),
width:
_borderWeight
,
),
);
final
Widget
containerFill
=
new
DecoratedBox
(
decoration:
new
BoxDecoration
(
color:
_getFillColor
(
themeData
)),
);
final
Widget
container
=
border
==
null
?
containerFill
:
new
_BorderContainer
(
border:
border
,
gap:
_borderGap
,
gapAnimation:
_floatingLabelController
.
view
,
child:
containerFill
,
);
final
Widget
label
=
decoration
.
labelText
==
null
?
null
:
new
_Shaker
(
animation:
_shakingLabelController
.
view
,
child:
new
AnimatedDefaultTextStyle
(
duration:
_kTransitionDuration
,
curve:
_kTransitionCurve
,
style:
widget
.
_labelIsFloating
?
_getFloatingLabelStyle
(
themeData
)
:
_getInlineLabelStyle
(
themeData
),
child:
new
Text
(
decoration
.
labelText
,
overflow:
TextOverflow
.
ellipsis
,
textAlign:
textAlign
,
),
),
);
final
Widget
prefix
=
decoration
.
prefixText
==
null
?
null
:
new
AnimatedOpacity
(
duration:
_kTransitionDuration
,
curve:
_kTransitionCurve
,
opacity:
widget
.
_labelIsFloating
?
1.0
:
0.0
,
child:
new
Text
(
decoration
.
prefixText
,
style:
decoration
.
prefixStyle
??
inlineStyle
),
);
final
Widget
suffix
=
decoration
.
suffixText
==
null
?
null
:
new
AnimatedOpacity
(
duration:
_kTransitionDuration
,
curve:
_kTransitionCurve
,
opacity:
widget
.
_labelIsFloating
?
1.0
:
0.0
,
child:
new
Text
(
decoration
.
suffixText
,
style:
decoration
.
suffixStyle
??
inlineStyle
),
);
final
Color
activeColor
=
_getActiveColor
(
themeData
);
final
double
iconSize
=
decoration
.
isDense
?
18.0
:
24.0
;
final
Color
iconColor
=
isFocused
?
activeColor
:
Colors
.
black45
;
final
Widget
icon
=
decoration
.
icon
==
null
?
null
:
new
Padding
(
padding:
const
EdgeInsetsDirectional
.
only
(
end:
16.0
),
child:
IconTheme
.
merge
(
data:
new
IconThemeData
(
color:
iconColor
,
size:
iconSize
,
),
child:
decoration
.
icon
,
),
);
final
Widget
prefixIcon
=
decoration
.
prefixIcon
==
null
?
null
:
IconTheme
.
merge
(
data:
new
IconThemeData
(
color:
iconColor
,
size:
iconSize
,
),
child:
decoration
.
prefixIcon
,
);
final
Widget
suffixIcon
=
decoration
.
suffixIcon
==
null
?
null
:
IconTheme
.
merge
(
data:
new
IconThemeData
(
color:
iconColor
,
size:
iconSize
,
),
child:
decoration
.
suffixIcon
,
);
final
Widget
helperError
=
new
_HelperError
(
textAlign:
textAlign
,
helperText:
decoration
.
helperText
,
helperStyle:
_getHelperStyle
(
themeData
),
errorText:
decoration
.
errorText
,
errorStyle:
_getErrorStyle
(
themeData
),
);
final
Widget
counter
=
decoration
.
counterText
==
null
?
null
:
new
Text
(
decoration
.
counterText
,
style:
_getHelperStyle
(
themeData
).
merge
(
decoration
.
counterStyle
),
textAlign:
textAlign
==
TextAlign
.
end
?
TextAlign
.
start
:
TextAlign
.
end
,
overflow:
TextOverflow
.
ellipsis
,
);
EdgeInsets
contentPadding
;
double
floatingLabelHeight
;
if
(
decoration
.
isCollapsed
)
{
floatingLabelHeight
=
0.0
;
contentPadding
=
decoration
.
contentPadding
??
EdgeInsets
.
zero
;
}
else
if
(
decoration
.
border
==
null
||
!
decoration
.
border
.
isOutline
)
{
// 4.0: the vertical gap between the inline elements and the floating label.
floatingLabelHeight
=
4.0
+
0.75
*
inlineStyle
.
fontSize
;
if
(
decoration
.
filled
)
{
contentPadding
=
decoration
.
contentPadding
??
(
decoration
.
isDense
?
const
EdgeInsets
.
fromLTRB
(
12.0
,
8.0
,
12.0
,
8.0
)
:
const
EdgeInsets
.
fromLTRB
(
12.0
,
12.0
,
12.0
,
12.0
));
}
else
{
// Not left or right padding for underline borders that aren't filled
// is a small concession to backwards compatibility. This eliminates
// the most noticeable layout change introduced by #13734.
contentPadding
=
decoration
.
contentPadding
??
(
decoration
.
isDense
?
const
EdgeInsets
.
fromLTRB
(
0.0
,
8.0
,
0.0
,
8.0
)
:
const
EdgeInsets
.
fromLTRB
(
0.0
,
12.0
,
0.0
,
12.0
));
}
}
else
{
floatingLabelHeight
=
0.0
;
contentPadding
=
decoration
.
contentPadding
??
(
decoration
.
isDense
?
const
EdgeInsets
.
fromLTRB
(
12.0
,
20.0
,
12.0
,
12.0
)
:
const
EdgeInsets
.
fromLTRB
(
12.0
,
24.0
,
12.0
,
16.0
));
}
return
new
_Decorator
(
decoration:
new
_Decoration
(
contentPadding:
contentPadding
,
floatingLabelHeight:
floatingLabelHeight
,
floatingLabelProgress:
_floatingLabelController
.
value
,
border:
decoration
.
border
,
borderGap:
_borderGap
,
icon:
icon
,
input:
widget
.
child
,
label:
label
,
hint:
hint
,
prefix:
prefix
,
suffix:
suffix
,
prefixIcon:
prefixIcon
,
suffixIcon:
suffixIcon
,
helperError:
helperError
,
counter:
counter
,
container:
container
,
),
);
}
}
/// Text and styles used to label an input field.
/// The border, labels, icons, and styles used to decorate a Material
/// Design text field.
///
/// The [TextField] and [InputDecorator] classes use [InputDecoration] objects
/// to describe their decoration. (In fact, this class is merely the
...
...
@@ -28,16 +1639,16 @@ class _InputDecoratorChildGlobalKey extends GlobalObjectKey {
/// * [TextField], which is a text input widget that uses an
/// [InputDecoration].
/// * [InputDecorator], which is a widget that draws an [InputDecoration]
/// around an
arbitrary
child widget.
/// * [Decoration] and [DecoratedBox], for drawing
arbitrary decoration
s
/// around
other widgets
.
/// around an
input
child widget.
/// * [Decoration] and [DecoratedBox], for drawing
borders and background
s
/// around
a child widget
.
@immutable
class
InputDecoration
{
/// Creates a bundle of text and styles used to label an input field.
/// Creates a bundle of the border, labels, icons, and styles used to
/// decorate a Material Design text field.
///
/// Sets the [isCollapsed] property to false. To create a decoration that does
/// not reserve space for [labelText] or [errorText], use
/// [InputDecoration.collapsed].
/// The [isDense], [filled], and [enabled] arguments must not
/// be null.
const
InputDecoration
({
this
.
icon
,
this
.
labelText
,
...
...
@@ -49,25 +1660,39 @@ class InputDecoration {
this
.
errorText
,
this
.
errorStyle
,
this
.
isDense
:
false
,
this
.
hideDivider
:
false
,
this
.
contentPadding
,
this
.
prefixIcon
,
this
.
prefixText
,
this
.
prefixStyle
,
this
.
suffixText
,
this
.
suffixIcon
,
this
.
suffixStyle
,
this
.
counterText
,
this
.
counterStyle
,
})
:
isCollapsed
=
false
;
/// Creates a decoration that is the same size as the input field.
this
.
filled
:
false
,
this
.
fillColor
,
this
.
border
:
const
UnderlineInputBorder
(),
this
.
enabled
:
true
,
})
:
assert
(
isDense
!=
null
),
assert
(
filled
!=
null
),
assert
(
enabled
!=
null
),
isCollapsed
=
false
;
/// Defines an [InputDecorator] that is the same size as the input field.
///
/// This type of input decoration does not include a divider or an icon and
/// does not reserve space for [labelText] or [errorText].
/// This type of input decoration only includes the border.
///
/// Sets the [isCollapsed] property to true.
const
InputDecoration
.
collapsed
({
@required
this
.
hintText
,
this
.
hintStyle
,
})
:
icon
=
null
,
this
.
filled
:
false
,
this
.
fillColor
,
this
.
border
:
const
UnderlineInputBorder
(),
this
.
enabled
:
true
,
})
:
assert
(
filled
!=
null
),
assert
(
enabled
!=
null
),
icon
=
null
,
labelText
=
null
,
labelStyle
=
null
,
helperText
=
null
,
...
...
@@ -75,21 +1700,31 @@ class InputDecoration {
errorText
=
null
,
errorStyle
=
null
,
isDense
=
false
,
contentPadding
=
EdgeInsets
.
zero
,
isCollapsed
=
true
,
hideDivider
=
true
,
prefixIcon
=
null
,
prefixText
=
null
,
prefixStyle
=
null
,
suffixIcon
=
null
,
suffixText
=
null
,
suffixStyle
=
null
,
counterText
=
null
,
counterStyle
=
null
;
/// An icon to show before the input field.
/// An icon to show before the input field and outside of the decoration's
/// container.
///
/// The size and color of the icon is configured automatically using an
/// [IconTheme] and therefore does not need to be explicitly given in the
/// icon widget.
///
/// The trailing edge of the icon is padded by 16dps.
///
/// The decoration's container is the area which is filled if [isFilled] is
/// true and bordered per the [border]. It's the area adjacent to
/// [decoration.icon] and above the widgets that contain [helperText],
/// [errorText], and [counterText].
///
/// See [Icon], [ImageIcon].
final
Widget
icon
;
...
...
@@ -108,14 +1743,14 @@ class InputDecoration {
/// When the [labelText] is on top of the input field, the text uses the
/// [hintStyle] instead.
///
/// If null, defaults
of
a value derived from the base [TextStyle] for the
/// If null, defaults
to
a value derived from the base [TextStyle] for the
/// input field and the current [Theme].
final
TextStyle
labelStyle
;
/// Text that provides context about the
field’s value, such as how the value
/// will be used.
/// Text that provides context about the
input [child]'s value, such as how
///
the value
will be used.
///
/// If non-null, the text is displayed below the input
field
, in the same
/// If non-null, the text is displayed below the input
[child]
, in the same
/// location as [errorText]. If a non-null [errorText] value is specified then
/// the helper text is not shown.
final
String
helperText
;
...
...
@@ -125,25 +1760,25 @@ class InputDecoration {
/// Text that suggests what sort of input the field accepts.
///
/// Displayed on top of the input field (i.e., at the same location on the
/// screen where text my be entered in the input field) when the input field
/// is empty and either (a) [labelText] is null or (b) the input field has
/// focus.
/// Displayed on top of the input [child] (i.e., at the same location on the
/// screen where text my be entered in the input [child]) when the input
/// [isEmpty] and either (a) [labelText] is null or (b) the input has the focus.
final
String
hintText
;
/// The style to use for the [hintText].
///
/// Also used for the [labelText] when the [labelText] is displayed on
/// top of the input field (i.e., at the same location on the screen where
/// text my be entered in the input
field
).
/// text my be entered in the input
[child]
).
///
/// If null, defaults
of
a value derived from the base [TextStyle] for the
/// If null, defaults
to
a value derived from the base [TextStyle] for the
/// input field and the current [Theme].
final
TextStyle
hintStyle
;
/// Text that appears below the input
field
.
/// Text that appears below the input
[child] and the border
.
///
/// If non-null, the divider that appears below the input field is red.
/// If non-null, the border's color animates to red and the [helperText] is
/// not shown.
final
String
errorText
;
/// The style to use for the [errorText].
...
...
@@ -152,24 +1787,53 @@ class InputDecoration {
/// input field and the current [Theme].
final
TextStyle
errorStyle
;
/// Whether the input
field
is part of a dense form (i.e., uses less vertical
/// Whether the input
[child]
is part of a dense form (i.e., uses less vertical
/// space).
///
/// Defaults to false.
final
bool
isDense
;
/// The padding for the input decoration's container.
///
/// The decoration's container is the area which is filled if [isFilled] is
/// true and bordered per the [border]. It's the area adjacent to
/// [decoration.icon] and above the widgets that contain [helperText],
/// [errorText], and [counterText].
///
/// By default the `contentPadding` reflects [isDense] and the type of the
/// [border]. If [isCollapsed] is true then `contentPadding` is
/// [EdgeInsets.zero].
final
EdgeInsets
contentPadding
;
/// Whether the decoration is the same size as the input field.
///
/// A collapsed decoration cannot have [labelText], [errorText], an [icon], or
/// a divider because those elements require extra space.
/// A collapsed decoration cannot have [labelText], [errorText], an [icon].
///
/// To create a collapsed input decoration, use [InputDecoration..collapsed].
final
bool
isCollapsed
;
/// Whether to hide the divider below the input field and above the error text.
/// An icon that that appears before the [prefixText] and the input and within
/// the decoration's container.
///
/// Defaults to false.
final
bool
hideDivider
;
/// The size and color of the prefix icon is configured automatically using an
/// [IconTheme] and therefore does not need to be explicitly given in the
/// icon widget.
///
/// The prefix icon is not padded. To pad the trailing edge of the prefix icon:
/// ```dart
/// prefixIcon: new Padding(
/// padding: const EdgeInsetsDirectional.only(end: 16.0),
/// child: myIcon,
/// )
/// ```
///
/// The decoration's container is the area which is filled if [isFilled] is
/// true and bordered per the [border]. It's the area adjacent to
/// [decoration.icon] and above the widgets that contain [helperText],
/// [errorText], and [counterText].
///
/// See [Icon], [ImageIcon].
final
Widget
prefixIcon
;
/// Optional text prefix to place on the line before the input.
///
...
...
@@ -182,6 +1846,29 @@ class InputDecoration {
/// If null, defaults to the [hintStyle].
final
TextStyle
prefixStyle
;
/// An icon that that appears after the input and [suffixText] and within
/// the decoration's container.
///
/// The size and color of the suffix icon is configured automatically using an
/// [IconTheme] and therefore does not need to be explicitly given in the
/// icon widget.
///
/// The suffix icon is not padded. To pad the leading edge of the prefix icon:
/// ```dart
/// prefixIcon: new Padding(
/// padding: const EdgeInsetsDirectional.only(start: 16.0),
/// child: myIcon,
/// )
/// ```
///
/// The decoration's container is the area which is filled if [isFilled] is
/// true and bordered per the [border]. It's the area adjacent to
/// [decoration.icon] and above the widgets that contain [helperText],
/// [errorText], and [counterText].
///
/// See [Icon], [ImageIcon].
final
Widget
suffixIcon
;
/// Optional text suffix to place on the line after the input.
///
/// Uses the [suffixStyle]. Uses [hintStyle] if [suffixStyle] isn't
...
...
@@ -204,8 +1891,53 @@ class InputDecoration {
/// If null, defaults to the [helperStyle].
final
TextStyle
counterStyle
;
/// Creates a copy of this input decoration but with the given fields replaced
/// with the new values.
/// If true the decoration's container is filled with [fillColor].
///
/// Typically this field set to true if [border] is
/// [const UnderlineInputBorder()].
///
/// The decoration's container is the area which is filled if [isFilled] is
/// true and bordered per the [border]. It's the area adjacent to
/// [decoration.icon] and above the widgets that contain [helperText],
/// [errorText], and [counterText].
///
/// This property is false by default.
final
bool
filled
;
/// The color to fill the decoration's container with, if [filled] is true.
///
/// By default the fillColor is based on the current [Theme].
///
/// The decoration's container is the area which is filled if [isFilled] is
/// true and bordered per the [border]. It's the area adjacent to
/// [decoration.icon] and above the widgets that contain [helperText],
/// [errorText], and [counterText].
final
Color
fillColor
;
/// The border to draw around the decoration's container.
///
/// The decoration's container is the area which is filled if [isFilled] is
/// true and bordered per the [border]. It's the area adjacent to
/// [decoration.icon] and above the widgets that contain [helperText],
/// [errorText], and [counterText].
///
/// The default value of this property is `const UnderlineInputBorder()`.
///
/// See also:
/// * [UnderlineInputBorder], which draws a horizontal line at the
/// bottom of the input decorator's container.
/// * [OutlineInputBorder], an [InputDecorator] border which draws a
/// rounded rectangle around the input decorator's container.
final
InputBorder
border
;
/// If false [helperText],[errorText], and [counterText] are not displayed,
/// and the opacity of the remaining visual elements is reduced.
///
/// This property is true by default.
final
bool
enabled
;
/// Creates a copy of this input decoration with the given fields replaced
/// by the new values.
///
/// Always sets [isCollapsed] to false.
InputDecoration
copyWith
({
...
...
@@ -219,13 +1951,19 @@ class InputDecoration {
String
errorText
,
TextStyle
errorStyle
,
bool
isDense
,
bool
hideDivider
,
EdgeInsets
contentPadding
,
Widget
prefixIcon
,
String
prefixText
,
TextStyle
prefixStyle
,
Widget
suffixIcon
,
String
suffixText
,
TextStyle
suffixStyle
,
String
counterText
,
TextStyle
counterStyle
,
bool
filled
,
Color
fillColor
,
InputBorder
border
,
bool
enabled
,
})
{
return
new
InputDecoration
(
icon:
icon
??
this
.
icon
,
...
...
@@ -238,13 +1976,19 @@ class InputDecoration {
errorText:
errorText
??
this
.
errorText
,
errorStyle:
errorStyle
??
this
.
errorStyle
,
isDense:
isDense
??
this
.
isDense
,
hideDivider:
hideDivider
??
this
.
hideDivider
,
contentPadding:
contentPadding
??
this
.
contentPadding
,
prefixIcon:
prefixIcon
??
this
.
prefixIcon
,
prefixText:
prefixText
??
this
.
prefixText
,
prefixStyle:
prefixStyle
??
this
.
prefixStyle
,
suffixIcon:
suffixIcon
??
this
.
suffixIcon
,
suffixText:
suffixText
??
this
.
suffixText
,
suffixStyle:
suffixStyle
??
this
.
suffixStyle
,
counterText:
counterText
??
this
.
counterText
,
counterStyle:
counterStyle
??
this
.
counterStyle
,
filled:
filled
??
this
.
filled
,
fillColor:
fillColor
??
this
.
fillColor
,
border:
border
??
this
.
border
,
enabled:
enabled
??
this
.
enabled
,
);
}
...
...
@@ -265,14 +2009,20 @@ class InputDecoration {
&&
typedOther
.
errorText
==
errorText
&&
typedOther
.
errorStyle
==
errorStyle
&&
typedOther
.
isDense
==
isDense
&&
typedOther
.
contentPadding
==
contentPadding
&&
typedOther
.
isCollapsed
==
isCollapsed
&&
typedOther
.
hideDivider
==
hideDivider
&&
typedOther
.
prefixIcon
==
prefixIcon
&&
typedOther
.
prefixText
==
prefixText
&&
typedOther
.
prefixStyle
==
prefixStyle
&&
typedOther
.
suffixIcon
==
suffixIcon
&&
typedOther
.
suffixText
==
suffixText
&&
typedOther
.
suffixStyle
==
suffixStyle
&&
typedOther
.
counterText
==
counterText
&&
typedOther
.
counterStyle
==
counterStyle
;
&&
typedOther
.
counterStyle
==
counterStyle
&&
typedOther
.
filled
==
filled
&&
typedOther
.
fillColor
==
fillColor
&&
typedOther
.
border
==
border
&&
typedOther
.
enabled
==
enabled
;
}
@override
...
...
@@ -283,19 +2033,27 @@ class InputDecoration {
labelStyle
,
helperText
,
helperStyle
,
hashValues
(
// Over 20 fields...
hintText
,
hintStyle
,
errorText
,
errorStyle
,
isDense
,
contentPadding
,
isCollapsed
,
hideDivider
,
prefixIcon
,
prefixText
,
prefixStyle
,
suffixIcon
,
suffixText
,
suffixStyle
,
counterText
,
counterStyle
,
filled
,
fillColor
,
border
,
enabled
,
),
);
}
...
...
@@ -314,14 +2072,18 @@ class InputDecoration {
description
.
add
(
'errorText: "
$errorText
"'
);
if
(
isDense
)
description
.
add
(
'isDense:
$isDense
'
);
if
(
contentPadding
!=
null
)
description
.
add
(
'contentPadding:
$contentPadding
'
);
if
(
isCollapsed
)
description
.
add
(
'isCollapsed:
$isCollapsed
'
);
if
(
hideDivider
)
description
.
add
(
'
hideDivider:
$hideDivider
'
);
if
(
prefixIcon
!=
null
)
description
.
add
(
'
prefixIcon:
$prefixIcon
'
);
if
(
prefixText
!=
null
)
description
.
add
(
'prefixText:
$prefixText
'
);
if
(
prefixStyle
!=
null
)
description
.
add
(
'prefixStyle:
$prefixStyle
'
);
if
(
suffixIcon
!=
null
)
description
.
add
(
'suffixIcon:
$suffixIcon
'
);
if
(
suffixText
!=
null
)
description
.
add
(
'suffixText:
$suffixText
'
);
if
(
suffixStyle
!=
null
)
...
...
@@ -330,408 +2092,14 @@ class InputDecoration {
description
.
add
(
'counterText:
$counterText
'
);
if
(
counterStyle
!=
null
)
description
.
add
(
'counterStyle:
$counterStyle
'
);
if
(
filled
)
description
.
add
(
'filled: true'
);
if
(
fillColor
!=
null
)
description
.
add
(
'fillColor:
$fillColor
'
);
if
(
border
!=
null
)
description
.
add
(
'border:
$border
'
);
if
(!
enabled
)
description
.
add
(
'enabled: false'
);
return
'InputDecoration(
${description.join(', ')}
)'
;
}
}
/// Displays the visual elements of a Material Design text field around an
/// arbitrary widget.
///
/// Use [InputDecorator] to create widgets that look and behave like a
/// [TextField] but can be used to input information other than text.
///
/// The configuration of this widget is primarily provided in the form of an
/// [InputDecoration] object.
///
/// Requires one of its ancestors to be a [Material] widget.
///
/// See also:
///
/// * [TextField], which uses an [InputDecorator] to draw labels and other
/// visual elements around a text entry widget.
/// * [Decoration] and [DecoratedBox], for drawing arbitrary decorations
/// around other widgets.
class
InputDecorator
extends
StatelessWidget
{
/// Creates a widget that displays labels and other visual elements similar
/// to a [TextField].
///
/// The [isFocused] and [isEmpty] arguments must not be null.
const
InputDecorator
({
Key
key
,
@required
this
.
decoration
,
this
.
baseStyle
,
this
.
textAlign
,
this
.
isFocused
:
false
,
this
.
isEmpty
:
false
,
this
.
child
,
})
:
assert
(
isFocused
!=
null
),
assert
(
isEmpty
!=
null
),
super
(
key:
key
);
/// The text and styles to use when decorating the child.
final
InputDecoration
decoration
;
/// The style on which to base the label, hint, and error styles if the
/// [decoration] does not provide explicit styles.
///
/// If null, defaults to a text style from the current [Theme].
final
TextStyle
baseStyle
;
/// How the text in the decoration should be aligned horizontally.
final
TextAlign
textAlign
;
/// Whether the input field has focus.
///
/// Determines the position of the label text and the color of the divider.
///
/// Defaults to false.
final
bool
isFocused
;
/// Whether the input field is empty.
///
/// Determines the position of the label text and whether to display the hint
/// text.
///
/// Defaults to false.
final
bool
isEmpty
;
/// The widget below this widget in the tree.
///
/// Typically an [EditableText], [DropdownButton], or [InkWell].
final
Widget
child
;
static
const
double
_kBottomBorderHeight
=
1.0
;
static
const
double
_kDensePadding
=
4.0
;
static
const
double
_kNormalPadding
=
8.0
;
static
const
double
_kDenseTopPadding
=
8.0
;
static
const
double
_kNormalTopPadding
=
16.0
;
@override
void
debugFillProperties
(
DiagnosticPropertiesBuilder
description
)
{
super
.
debugFillProperties
(
description
);
description
.
add
(
new
DiagnosticsProperty
<
InputDecoration
>(
'decoration'
,
decoration
));
description
.
add
(
new
DiagnosticsProperty
<
TextStyle
>(
'baseStyle'
,
baseStyle
,
defaultValue:
null
));
description
.
add
(
new
DiagnosticsProperty
<
bool
>(
'isFocused'
,
isFocused
));
description
.
add
(
new
DiagnosticsProperty
<
bool
>(
'isEmpty'
,
isEmpty
));
}
Color
_getActiveColor
(
ThemeData
themeData
)
{
if
(
isFocused
)
{
switch
(
themeData
.
brightness
)
{
case
Brightness
.
dark
:
return
themeData
.
accentColor
;
case
Brightness
.
light
:
return
themeData
.
primaryColor
;
}
}
return
themeData
.
hintColor
;
}
Widget
_buildContent
(
Color
borderColor
,
double
topPadding
,
bool
isDense
,
Widget
inputChild
)
{
if
(
decoration
.
hideDivider
)
{
return
new
Container
(
padding:
new
EdgeInsets
.
only
(
top:
topPadding
,
bottom:
_kNormalPadding
),
child:
inputChild
,
);
}
return
new
AnimatedContainer
(
padding:
new
EdgeInsets
.
only
(
top:
topPadding
,
bottom:
_kNormalPadding
-
_kBottomBorderHeight
),
duration:
_kTransitionDuration
,
curve:
_kTransitionCurve
,
decoration:
new
BoxDecoration
(
border:
new
Border
(
bottom:
new
BorderSide
(
color:
borderColor
,
width:
_kBottomBorderHeight
,
),
),
),
child:
inputChild
,
);
}
@override
Widget
build
(
BuildContext
context
)
{
assert
(
debugCheckHasMaterial
(
context
));
final
ThemeData
themeData
=
Theme
.
of
(
context
);
final
double
textScaleFactor
=
MediaQuery
.
of
(
context
,
nullOk:
true
)?.
textScaleFactor
??
1.0
;
final
bool
isDense
=
decoration
.
isDense
;
final
bool
isCollapsed
=
decoration
.
isCollapsed
;
assert
(!
isDense
||
!
isCollapsed
);
final
String
labelText
=
decoration
.
labelText
;
final
String
helperText
=
decoration
.
helperText
;
final
String
counterText
=
decoration
.
counterText
;
final
String
hintText
=
decoration
.
hintText
;
final
String
errorText
=
decoration
.
errorText
;
// If we're not focused, there's no value, and labelText was provided,
// then the label appears where the hint would. And we will not show
// the hintText.
final
bool
hasInlineLabel
=
!
isFocused
&&
labelText
!=
null
&&
isEmpty
;
final
Color
activeColor
=
_getActiveColor
(
themeData
);
final
TextStyle
baseStyle
=
themeData
.
textTheme
.
subhead
.
merge
(
this
.
baseStyle
);
final
TextStyle
hintStyle
=
baseStyle
.
copyWith
(
color:
themeData
.
hintColor
).
merge
(
decoration
.
hintStyle
);
final
TextStyle
helperStyle
=
themeData
.
textTheme
.
caption
.
copyWith
(
color:
themeData
.
hintColor
).
merge
(
decoration
.
helperStyle
);
final
TextStyle
counterStyle
=
helperStyle
.
merge
(
decoration
.
counterStyle
);
final
TextStyle
subtextStyle
=
errorText
!=
null
?
themeData
.
textTheme
.
caption
.
copyWith
(
color:
themeData
.
errorColor
).
merge
(
decoration
.
errorStyle
)
:
helperStyle
;
double
topPadding
=
isCollapsed
?
0.0
:
(
isDense
?
_kDenseTopPadding
:
_kNormalTopPadding
);
final
List
<
Widget
>
stackChildren
=
<
Widget
>[];
if
(
labelText
!=
null
)
{
assert
(!
isCollapsed
);
final
TextStyle
floatingLabelStyle
=
themeData
.
textTheme
.
caption
.
copyWith
(
color:
activeColor
).
merge
(
decoration
.
labelStyle
);
final
TextStyle
labelStyle
=
hasInlineLabel
?
hintStyle
:
floatingLabelStyle
;
final
double
labelTextHeight
=
floatingLabelStyle
.
fontSize
*
textScaleFactor
;
final
double
topPaddingIncrement
=
labelTextHeight
+
(
isDense
?
_kDensePadding
:
_kNormalPadding
);
stackChildren
.
add
(
new
AnimatedPositionedDirectional
(
start:
0.0
,
top:
topPadding
+
(
hasInlineLabel
?
topPaddingIncrement
:
0.0
),
duration:
_kTransitionDuration
,
curve:
_kTransitionCurve
,
child:
new
_AnimatedLabel
(
text:
labelText
,
style:
labelStyle
,
duration:
_kTransitionDuration
,
curve:
_kTransitionCurve
,
),
),
);
topPadding
+=
topPaddingIncrement
;
}
if
(
hintText
!=
null
)
{
stackChildren
.
add
(
new
AnimatedPositionedDirectional
(
start:
0.0
,
end:
0.0
,
top:
topPadding
,
duration:
_kTransitionDuration
,
curve:
_kTransitionCurve
,
child:
new
AnimatedOpacity
(
opacity:
(
isEmpty
&&
!
hasInlineLabel
)
?
1.0
:
0.0
,
duration:
_kTransitionDuration
,
curve:
_kTransitionCurve
,
child:
new
Text
(
hintText
,
style:
hintStyle
,
overflow:
TextOverflow
.
ellipsis
,
textAlign:
textAlign
,
),
),
),
);
}
Widget
inputChild
=
new
KeyedSubtree
(
// It's important that we maintain the state of our child subtree, as it
// may be stateful (e.g. containing text selections). Since our build
// function risks changing the depth of the tree, we preserve the subtree
// using global keys.
// GlobalObjectKey(context) will always be the same whenever we are built.
// Additionally, we use a subclass of GlobalObjectKey to avoid clashes
// with anyone else using our BuildContext as their global object key
// value.
key:
new
_InputDecoratorChildGlobalKey
(
context
),
child:
child
,
);
if
(!
hasInlineLabel
&&
(!
isEmpty
||
hintText
==
null
)
&&
(
decoration
?.
prefixText
!=
null
||
decoration
?.
suffixText
!=
null
))
{
final
List
<
Widget
>
rowContents
=
<
Widget
>[];
if
(
decoration
.
prefixText
!=
null
)
{
rowContents
.
add
(
new
Text
(
decoration
.
prefixText
,
style:
decoration
.
prefixStyle
??
hintStyle
)
);
}
rowContents
.
add
(
new
Expanded
(
child:
inputChild
));
if
(
decoration
.
suffixText
!=
null
)
{
rowContents
.
add
(
new
Text
(
decoration
.
suffixText
,
style:
decoration
.
suffixStyle
??
hintStyle
)
);
}
inputChild
=
new
Row
(
children:
rowContents
);
}
// The inputChild and the helper/error text need to be in a column so that if the inputChild is
// a multiline input or a non-text widget, it lays out with the helper/error text below the
// inputChild.
final
List
<
Widget
>
columnChildren
=
<
Widget
>[];
if
(
isCollapsed
)
{
columnChildren
.
add
(
inputChild
);
}
else
{
final
Color
borderColor
=
errorText
==
null
?
activeColor
:
themeData
.
errorColor
;
columnChildren
.
add
(
_buildContent
(
borderColor
,
topPadding
,
isDense
,
inputChild
));
}
if
(
errorText
!=
null
||
helperText
!=
null
||
counterText
!=
null
)
{
assert
(!
isCollapsed
,
"Collapsed fields can't have errorText, helperText, or counterText set."
);
final
EdgeInsets
topPadding
=
new
EdgeInsets
.
only
(
top:
_kBottomBorderHeight
+
(
isDense
?
_kDensePadding
:
_kNormalPadding
)
);
Widget
buildSubText
()
{
return
new
AnimatedContainer
(
padding:
topPadding
,
duration:
_kTransitionDuration
,
curve:
_kTransitionCurve
,
child:
new
Text
(
errorText
??
helperText
,
style:
subtextStyle
,
textAlign:
textAlign
,
overflow:
TextOverflow
.
ellipsis
,
),
);
}
Widget
buildCounter
()
{
return
new
AnimatedContainer
(
padding:
topPadding
,
duration:
_kTransitionDuration
,
curve:
_kTransitionCurve
,
child:
new
Text
(
counterText
,
style:
counterStyle
,
textAlign:
textAlign
==
TextAlign
.
end
?
TextAlign
.
start
:
TextAlign
.
end
,
overflow:
TextOverflow
.
ellipsis
,
),
);
}
final
bool
needSubTextField
=
errorText
!=
null
||
helperText
!=
null
;
final
bool
needCounterField
=
counterText
!=
null
;
if
(
needCounterField
&&
needSubTextField
)
{
columnChildren
.
add
(
new
Row
(
children:
<
Widget
>[
new
Expanded
(
child:
buildSubText
()),
buildCounter
(),
],
),
);
}
else
if
(
needSubTextField
)
{
columnChildren
.
add
(
buildSubText
());
}
else
if
(
needCounterField
)
{
columnChildren
.
add
(
buildCounter
());
}
}
stackChildren
.
add
(
new
Column
(
mainAxisSize:
MainAxisSize
.
min
,
crossAxisAlignment:
CrossAxisAlignment
.
stretch
,
children:
columnChildren
,
),
);
final
Widget
stack
=
new
Stack
(
fit:
StackFit
.
passthrough
,
children:
stackChildren
);
if
(
decoration
.
icon
!=
null
)
{
assert
(!
isCollapsed
);
final
double
iconSize
=
isDense
?
18.0
:
24.0
;
final
double
entryTextHeight
=
baseStyle
.
fontSize
*
textScaleFactor
;
final
double
iconTop
=
topPadding
+
(
entryTextHeight
-
iconSize
)
/
2.0
;
return
new
Row
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
<
Widget
>[
new
AnimatedContainer
(
margin:
new
EdgeInsets
.
only
(
top:
iconTop
),
duration:
_kTransitionDuration
,
curve:
_kTransitionCurve
,
width:
isDense
?
40.0
:
48.0
,
child:
IconTheme
.
merge
(
data:
new
IconThemeData
(
color:
isFocused
?
activeColor
:
Colors
.
black45
,
size:
iconSize
,
),
child:
decoration
.
icon
,
),
),
new
Expanded
(
child:
stack
),
],
);
}
else
{
return
new
ConstrainedBox
(
constraints:
const
BoxConstraints
(
minWidth:
double
.
INFINITY
),
child:
stack
,
);
}
}
}
// Smoothly animate the label of an InputDecorator as the label
// transitions between inline and caption.
class
_AnimatedLabel
extends
ImplicitlyAnimatedWidget
{
const
_AnimatedLabel
({
Key
key
,
this
.
text
,
@required
this
.
style
,
Curve
curve:
Curves
.
linear
,
@required
Duration
duration
,
this
.
textAlign
,
this
.
overflow
,
})
:
assert
(
style
!=
null
),
super
(
key:
key
,
curve:
curve
,
duration:
duration
);
final
String
text
;
final
TextStyle
style
;
final
TextAlign
textAlign
;
final
TextOverflow
overflow
;
@override
_AnimatedLabelState
createState
()
=>
new
_AnimatedLabelState
();
@override
void
debugFillProperties
(
DiagnosticPropertiesBuilder
description
)
{
super
.
debugFillProperties
(
description
);
style
?.
debugFillProperties
(
description
);
}
}
class
_AnimatedLabelState
extends
AnimatedWidgetBaseState
<
_AnimatedLabel
>
{
TextStyleTween
_style
;
@override
void
forEachTween
(
TweenVisitor
<
dynamic
>
visitor
)
{
_style
=
visitor
(
_style
,
widget
.
style
,
(
dynamic
value
)
=>
new
TextStyleTween
(
begin:
value
));
}
@override
Widget
build
(
BuildContext
context
)
{
TextStyle
style
=
_style
.
evaluate
(
animation
);
double
scale
=
1.0
;
if
(
style
.
fontSize
!=
widget
.
style
.
fontSize
)
{
// While the fontSize is transitioning, use a scaled Transform as a
// fraction of the original fontSize. That way we get a smooth scaling
// effect with no snapping between discrete font sizes.
scale
=
style
.
fontSize
/
widget
.
style
.
fontSize
;
style
=
style
.
copyWith
(
fontSize:
widget
.
style
.
fontSize
);
}
return
new
Transform
(
transform:
new
Matrix4
.
identity
()..
scale
(
scale
),
child:
new
Text
(
widget
.
text
,
style:
style
,
textAlign:
widget
.
textAlign
,
overflow:
widget
.
overflow
,
),
);
}
}
packages/flutter/lib/src/material/text_field.dart
View file @
d188a8ff
...
...
@@ -9,6 +9,7 @@ import 'package:flutter/widgets.dart';
import
'feedback.dart'
;
import
'input_decorator.dart'
;
import
'material.dart'
;
import
'text_selection.dart'
;
import
'theme.dart'
;
...
...
packages/flutter/lib/src/painting/shape_decoration.dart
View file @
d188a8ff
...
...
@@ -285,6 +285,7 @@ class _ShapeDecorationPainter extends BoxPainter {
final
ShapeDecoration
_decoration
;
Rect
_lastRect
;
TextDirection
_lastTextDirection
;
Path
_outerPath
;
Path
_innerPath
;
Paint
_interiorPaint
;
...
...
@@ -292,10 +293,11 @@ class _ShapeDecorationPainter extends BoxPainter {
List
<
Path
>
_shadowPaths
;
List
<
Paint
>
_shadowPaints
;
void
_precache
(
Rect
rect
)
{
void
_precache
(
Rect
rect
,
TextDirection
textDirection
)
{
assert
(
rect
!=
null
);
if
(
rect
==
_lastRect
)
if
(
rect
==
_lastRect
&&
textDirection
==
_lastTextDirection
)
return
;
// We reach here in two cases:
// - the very first time we paint, in which case everything except _decoration is null
// - subsequent times, if the rect has changed, in which case we only need to update
...
...
@@ -328,7 +330,9 @@ class _ShapeDecorationPainter extends BoxPainter {
_outerPath
=
_decoration
.
shape
.
getOuterPath
(
rect
);
if
(
_decoration
.
image
!=
null
)
_innerPath
=
_decoration
.
shape
.
getInnerPath
(
rect
);
_lastRect
=
rect
;
_lastTextDirection
=
textDirection
;
}
void
_paintShadows
(
Canvas
canvas
)
{
...
...
@@ -362,10 +366,11 @@ class _ShapeDecorationPainter extends BoxPainter {
assert
(
configuration
!=
null
);
assert
(
configuration
.
size
!=
null
);
final
Rect
rect
=
offset
&
configuration
.
size
;
_precache
(
rect
);
final
TextDirection
textDirection
=
configuration
.
textDirection
;
_precache
(
rect
,
textDirection
);
_paintShadows
(
canvas
);
_paintInterior
(
canvas
);
_paintImage
(
canvas
,
configuration
);
_decoration
.
shape
.
paint
(
canvas
,
rect
);
_decoration
.
shape
.
paint
(
canvas
,
rect
,
textDirection:
textDirection
);
}
}
packages/flutter/test/material/input_decorator_test.dart
View file @
d188a8ff
...
...
@@ -6,509 +6,911 @@ import 'package:flutter/material.dart';
import
'package:flutter/rendering.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
void
main
(
)
{
Widget
buildInputDecorator
({
InputDecoration
decoration
=
const
InputDecoration
(),
Widget
child
=
const
Text
(
'Test'
)})
{
Widget
buildInputDecorator
(
{
InputDecoration
decoration:
const
InputDecoration
(),
TextDirection
textDirection:
TextDirection
.
ltr
,
bool
isEmpty:
false
,
bool
isFocused:
false
,
TextStyle
baseStyle
,
Widget
child:
const
Text
(
'text'
,
style:
const
TextStyle
(
fontFamily:
'Ahem'
,
fontSize:
16.0
),
),
})
{
return
new
MaterialApp
(
home:
new
Material
(
child:
new
DefaultTextStyle
(
style:
const
TextStyle
(
fontFamily:
'Ahem'
,
fontSize:
10.0
),
child:
new
Center
(
child:
new
Align
(
alignment:
Alignment
.
topLeft
,
child:
new
Directionality
(
textDirection:
textDirection
,
child:
new
InputDecorator
(
decoration:
decoration
,
isEmpty:
isEmpty
,
isFocused:
isFocused
,
baseStyle:
baseStyle
,
child:
child
,
),
),
),
),
);
}
Finder
findInputDecoratorChildContainer
()
{
return
find
.
byWidgetPredicate
(
(
Widget
w
)
{
return
w
is
AnimatedContainer
&&
(
w
as
dynamic
).
decoration
!=
null
;
});
}
}
double
getBoxDecorationThickness
(
WidgetTester
tester
)
{
final
AnimatedContainer
container
=
tester
.
widget
(
findInputDecoratorChildContainer
());
final
BoxDecoration
decoration
=
container
.
decoration
;
final
Border
border
=
decoration
.
border
;
return
border
.
bottom
.
width
;
}
Finder
findBorderPainter
(
)
{
return
find
.
descendant
(
of:
find
.
byWidgetPredicate
((
Widget
w
)
=>
'
${w.runtimeType}
'
==
'_BorderContainer'
),
matching:
find
.
byWidgetPredicate
((
Widget
w
)
=>
w
is
CustomPaint
),
)
;
}
double
getDividerY
(
WidgetTester
tester
)
{
final
Finder
animatedContainerFinder
=
find
.
byWidgetPredicate
(
(
Widget
w
)
{
return
w
is
AnimatedContainer
&&
(
w
as
dynamic
).
decoration
!=
null
;
});
return
tester
.
getRect
(
animatedContainerFinder
).
bottom
;
}
double
getBorderBottom
(
WidgetTester
tester
)
{
final
RenderBox
box
=
InputDecorator
.
containerOf
(
tester
.
element
(
findBorderPainter
()));
return
box
.
size
.
height
;
}
double
getDividerWidth
(
WidgetTester
tester
)
{
final
Finder
animatedContainerFinder
=
find
.
byWidgetPredicate
(
(
Widget
w
)
{
return
w
is
AnimatedContainer
&&
(
w
as
dynamic
).
decoration
!=
null
;
});
return
tester
.
getRect
(
animatedContainerFinder
).
size
.
width
;
}
double
getBorderWeight
(
WidgetTester
tester
)
{
if
(!
tester
.
any
(
findBorderPainter
()))
return
0.0
;
final
CustomPaint
customPaint
=
tester
.
widget
(
findBorderPainter
());
final
dynamic
/* _InputBorderPainter */
inputBorderPainter
=
customPaint
.
foregroundPainter
;
final
dynamic
/*_InputBorderTween */
inputBorderTween
=
inputBorderPainter
.
border
;
final
Animation
<
double
>
animation
=
inputBorderPainter
.
borderAnimation
;
final
dynamic
/*_InputBorder */
border
=
inputBorderTween
.
evaluate
(
animation
);
return
border
.
borderSide
.
width
;
}
testWidgets
(
'InputDecorator always expands horizontally'
,
(
WidgetTester
tester
)
async
{
final
Key
key
=
new
UniqueKey
();
double
getHintOpacity
(
WidgetTester
tester
)
{
final
Opacity
opacityWidget
=
tester
.
widget
<
Opacity
>(
find
.
ancestor
(
of:
find
.
text
(
'hint'
),
matching:
find
.
byType
(
Opacity
),
).
last
);
return
opacityWidget
.
opacity
;
}
void
main
(
)
{
testWidgets
(
'InputDecorator input/label layout'
,
(
WidgetTester
tester
)
async
{
// The label appears above the input text
await
tester
.
pumpWidget
(
buildInputDecorator
(
child:
new
Container
(
key:
key
,
width:
50.0
,
height:
60.0
,
color:
Colors
.
blue
),
// isEmpty: false (default)
// isFocused: false (default)
decoration:
const
InputDecoration
(
labelText:
'label'
,
),
),
);
expect
(
tester
.
element
(
find
.
byKey
(
key
)).
size
,
equals
(
const
Size
(
800.0
,
60.0
)));
// Overall height for this InputDecorator is 56dps:
// 12 - top padding
// 12 - floating label (ahem font size 16dps * 0.75 = 12)
// 4 - floating label / input text gap
// 16 - input text (ahem font size 16dps)
// 12 - bottom padding
expect
(
tester
.
getSize
(
find
.
byType
(
InputDecorator
)),
const
Size
(
800.0
,
56.0
));
expect
(
tester
.
getTopLeft
(
find
.
text
(
'text'
)).
dy
,
28.0
);
expect
(
tester
.
getBottomLeft
(
find
.
text
(
'text'
)).
dy
,
44.0
);
expect
(
tester
.
getTopLeft
(
find
.
text
(
'label'
)).
dy
,
12.0
);
expect
(
tester
.
getBottomLeft
(
find
.
text
(
'label'
)).
dy
,
24.0
);
expect
(
getBorderBottom
(
tester
),
56.0
);
expect
(
getBorderWeight
(
tester
),
1.0
);
// isFocused: true increases the border's weight from 1.0 to 2.0
// but does not change the overall height.
await
tester
.
pumpWidget
(
buildInputDecorator
(
// isEmpty: false (default)
isFocused:
true
,
decoration:
const
InputDecoration
(
icon:
const
Icon
(
Icons
.
add_shopping_cart
)
,
labelText:
'label'
,
),
child:
new
Container
(
key:
key
,
width:
50.0
,
height:
60.0
,
color:
Colors
.
blue
),
),
);
await
tester
.
pumpAndSettle
();
expect
(
tester
.
getSize
(
find
.
byType
(
InputDecorator
)),
const
Size
(
800.0
,
56.0
));
expect
(
tester
.
getTopLeft
(
find
.
text
(
'text'
)).
dy
,
28.0
);
expect
(
tester
.
getBottomLeft
(
find
.
text
(
'text'
)).
dy
,
44.0
);
expect
(
tester
.
getTopLeft
(
find
.
text
(
'label'
)).
dy
,
12.0
);
expect
(
tester
.
getBottomLeft
(
find
.
text
(
'label'
)).
dy
,
24.0
);
expect
(
getBorderBottom
(
tester
),
56.0
);
expect
(
getBorderWeight
(
tester
),
2.0
);
expect
(
tester
.
element
(
find
.
byKey
(
key
)).
size
,
equals
(
const
Size
(
752.0
,
60.0
)));
// isEmpty: true causes the label to be aligned with the input text
await
tester
.
pumpWidget
(
buildInputDecorator
(
isEmpty:
true
,
isFocused:
false
,
decoration:
const
InputDecoration
(
labelText:
'label'
,
),
),
);
// The label animates downwards from it's initial position
// above the input text. The animation's duration is 200ms.
{
await
tester
.
pump
(
const
Duration
(
milliseconds:
50
));
final
double
labelY50ms
=
tester
.
getTopLeft
(
find
.
text
(
'label'
)).
dy
;
expect
(
labelY50ms
,
inExclusiveRange
(
12.0
,
20.0
));
await
tester
.
pump
(
const
Duration
(
milliseconds:
50
));
final
double
labelY100ms
=
tester
.
getTopLeft
(
find
.
text
(
'label'
)).
dy
;
expect
(
labelY100ms
,
inExclusiveRange
(
labelY50ms
,
20.0
));
}
await
tester
.
pumpAndSettle
();
expect
(
tester
.
getSize
(
find
.
byType
(
InputDecorator
)),
const
Size
(
800.0
,
56.0
));
expect
(
tester
.
getTopLeft
(
find
.
text
(
'text'
)).
dy
,
28.0
);
expect
(
tester
.
getBottomLeft
(
find
.
text
(
'text'
)).
dy
,
44.0
);
expect
(
tester
.
getTopLeft
(
find
.
text
(
'label'
)).
dy
,
20.0
);
expect
(
tester
.
getBottomLeft
(
find
.
text
(
'label'
)).
dy
,
36.0
);
expect
(
getBorderBottom
(
tester
),
56.0
);
expect
(
getBorderWeight
(
tester
),
1.0
);
// isFocused: true causes the label to move back up above the input text.
await
tester
.
pumpWidget
(
buildInputDecorator
(
decoration:
const
InputDecoration
.
collapsed
(
hintText:
'Hint text'
,
isEmpty:
true
,
isFocused:
true
,
decoration:
const
InputDecoration
(
labelText:
'label'
,
),
child:
new
Container
(
key:
key
,
width:
50.0
,
height:
60.0
,
color:
Colors
.
blue
),
),
);
expect
(
tester
.
element
(
find
.
byKey
(
key
)).
size
,
equals
(
const
Size
(
800.0
,
60.0
)));
});
// The label animates upwards from it's initial position
// above the input text. The animation's duration is 200ms.
{
await
tester
.
pump
(
const
Duration
(
milliseconds:
50
));
final
double
labelY50ms
=
tester
.
getTopLeft
(
find
.
text
(
'label'
)).
dy
;
expect
(
labelY50ms
,
inExclusiveRange
(
12.0
,
28.0
));
await
tester
.
pump
(
const
Duration
(
milliseconds:
50
));
final
double
labelY100ms
=
tester
.
getTopLeft
(
find
.
text
(
'label'
)).
dy
;
expect
(
labelY100ms
,
inExclusiveRange
(
12.0
,
labelY50ms
));
}
testWidgets
(
'InputDecorator draws the divider correctly in the right place.'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpAndSettle
();
expect
(
tester
.
getSize
(
find
.
byType
(
InputDecorator
)),
const
Size
(
800.0
,
56.0
));
expect
(
tester
.
getTopLeft
(
find
.
text
(
'text'
)).
dy
,
28.0
);
expect
(
tester
.
getBottomLeft
(
find
.
text
(
'text'
)).
dy
,
44.0
);
expect
(
tester
.
getTopLeft
(
find
.
text
(
'label'
)).
dy
,
12.0
);
expect
(
tester
.
getBottomLeft
(
find
.
text
(
'label'
)).
dy
,
24.0
);
expect
(
getBorderBottom
(
tester
),
56.0
);
expect
(
getBorderWeight
(
tester
),
2.0
);
// enabled: false causes the border to disappear
await
tester
.
pumpWidget
(
buildInputDecorator
(
isEmpty:
true
,
isFocused:
false
,
decoration:
const
InputDecoration
(
hintText:
'Hint'
,
labelText:
'Label'
,
helperText:
'Helper'
,
counterText:
'Counter'
,
labelText:
'label'
,
enabled:
false
,
),
),
);
expect
(
getBoxDecorationThickness
(
tester
),
equals
(
1.0
));
expect
(
getDividerY
(
tester
),
equals
(
316.5
));
expect
(
getDividerWidth
(
tester
),
equals
(
800.0
));
await
tester
.
pumpAndSettle
();
expect
(
tester
.
getSize
(
find
.
byType
(
InputDecorator
)),
const
Size
(
800.0
,
56.0
));
expect
(
tester
.
getTopLeft
(
find
.
text
(
'text'
)).
dy
,
28.0
);
expect
(
tester
.
getBottomLeft
(
find
.
text
(
'text'
)).
dy
,
44.0
);
expect
(
tester
.
getTopLeft
(
find
.
text
(
'label'
)).
dy
,
20.0
);
expect
(
tester
.
getBottomLeft
(
find
.
text
(
'label'
)).
dy
,
36.0
);
expect
(
getBorderWeight
(
tester
),
0.0
);
});
testWidgets
(
'InputDecorator draws the divider correctly in the right place for dense layout.'
,
(
WidgetTester
tester
)
async
{
// Overall height for this InputDecorator is 40.0dps
// 12 - top padding
// 16 - input text (ahem font size 16dps)
// 12 - bottom padding
testWidgets
(
'InputDecorator input/hint layout'
,
(
WidgetTester
tester
)
async
{
// The hint aligns with the input text
await
tester
.
pumpWidget
(
buildInputDecorator
(
isEmpty:
true
,
// isFocused: false (default)
decoration:
const
InputDecoration
(
hintText:
'Hint'
,
labelText:
'Label'
,
helperText:
'Helper'
,
counterText:
'Counter'
,
isDense:
true
,
hintText:
'hint'
,
),
),
);
expect
(
getBoxDecorationThickness
(
tester
),
equals
(
1.0
));
expect
(
getDividerY
(
tester
),
equals
(
312.5
));
expect
(
getDividerWidth
(
tester
),
equals
(
800.0
));
expect
(
tester
.
getSize
(
find
.
byType
(
InputDecorator
)),
const
Size
(
800.0
,
40.0
));
expect
(
tester
.
getTopLeft
(
find
.
text
(
'text'
)).
dy
,
12.0
);
expect
(
tester
.
getBottomLeft
(
find
.
text
(
'text'
)).
dy
,
28.0
);
expect
(
tester
.
getTopLeft
(
find
.
text
(
'hint'
)).
dy
,
12.0
);
expect
(
tester
.
getBottomLeft
(
find
.
text
(
'hint'
)).
dy
,
28.0
);
expect
(
getBorderBottom
(
tester
),
40.0
);
expect
(
getBorderWeight
(
tester
),
1.0
);
});
testWidgets
(
'InputDecorator does not draw the underline when hideDivider is true.'
,
(
WidgetTester
tester
)
async
{
testWidgets
(
'InputDecorator input/label/hint layout'
,
(
WidgetTester
tester
)
async
{
// Label is visible, hint is not (opacity 0.0).
await
tester
.
pumpWidget
(
buildInputDecorator
(
isEmpty:
true
,
// isFocused: false (default)
decoration:
const
InputDecoration
(
hintText:
'Hint'
,
labelText:
'Label'
,
helperText:
'Helper'
,
counterText:
'Counter'
,
hideDivider:
true
,
labelText:
'label'
,
hintText:
'hint'
,
),
),
);
expect
(
findInputDecoratorChildContainer
(),
findsNothing
);
});
// Overall height for this InputDecorator is 56dps. When the
// label is "floating" (empty input or no focus) the layout is:
//
// 12 - top padding
// 12 - floating label (ahem font size 16dps * 0.75 = 12)
// 4 - floating label / input text gap
// 16 - input text (ahem font size 16dps)
// 12 - bottom padding
//
// When the label is not floating, it's vertically centered.
//
// 20 - top padding
// 16 - label (ahem font size 16dps)
// 20 - bottom padding (empty input text still appears here)
testWidgets
(
'InputDecorator uses proper padding for dense mode'
,
(
WidgetTester
tester
)
async
{
// The label is not floating so it's vertically centered.
expect
(
tester
.
getSize
(
find
.
byType
(
InputDecorator
)),
const
Size
(
800.0
,
56.0
));
expect
(
tester
.
getTopLeft
(
find
.
text
(
'text'
)).
dy
,
28.0
);
expect
(
tester
.
getBottomLeft
(
find
.
text
(
'text'
)).
dy
,
44.0
);
expect
(
tester
.
getTopLeft
(
find
.
text
(
'label'
)).
dy
,
20.0
);
expect
(
tester
.
getBottomLeft
(
find
.
text
(
'label'
)).
dy
,
36.0
);
expect
(
getHintOpacity
(
tester
),
0.0
);
expect
(
getBorderBottom
(
tester
),
56.0
);
expect
(
getBorderWeight
(
tester
),
1.0
);
// Label moves upwards, hint is visible (opacity 1.0).
await
tester
.
pumpWidget
(
buildInputDecorator
(
isEmpty:
true
,
isFocused:
true
,
decoration:
const
InputDecoration
(
hintText:
'Hint'
,
labelText:
'Label'
,
helperText:
'Helper'
,
counterText:
'Counter'
,
isDense:
true
,
labelText:
'label'
,
hintText:
'hint'
,
),
),
);
// TODO(#12357): Update this test when the font metric bug is fixed to remove the anyOfs.
expect
(
tester
.
getRect
(
find
.
text
(
'Label'
)).
size
,
anyOf
(<
Size
>[
const
Size
(
60.0
,
12.0
),
const
Size
(
61.0
,
12.0
)]),
);
expect
(
tester
.
getRect
(
find
.
text
(
'Label'
)).
left
,
equals
(
0.0
));
expect
(
tester
.
getRect
(
find
.
text
(
'Label'
)).
top
,
equals
(
278.5
));
expect
(
tester
.
getRect
(
find
.
text
(
'Hint'
)).
size
,
equals
(
const
Size
(
800.0
,
16.0
)));
expect
(
tester
.
getRect
(
find
.
text
(
'Hint'
)).
left
,
equals
(
0.0
));
expect
(
tester
.
getRect
(
find
.
text
(
'Hint'
)).
top
,
equals
(
294.5
));
expect
(
tester
.
getRect
(
find
.
text
(
'Helper'
)).
size
,
anyOf
(<
Size
>[
const
Size
(
716.0
,
12.0
),
const
Size
(
715.0
,
12.0
)]),
);
expect
(
tester
.
getRect
(
find
.
text
(
'Helper'
)).
left
,
equals
(
0.0
));
expect
(
tester
.
getRect
(
find
.
text
(
'Helper'
)).
top
,
equals
(
317.5
));
expect
(
tester
.
getRect
(
find
.
text
(
'Counter'
)).
size
,
anyOf
(<
Size
>[
const
Size
(
84.0
,
12.0
),
const
Size
(
85.0
,
12.0
)]),
);
expect
(
tester
.
getRect
(
find
.
text
(
'Counter'
)).
left
,
anyOf
(
716.0
,
715.0
));
expect
(
tester
.
getRect
(
find
.
text
(
'Counter'
)).
top
,
equals
(
317.5
));
});
// The hint's opacity animates from 0.0 to 1.0.
// The animation's duration is 200ms.
{
await
tester
.
pump
(
const
Duration
(
milliseconds:
50
));
final
double
hintOpacity50ms
=
getHintOpacity
(
tester
);
expect
(
hintOpacity50ms
,
inExclusiveRange
(
0.0
,
1.0
));
await
tester
.
pump
(
const
Duration
(
milliseconds:
50
));
final
double
hintOpacity100ms
=
getHintOpacity
(
tester
);
expect
(
hintOpacity100ms
,
inExclusiveRange
(
hintOpacity50ms
,
1.0
));
}
await
tester
.
pumpAndSettle
();
expect
(
tester
.
getSize
(
find
.
byType
(
InputDecorator
)),
const
Size
(
800.0
,
56.0
));
expect
(
tester
.
getTopLeft
(
find
.
text
(
'text'
)).
dy
,
28.0
);
expect
(
tester
.
getBottomLeft
(
find
.
text
(
'text'
)).
dy
,
44.0
);
expect
(
tester
.
getTopLeft
(
find
.
text
(
'label'
)).
dy
,
12.0
);
expect
(
tester
.
getBottomLeft
(
find
.
text
(
'label'
)).
dy
,
24.0
);
expect
(
tester
.
getTopLeft
(
find
.
text
(
'hint'
)).
dy
,
28.0
);
expect
(
tester
.
getBottomLeft
(
find
.
text
(
'hint'
)).
dy
,
44.0
);
expect
(
getHintOpacity
(
tester
),
1.0
);
expect
(
getBorderBottom
(
tester
),
56.0
);
expect
(
getBorderWeight
(
tester
),
2.0
);
testWidgets
(
'InputDecorator uses proper padding'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
buildInputDecorator
(
isEmpty:
false
,
isFocused:
true
,
decoration:
const
InputDecoration
(
hintText:
'Hint'
,
labelText:
'Label'
,
helperText:
'Helper'
,
counterText:
'Counter'
,
labelText:
'label'
,
hintText:
'hint'
,
),
),
);
// T
ODO(#12357): Update this test when the font metric bug is fixed to remove the anyOfs
.
expect
(
tester
.
getRect
(
find
.
text
(
'Label'
)).
size
,
anyOf
(<
Size
>[
const
Size
(
60.0
,
12.0
),
const
Size
(
61.0
,
12.0
)]),
);
expect
(
tester
.
getRect
(
find
.
text
(
'Label'
)).
left
,
equals
(
0
.0
));
expect
(
tester
.
getRect
(
find
.
text
(
'Label'
)).
top
,
equals
(
278.5
));
expect
(
tester
.
getRect
(
find
.
text
(
'Hint'
)).
size
,
equals
(
const
Size
(
800.0
,
16.0
))
);
expect
(
tester
.
getRect
(
find
.
text
(
'Hint'
)).
left
,
equals
(
0.0
));
expect
(
tester
.
getRect
(
find
.
text
(
'Hint'
)).
top
,
equals
(
298.5
));
expect
(
tester
.
getRect
(
find
.
text
(
'Helper'
)).
size
,
anyOf
(<
Size
>[
const
Size
(
715.0
,
12.0
),
const
Size
(
716.0
,
12.0
)]),
);
expect
(
tester
.
get
Rect
(
find
.
text
(
'Helper'
)).
left
,
equals
(
0.0
)
);
expect
(
tester
.
get
Rect
(
find
.
text
(
'Helper'
)).
top
,
equals
(
325.5
)
);
expect
(
tester
.
getRect
(
find
.
text
(
'Counter'
)).
size
,
anyOf
(<
Size
>[
const
Size
(
84.0
,
12.0
),
const
Size
(
85.0
,
12.0
)]),
);
expect
(
tester
.
getRect
(
find
.
text
(
'Counter'
)).
left
,
anyOf
(
715.0
,
716.0
)
);
expect
(
tester
.
getRect
(
find
.
text
(
'Counter'
)).
top
,
equals
(
325.5
)
);
// T
he hint's opacity animates from 1.0 to 0.0
.
// The animation's duration is 200ms.
{
await
tester
.
pump
(
const
Duration
(
milliseconds:
50
));
final
double
hintOpacity50ms
=
getHintOpacity
(
tester
);
expect
(
hintOpacity50ms
,
inExclusiveRange
(
0.0
,
1
.0
));
await
tester
.
pump
(
const
Duration
(
milliseconds:
50
));
final
double
hintOpacity100ms
=
getHintOpacity
(
tester
);
expect
(
hintOpacity100ms
,
inExclusiveRange
(
0.0
,
hintOpacity50ms
));
}
await
tester
.
pumpAndSettle
();
expect
(
tester
.
getSize
(
find
.
byType
(
InputDecorator
)),
const
Size
(
800.0
,
56.0
));
expect
(
tester
.
getTopLeft
(
find
.
text
(
'text'
)).
dy
,
28.0
);
expect
(
tester
.
get
BottomLeft
(
find
.
text
(
'text'
)).
dy
,
44.0
);
expect
(
tester
.
get
TopLeft
(
find
.
text
(
'label'
)).
dy
,
12.0
);
expect
(
tester
.
getBottomLeft
(
find
.
text
(
'label'
)).
dy
,
24.0
);
expect
(
tester
.
getTopLeft
(
find
.
text
(
'hint'
)).
dy
,
28.0
);
expect
(
tester
.
getBottomLeft
(
find
.
text
(
'hint'
)).
dy
,
44.0
);
expect
(
getHintOpacity
(
tester
),
0.0
);
expect
(
getBorderBottom
(
tester
),
56.0
);
expect
(
getBorderWeight
(
tester
),
2.0
);
});
testWidgets
(
'InputDecorator uses proper padding when error is set'
,
(
WidgetTester
tester
)
async
{
testWidgets
(
'InputDecorator input/label/hint dense layout'
,
(
WidgetTester
tester
)
async
{
// Label is visible, hint is not (opacity 0.0).
await
tester
.
pumpWidget
(
buildInputDecorator
(
isEmpty:
true
,
// isFocused: false (default)
decoration:
const
InputDecoration
(
hintText:
'Hint'
,
labelText:
'Label'
,
helperText:
'Helper'
,
errorText:
'Error'
,
counterText:
'Counter'
,
labelText:
'label'
,
hintText:
'hint'
,
isDense:
true
,
),
),
);
// TODO(#12357): Update this test when the font metric bug is fixed to remove the anyOfs.
expect
(
tester
.
getRect
(
find
.
text
(
'Label'
)).
size
,
anyOf
(<
Size
>[
const
Size
(
60.0
,
12.0
),
const
Size
(
61.0
,
12.0
)]),
);
expect
(
tester
.
getRect
(
find
.
text
(
'Label'
)).
left
,
equals
(
0.0
));
expect
(
tester
.
getRect
(
find
.
text
(
'Label'
)).
top
,
equals
(
278.5
));
expect
(
tester
.
getRect
(
find
.
text
(
'Hint'
)).
size
,
equals
(
const
Size
(
800.0
,
16.0
)));
expect
(
tester
.
getRect
(
find
.
text
(
'Hint'
)).
left
,
equals
(
0.0
));
expect
(
tester
.
getRect
(
find
.
text
(
'Hint'
)).
top
,
equals
(
298.5
));
expect
(
tester
.
getRect
(
find
.
text
(
'Error'
)).
size
,
anyOf
(<
Size
>[
const
Size
(
715.0
,
12.0
),
const
Size
(
716.0
,
12.0
)]),
);
expect
(
tester
.
getRect
(
find
.
text
(
'Error'
)).
left
,
equals
(
0.0
));
expect
(
tester
.
getRect
(
find
.
text
(
'Error'
)).
top
,
equals
(
325.5
));
expect
(
tester
.
getRect
(
find
.
text
(
'Counter'
)).
size
,
anyOf
(<
Size
>[
const
Size
(
84.0
,
12.0
),
const
Size
(
85.0
,
12.0
)]),
// Overall height for this InputDecorator is 48dps. When the
// label is "floating" (empty input or no focus) the layout is:
//
// 8 - top padding
// 12 - floating label (ahem font size 16dps * 0.75 = 12)
// 4 - floating label / input text gap
// 16 - input text (ahem font size 16dps)
// 8 - bottom padding
//
// When the label is not floating, it's vertically centered.
//
// 16 - top padding
// 16 - label (ahem font size 16dps)
// 16 - bottom padding (empty input text still appears here)
// The label is not floating so it's vertically centered.
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
,
16.0
);
expect
(
tester
.
getBottomLeft
(
find
.
text
(
'label'
)).
dy
,
32.0
);
expect
(
getHintOpacity
(
tester
),
0.0
);
expect
(
getBorderBottom
(
tester
),
48.0
);
expect
(
getBorderWeight
(
tester
),
1.0
);
// Label is visible, hint is not (opacity 0.0).
await
tester
.
pumpWidget
(
buildInputDecorator
(
isEmpty:
true
,
isFocused:
true
,
decoration:
const
InputDecoration
(
labelText:
'label'
,
hintText:
'hint'
,
isDense:
true
,
),
),
);
expect
(
tester
.
getRect
(
find
.
text
(
'Counter'
)).
left
,
anyOf
(
715.0
,
716.0
));
expect
(
tester
.
getRect
(
find
.
text
(
'Counter'
)).
top
,
equals
(
325.5
));
await
tester
.
pumpAndSettle
();
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
,
8.0
);
expect
(
tester
.
getBottomLeft
(
find
.
text
(
'label'
)).
dy
,
20.0
);
expect
(
getHintOpacity
(
tester
),
1.0
);
expect
(
getBorderBottom
(
tester
),
48.0
);
expect
(
getBorderWeight
(
tester
),
2.0
);
});
testWidgets
(
'InputDecorator animates properly'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
new
MaterialApp
(
home:
const
Material
(
child:
const
DefaultTextStyle
(
style:
const
TextStyle
(
fontFamily:
'Ahem'
,
fontSize:
10.0
),
child:
const
Center
(
child:
const
TextField
(
testWidgets
(
'InputDecorator with null border'
,
(
WidgetTester
tester
)
async
{
// Label is visible, hint is not (opacity 0.0).
await
tester
.
pumpWidget
(
buildInputDecorator
(
isEmpty:
true
,
// isFocused: false (default)
decoration:
const
InputDecoration
(
suffixText:
'S'
,
prefixText:
'P'
,
hintText:
'Hint'
,
labelText:
'Label'
,
helperText:
'Helper'
,
counterText:
'Counter'
,
),
border:
null
,
),
),
);
expect
(
getBorderWeight
(
tester
),
0.0
);
});
testWidgets
(
'InputDecorator error/helper/counter layout'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
buildInputDecorator
(
isEmpty:
true
,
// isFocused: false (default)
decoration:
const
InputDecoration
(
labelText:
'label'
,
helperText:
'helper'
,
counterText:
'counter'
,
filled:
true
,
),
),
);
// Overall height for this InputDecorator is 76dps. When the label is
// floating the layout is:
//
// 12 - top padding
// 12 - floating label (ahem font size 16dps * 0.75 = 12)
// 4 - floating label / input text gap
// 16 - input text (ahem font size 16dps)
// 12 - bottom padding
// 8 - below the border padding
// 12 - help/error/counter text (ahem font size 12dps)
//
// When the label is not floating, it's vertically centered in the space
// above the subtext:
//
// 20 - top padding
// 16 - label (ahem font size 16dps)
// 20 - bottom padding (empty input text still appears here)
// 8 - below the border padding
// 12 - help/error/counter text (ahem font size 12dps)
// isEmpty: true, the label is not floating
expect
(
tester
.
getSize
(
find
.
byType
(
InputDecorator
)),
const
Size
(
800.0
,
76.0
));
expect
(
tester
.
getTopLeft
(
find
.
text
(
'text'
)).
dy
,
28.0
);
expect
(
tester
.
getBottomLeft
(
find
.
text
(
'text'
)).
dy
,
44.0
);
expect
(
tester
.
getTopLeft
(
find
.
text
(
'label'
)).
dy
,
20.0
);
expect
(
tester
.
getBottomLeft
(
find
.
text
(
'label'
)).
dy
,
36.0
);
expect
(
getBorderBottom
(
tester
),
56.0
);
expect
(
getBorderWeight
(
tester
),
1.0
);
expect
(
tester
.
getTopLeft
(
find
.
text
(
'helper'
)),
const
Offset
(
12.0
,
64.0
));
expect
(
tester
.
getTopRight
(
find
.
text
(
'counter'
)),
const
Offset
(
788.0
,
64.0
));
// If errorText is specified then the helperText isn't shown
await
tester
.
pumpWidget
(
buildInputDecorator
(
// isEmpty: false (default)
// isFocused: false (default)
decoration:
const
InputDecoration
(
labelText:
'label'
,
errorText:
'error'
,
helperText:
'helper'
,
counterText:
'counter'
,
filled:
true
,
),
),
);
await
tester
.
pumpAndSettle
();
// isEmpty: false, the label _is_ floating
expect
(
tester
.
getSize
(
find
.
byType
(
InputDecorator
)),
const
Size
(
800.0
,
76.0
));
expect
(
tester
.
getTopLeft
(
find
.
text
(
'text'
)).
dy
,
28.0
);
expect
(
tester
.
getBottomLeft
(
find
.
text
(
'text'
)).
dy
,
44.0
);
expect
(
tester
.
getTopLeft
(
find
.
text
(
'label'
)).
dy
,
12.0
);
expect
(
tester
.
getBottomLeft
(
find
.
text
(
'label'
)).
dy
,
24.0
);
expect
(
getBorderBottom
(
tester
),
56.0
);
expect
(
getBorderWeight
(
tester
),
1.0
);
expect
(
tester
.
getTopLeft
(
find
.
text
(
'error'
)),
const
Offset
(
12.0
,
64.0
));
expect
(
tester
.
getTopRight
(
find
.
text
(
'counter'
)),
const
Offset
(
788.0
,
64.0
));
expect
(
find
.
text
(
'helper'
),
findsNothing
);
// Overall height for this dense layout InputDecorator is 68dps. When the
// label is floating the layout is:
//
// 8 - top padding
// 12 - floating label (ahem font size 16dps * 0.75 = 12)
// 4 - floating label / input text gap
// 16 - input text (ahem font size 16dps)
// 8 - bottom padding
// 8 - below the border padding
// 12 - help/error/counter text (ahem font size 12dps)
//
// When the label is not floating, it's vertically centered in the space
// above the subtext:
//
// 16 - top padding
// 16 - label (ahem font size 16dps)
// 16 - bottom padding (empty input text still appears here)
// 8 - below the border padding
// 12 - help/error/counter text (ahem font size 12dps)
// The layout of the error/helper/counter subtext doesn't change for dense layout.
await
tester
.
pumpWidget
(
buildInputDecorator
(
// isEmpty: false (default)
// isFocused: false (default)
decoration:
const
InputDecoration
(
isDense:
true
,
labelText:
'label'
,
errorText:
'error'
,
helperText:
'helper'
,
counterText:
'counter'
,
filled:
true
,
),
),
));
// TODO(#12357): Update this test when the font metric bug is fixed to remove the anyOfs.
expect
(
tester
.
getRect
(
find
.
text
(
'Label'
)).
size
,
anyOf
(<
Size
>[
const
Size
(
80.0
,
16.0
),
const
Size
(
81.0
,
16.0
)]),
);
expect
(
tester
.
getRect
(
find
.
text
(
'Label'
)).
left
,
equals
(
0.0
));
expect
(
tester
.
getRect
(
find
.
text
(
'Label'
)).
top
,
equals
(
295.5
));
expect
(
tester
.
getRect
(
find
.
text
(
'Hint'
)).
size
,
equals
(
const
Size
(
800.0
,
16.0
)));
expect
(
tester
.
getRect
(
find
.
text
(
'Hint'
)).
left
,
equals
(
0.0
));
expect
(
tester
.
getRect
(
find
.
text
(
'Hint'
)).
top
,
equals
(
295.5
));
expect
(
tester
.
getRect
(
find
.
text
(
'Helper'
)).
size
,
anyOf
(<
Size
>[
const
Size
(
715.0
,
12.0
),
const
Size
(
716.0
,
12.0
)]),
);
expect
(
tester
.
getRect
(
find
.
text
(
'Helper'
)).
left
,
equals
(
0.0
));
expect
(
tester
.
getRect
(
find
.
text
(
'Helper'
)).
top
,
equals
(
328.5
));
expect
(
tester
.
getRect
(
find
.
text
(
'Counter'
)).
size
,
anyOf
(<
Size
>[
const
Size
(
84.0
,
12.0
),
const
Size
(
85.0
,
12.0
)]),
);
expect
(
tester
.
getRect
(
find
.
text
(
'Counter'
)).
left
,
anyOf
(
715.0
,
716.0
));
expect
(
tester
.
getRect
(
find
.
text
(
'Counter'
)).
top
,
equals
(
328.5
));
expect
(
find
.
text
(
'P'
),
findsNothing
);
expect
(
find
.
text
(
'S'
),
findsNothing
);
await
tester
.
pumpAndSettle
();
await
tester
.
tap
(
find
.
byType
(
TextField
));
await
tester
.
pump
(
const
Duration
(
milliseconds:
100
));
// isEmpty: false, the label _is_ floating
expect
(
tester
.
getSize
(
find
.
byType
(
InputDecorator
)),
const
Size
(
800.0
,
68.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
,
8.0
);
expect
(
tester
.
getBottomLeft
(
find
.
text
(
'label'
)).
dy
,
20.0
);
expect
(
getBorderBottom
(
tester
),
48.0
);
expect
(
getBorderWeight
(
tester
),
1.0
);
expect
(
tester
.
getTopLeft
(
find
.
text
(
'error'
)),
const
Offset
(
12.0
,
56.0
));
expect
(
tester
.
getTopRight
(
find
.
text
(
'counter'
)),
const
Offset
(
788.0
,
56.0
));
expect
(
tester
.
getRect
(
find
.
text
(
'Label'
)).
size
,
anyOf
(<
Size
>[
const
Size
(
60.0
,
12.0
),
const
Size
(
61.0
,
12.0
)]),
);
expect
(
tester
.
getRect
(
find
.
text
(
'Label'
)).
left
,
equals
(
0.0
));
expect
(
tester
.
getRect
(
find
.
text
(
'Label'
)).
top
,
equals
(
295.5
));
expect
(
tester
.
getRect
(
find
.
text
(
'Hint'
)).
size
,
equals
(
const
Size
(
800.0
,
16.0
)));
expect
(
tester
.
getRect
(
find
.
text
(
'Hint'
)).
left
,
equals
(
0.0
));
expect
(
tester
.
getRect
(
find
.
text
(
'Hint'
)).
top
,
equals
(
295.5
));
expect
(
tester
.
getRect
(
find
.
text
(
'Helper'
)).
size
,
anyOf
(<
Size
>[
const
Size
(
715.0
,
12.0
),
const
Size
(
716.0
,
12.0
)]),
);
expect
(
tester
.
getRect
(
find
.
text
(
'Helper'
)).
left
,
equals
(
0.0
));
expect
(
tester
.
getRect
(
find
.
text
(
'Helper'
)).
top
,
equals
(
328.5
));
expect
(
tester
.
getRect
(
find
.
text
(
'Counter'
)).
size
,
anyOf
(<
Size
>[
const
Size
(
84.0
,
12.0
),
const
Size
(
85.0
,
12.0
)]),
await
tester
.
pumpWidget
(
buildInputDecorator
(
isEmpty:
true
,
// isFocused: false (default)
decoration:
const
InputDecoration
(
isDense:
true
,
labelText:
'label'
,
errorText:
'error'
,
helperText:
'helper'
,
counterText:
'counter'
,
filled:
true
,
),
),
);
expect
(
tester
.
getRect
(
find
.
text
(
'Counter'
)).
left
,
anyOf
(
715.0
,
716.0
));
expect
(
tester
.
getRect
(
find
.
text
(
'Counter'
)).
top
,
equals
(
328.5
));
expect
(
find
.
text
(
'P'
),
findsNothing
);
expect
(
find
.
text
(
'S'
),
findsNothing
);
await
tester
.
pumpAndSettle
();
await
tester
.
pump
(
const
Duration
(
seconds:
1
));
// isEmpty: false, the label is not floating
expect
(
tester
.
getSize
(
find
.
byType
(
InputDecorator
)),
const
Size
(
800.0
,
68.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
,
16.0
);
expect
(
tester
.
getBottomLeft
(
find
.
text
(
'label'
)).
dy
,
32.0
);
expect
(
getBorderBottom
(
tester
),
48.0
);
expect
(
getBorderWeight
(
tester
),
1.0
);
expect
(
tester
.
getTopLeft
(
find
.
text
(
'error'
)),
const
Offset
(
12.0
,
56.0
));
expect
(
tester
.
getTopRight
(
find
.
text
(
'counter'
)),
const
Offset
(
788.0
,
56.0
));
});
expect
(
tester
.
getRect
(
find
.
text
(
'Label'
)).
size
,
anyOf
(<
Size
>[
const
Size
(
60.0
,
12.0
),
const
Size
(
61.0
,
12.0
)]),
);
expect
(
tester
.
getRect
(
find
.
text
(
'Label'
)).
left
,
equals
(
0.0
));
expect
(
tester
.
getRect
(
find
.
text
(
'Label'
)).
top
,
equals
(
275.5
));
expect
(
tester
.
getRect
(
find
.
text
(
'Hint'
)).
size
,
equals
(
const
Size
(
800.0
,
16.0
)));
expect
(
tester
.
getRect
(
find
.
text
(
'Hint'
)).
left
,
equals
(
0.0
));
expect
(
tester
.
getRect
(
find
.
text
(
'Hint'
)).
top
,
equals
(
295.5
));
expect
(
tester
.
getRect
(
find
.
text
(
'Helper'
)).
size
,
anyOf
(<
Size
>[
const
Size
(
715.0
,
12.0
),
const
Size
(
716.0
,
12.0
)]),
);
expect
(
tester
.
getRect
(
find
.
text
(
'Helper'
)).
left
,
equals
(
0.0
));
expect
(
tester
.
getRect
(
find
.
text
(
'Helper'
)).
top
,
equals
(
328.5
));
expect
(
tester
.
getRect
(
find
.
text
(
'Counter'
)).
size
,
anyOf
(<
Size
>[
const
Size
(
84.0
,
12.0
),
const
Size
(
85.0
,
12.0
)]),
testWidgets
(
'InputDecorator prefix/suffix'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
buildInputDecorator
(
// isEmpty: false (default)
// isFocused: false (default)
decoration:
const
InputDecoration
(
prefixText:
'p'
,
suffixText:
's'
,
filled:
true
,
),
),
);
// Overall height for this InputDecorator is 40dps:
// 12 - top padding
// 16 - input text (ahem font size 16dps)
// 12 - bottom padding
//
// The prefix and suffix wrap the input text and are left and right justified
// respectively. They should have the same height as the input text (16).
expect
(
tester
.
getSize
(
find
.
byType
(
InputDecorator
)),
const
Size
(
800.0
,
40.0
));
expect
(
tester
.
getSize
(
find
.
text
(
'text'
)).
height
,
16.0
);
expect
(
tester
.
getSize
(
find
.
text
(
'p'
)).
height
,
16.0
);
expect
(
tester
.
getSize
(
find
.
text
(
's'
)).
height
,
16.0
);
expect
(
tester
.
getTopLeft
(
find
.
text
(
'text'
)).
dy
,
12.0
);
expect
(
tester
.
getTopLeft
(
find
.
text
(
'p'
)).
dy
,
12.0
);
expect
(
tester
.
getTopLeft
(
find
.
text
(
'p'
)).
dx
,
12.0
);
expect
(
tester
.
getTopLeft
(
find
.
text
(
's'
)).
dy
,
12.0
);
expect
(
tester
.
getTopRight
(
find
.
text
(
's'
)).
dx
,
788.0
);
// layout is a row: [p text s]
expect
(
tester
.
getTopLeft
(
find
.
text
(
'p'
)).
dx
,
12.0
);
expect
(
tester
.
getTopRight
(
find
.
text
(
'p'
)).
dx
,
lessThanOrEqualTo
(
tester
.
getTopLeft
(
find
.
text
(
'text'
)).
dx
));
expect
(
tester
.
getTopRight
(
find
.
text
(
'text'
)).
dx
,
lessThanOrEqualTo
(
tester
.
getTopLeft
(
find
.
text
(
's'
)).
dx
));
});
testWidgets
(
'InputDecorator icon/prefix/suffix'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
buildInputDecorator
(
// isEmpty: false (default)
// isFocused: false (default)
decoration:
const
InputDecoration
(
prefixText:
'p'
,
suffixText:
's'
,
icon:
const
Icon
(
Icons
.
android
),
filled:
true
,
),
),
);
expect
(
tester
.
getRect
(
find
.
text
(
'Counter'
)).
left
,
anyOf
(
715.0
,
716.0
));
expect
(
tester
.
getRect
(
find
.
text
(
'Counter'
)).
top
,
equals
(
328.5
));
expect
(
find
.
text
(
'P'
),
findsNothing
);
expect
(
find
.
text
(
'S'
),
findsNothing
);
await
tester
.
enterText
(
find
.
byType
(
TextField
),
'Test'
);
await
tester
.
pump
(
const
Duration
(
milliseconds:
100
));
// Overall height for this InputDecorator is 40dps:
// 12 - top padding
// 16 - input text (ahem font size 16dps)
// 12 - bottom padding
expect
(
tester
.
getRect
(
find
.
text
(
'Label'
)).
size
,
anyOf
(<
Size
>[
const
Size
(
60.0
,
12.0
),
const
Size
(
61.0
,
12.0
)]),
);
expect
(
tester
.
getRect
(
find
.
text
(
'Label'
)).
left
,
equals
(
0.0
));
expect
(
tester
.
getRect
(
find
.
text
(
'Label'
)).
top
,
equals
(
275.5
));
expect
(
tester
.
getRect
(
find
.
text
(
'Hint'
)).
size
,
equals
(
const
Size
(
800.0
,
16.0
)));
expect
(
tester
.
getRect
(
find
.
text
(
'Hint'
)).
left
,
equals
(
0.0
));
expect
(
tester
.
getRect
(
find
.
text
(
'Hint'
)).
top
,
equals
(
295.5
));
expect
(
tester
.
getRect
(
find
.
text
(
'Helper'
)).
size
,
anyOf
(<
Size
>[
const
Size
(
715.0
,
12.0
),
const
Size
(
716.0
,
12.0
)]),
);
expect
(
tester
.
getRect
(
find
.
text
(
'Helper'
)).
left
,
equals
(
0.0
));
expect
(
tester
.
getRect
(
find
.
text
(
'Helper'
)).
top
,
equals
(
328.5
));
expect
(
tester
.
getRect
(
find
.
text
(
'Counter'
)).
size
,
anyOf
(<
Size
>[
const
Size
(
84.0
,
12.0
),
const
Size
(
85.0
,
12.0
)]),
);
expect
(
tester
.
getRect
(
find
.
text
(
'Counter'
)).
left
,
anyOf
(
715.0
,
716.0
));
expect
(
tester
.
getRect
(
find
.
text
(
'Counter'
)).
top
,
equals
(
328.5
));
expect
(
tester
.
getRect
(
find
.
text
(
'P'
)).
size
,
anyOf
(<
Size
>[
const
Size
(
17.0
,
16.0
),
const
Size
(
16.0
,
16.0
)]),
);
expect
(
tester
.
getRect
(
find
.
text
(
'P'
)).
left
,
equals
(
0.0
));
expect
(
tester
.
getRect
(
find
.
text
(
'P'
)).
top
,
equals
(
295.5
));
expect
(
tester
.
getRect
(
find
.
text
(
'S'
)).
size
,
anyOf
(<
Size
>[
const
Size
(
17.0
,
16.0
),
const
Size
(
16.0
,
16.0
)]),
);
expect
(
tester
.
getRect
(
find
.
text
(
'S'
)).
left
,
anyOf
(
783.0
,
784.0
));
expect
(
tester
.
getRect
(
find
.
text
(
'S'
)).
top
,
equals
(
295.5
));
expect
(
tester
.
getSize
(
find
.
byType
(
InputDecorator
)),
const
Size
(
800.0
,
40.0
));
expect
(
tester
.
getSize
(
find
.
text
(
'text'
)).
height
,
16.0
);
expect
(
tester
.
getSize
(
find
.
text
(
'p'
)).
height
,
16.0
);
expect
(
tester
.
getSize
(
find
.
text
(
's'
)).
height
,
16.0
);
expect
(
tester
.
getTopLeft
(
find
.
text
(
'text'
)).
dy
,
12.0
);
expect
(
tester
.
getTopLeft
(
find
.
text
(
'p'
)).
dy
,
12.0
);
expect
(
tester
.
getTopLeft
(
find
.
text
(
's'
)).
dy
,
12.0
);
expect
(
tester
.
getTopRight
(
find
.
text
(
's'
)).
dx
,
788.0
);
expect
(
tester
.
getSize
(
find
.
byType
(
Icon
)).
height
,
24.0
);
await
tester
.
pump
(
const
Duration
(
seconds:
1
));
// The 24dps high icon is centered on the 16dps high input line
expect
(
tester
.
getTopLeft
(
find
.
byType
(
Icon
)).
dy
,
8.0
);
expect
(
tester
.
getRect
(
find
.
text
(
'Label'
)).
size
,
anyOf
(<
Size
>[
const
Size
(
60.0
,
12.0
),
const
Size
(
61.0
,
12.0
)]),
);
expect
(
tester
.
getRect
(
find
.
text
(
'Label'
)).
left
,
equals
(
0.0
));
expect
(
tester
.
getRect
(
find
.
text
(
'Label'
)).
top
,
equals
(
275.5
));
expect
(
tester
.
getRect
(
find
.
text
(
'Hint'
)).
size
,
equals
(
const
Size
(
800.0
,
16.0
)));
expect
(
tester
.
getRect
(
find
.
text
(
'Hint'
)).
left
,
equals
(
0.0
));
expect
(
tester
.
getRect
(
find
.
text
(
'Hint'
)).
top
,
equals
(
295.5
));
expect
(
tester
.
getRect
(
find
.
text
(
'Helper'
)).
size
,
anyOf
(<
Size
>[
const
Size
(
715.0
,
12.0
),
const
Size
(
716.0
,
12.0
)]),
);
expect
(
tester
.
getRect
(
find
.
text
(
'Helper'
)).
left
,
equals
(
0.0
));
expect
(
tester
.
getRect
(
find
.
text
(
'Helper'
)).
top
,
equals
(
328.5
));
expect
(
tester
.
getRect
(
find
.
text
(
'Counter'
)).
size
,
anyOf
(<
Size
>[
const
Size
(
84.0
,
12.0
),
const
Size
(
85.0
,
12.0
)]),
);
expect
(
tester
.
getRect
(
find
.
text
(
'Counter'
)).
left
,
anyOf
(
715.0
,
716.0
));
expect
(
tester
.
getRect
(
find
.
text
(
'Counter'
)).
top
,
equals
(
328.5
));
expect
(
tester
.
getRect
(
find
.
text
(
'P'
)).
size
,
anyOf
(<
Size
>[
const
Size
(
17.0
,
16.0
),
const
Size
(
16.0
,
16.0
)]),
);
expect
(
tester
.
getRect
(
find
.
text
(
'P'
)).
left
,
equals
(
0.0
));
expect
(
tester
.
getRect
(
find
.
text
(
'P'
)).
top
,
equals
(
295.5
));
expect
(
tester
.
getRect
(
find
.
text
(
'S'
)).
size
,
anyOf
(<
Size
>[
const
Size
(
17.0
,
16.0
),
const
Size
(
16.0
,
16.0
)]),
);
expect
(
tester
.
getRect
(
find
.
text
(
'S'
)).
left
,
anyOf
(
783.0
,
784.0
));
expect
(
tester
.
getRect
(
find
.
text
(
'S'
)).
top
,
equals
(
295.5
));
// layout is a row: [icon, p text s]
expect
(
tester
.
getTopLeft
(
find
.
byType
(
Icon
)).
dx
,
0.0
);
expect
(
tester
.
getTopRight
(
find
.
byType
(
Icon
)).
dx
,
lessThanOrEqualTo
(
tester
.
getTopLeft
(
find
.
text
(
'p'
)).
dx
));
expect
(
tester
.
getTopRight
(
find
.
text
(
'p'
)).
dx
,
lessThanOrEqualTo
(
tester
.
getTopLeft
(
find
.
text
(
'text'
)).
dx
));
expect
(
tester
.
getTopRight
(
find
.
text
(
'text'
)).
dx
,
lessThanOrEqualTo
(
tester
.
getTopLeft
(
find
.
text
(
's'
)).
dx
));
});
testWidgets
(
'InputDecorator
animates properly
'
,
(
WidgetTester
tester
)
async
{
final
Widget
child
=
const
InputDecorator
(
key:
const
Key
(
'key'
),
decoration:
const
InputDecoration
(),
baseStyle:
const
TextStyle
(),
textAlign:
TextAlign
.
center
,
isFocused:
false
,
isEmpty:
false
,
child:
const
Placeholder
()
,
);
expect
(
child
.
toString
(
),
'InputDecorator-[<
\'
key
\'
>](decoration: InputDecoration(), baseStyle: TextStyle(<all styles inherited>), isFocused: false, isEmpty: false)'
,
testWidgets
(
'InputDecorator
error/helper/counter RTL layout
'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
buildInputDecorator
(
// isEmpty: false (default)
// isFocused: false (default)
textDirection:
TextDirection
.
rtl
,
decoration:
const
InputDecoration
(
labelText:
'label'
,
helperText:
'helper'
,
counterText:
'counter'
,
filled:
true
,
),
)
,
);
});
testWidgets
(
'InputDecorator works with partially specified styles'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
new
MaterialApp
(
home:
const
Material
(
child:
const
DefaultTextStyle
(
style:
const
TextStyle
(
fontFamily:
'Ahem'
,
fontSize:
10.0
),
child:
const
Center
(
child:
const
TextField
(
// Overall height for this InputDecorator is 76dps:
// 12 - top padding
// 12 - floating label (ahem font size 16dps * 0.75 = 12)
// 4 - floating label / input text gap
// 16 - input text (ahem font size 16dps)
// 12 - bottom padding
// 8 - below the border padding
// 12 - [counter helper/error] (ahem font size 12dps)
expect
(
tester
.
getSize
(
find
.
byType
(
InputDecorator
)),
const
Size
(
800.0
,
76.0
));
expect
(
tester
.
getTopLeft
(
find
.
text
(
'text'
)).
dy
,
28.0
);
expect
(
tester
.
getBottomLeft
(
find
.
text
(
'text'
)).
dy
,
44.0
);
expect
(
tester
.
getTopLeft
(
find
.
text
(
'label'
)).
dy
,
12.0
);
expect
(
tester
.
getBottomLeft
(
find
.
text
(
'label'
)).
dy
,
24.0
);
expect
(
getBorderBottom
(
tester
),
56.0
);
expect
(
getBorderWeight
(
tester
),
1.0
);
expect
(
tester
.
getTopLeft
(
find
.
text
(
'counter'
)),
const
Offset
(
12.0
,
64.0
));
expect
(
tester
.
getTopRight
(
find
.
text
(
'helper'
)),
const
Offset
(
788.0
,
64.0
));
// If both error and helper are specified, show the error
await
tester
.
pumpWidget
(
buildInputDecorator
(
// isEmpty: false (default)
// isFocused: false (default)
textDirection:
TextDirection
.
rtl
,
decoration:
const
InputDecoration
(
labelText:
'label'
,
labelStyle:
const
TextStyle
(),
helperText:
'helper'
,
helperStyle:
const
TextStyle
(),
hintText:
'hint'
,
hintStyle:
const
TextStyle
(),
errorText:
'error'
,
errorStyle:
const
TextStyle
(),
prefixText:
'prefix'
,
prefixStyle:
const
TextStyle
(),
suffixText:
'suffix'
,
suffixStyle:
const
TextStyle
(),
counterText:
'counter'
,
counterStyle:
const
TextStyle
(),
),
filled:
true
,
),
),
);
await
tester
.
pumpAndSettle
();
expect
(
tester
.
getTopLeft
(
find
.
text
(
'counter'
)),
const
Offset
(
12.0
,
64.0
));
expect
(
tester
.
getTopRight
(
find
.
text
(
'error'
)),
const
Offset
(
788.0
,
64.0
));
expect
(
find
.
text
(
'helper'
),
findsNothing
);
});
testWidgets
(
'InputDecorator prefix/suffix RTL'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
buildInputDecorator
(
// isEmpty: false (default)
// isFocused: false (default)
textDirection:
TextDirection
.
rtl
,
decoration:
const
InputDecoration
(
prefixText:
'p'
,
suffixText:
's'
,
filled:
true
,
),
),
)
)
;
);
expect
(
find
.
text
(
'label'
),
findsOneWidget
);
// Overall height for this InputDecorator is 40dps:
// 12 - top padding
// 16 - input text (ahem font size 16dps)
// 12 - bottom padding
// Tap to make the hint show up.
await
tester
.
tap
(
find
.
byType
(
TextField
));
await
tester
.
pump
(
const
Duration
(
milliseconds:
100
));
expect
(
tester
.
getSize
(
find
.
byType
(
InputDecorator
)),
const
Size
(
800.0
,
40.0
));
expect
(
tester
.
getSize
(
find
.
text
(
'text'
)).
height
,
16.0
);
expect
(
tester
.
getSize
(
find
.
text
(
'p'
)).
height
,
16.0
);
expect
(
tester
.
getSize
(
find
.
text
(
's'
)).
height
,
16.0
);
expect
(
tester
.
getTopLeft
(
find
.
text
(
'text'
)).
dy
,
12.0
);
expect
(
tester
.
getTopLeft
(
find
.
text
(
'p'
)).
dy
,
12.0
);
expect
(
tester
.
getTopLeft
(
find
.
text
(
's'
)).
dy
,
12.0
);
expect
(
find
.
text
(
'hint'
),
findsOneWidget
);
// layout is a row: [s text p]
expect
(
tester
.
getTopLeft
(
find
.
text
(
's'
)).
dx
,
12.0
);
expect
(
tester
.
getTopRight
(
find
.
text
(
's'
)).
dx
,
lessThanOrEqualTo
(
tester
.
getTopLeft
(
find
.
text
(
'text'
)).
dx
));
expect
(
tester
.
getTopRight
(
find
.
text
(
'text'
)).
dx
,
lessThanOrEqualTo
(
tester
.
getTopLeft
(
find
.
text
(
'p'
)).
dx
));
});
// Enter text to make the text style get used.
await
tester
.
enterText
(
find
.
byType
(
TextField
),
'Test'
);
await
tester
.
pump
(
const
Duration
(
milliseconds:
100
));
testWidgets
(
'InputDecorator prefix/suffix dense layout'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
buildInputDecorator
(
// isEmpty: false (default)
isFocused:
true
,
decoration:
const
InputDecoration
(
isDense:
true
,
prefixText:
'p'
,
suffixText:
's'
,
filled:
true
,
),
),
);
// Overall height for this InputDecorator is 32dps:
// 8 - top padding
// 16 - input text (ahem font size 16dps)
// 8 - bottom padding
//
// The only difference from normal layout for this case is that the
// padding above and below the prefix, input text, suffix, is 8 instead of 12.
expect
(
tester
.
getSize
(
find
.
byType
(
InputDecorator
)),
const
Size
(
800.0
,
32.0
));
expect
(
tester
.
getSize
(
find
.
text
(
'text'
)).
height
,
16.0
);
expect
(
tester
.
getSize
(
find
.
text
(
'p'
)).
height
,
16.0
);
expect
(
tester
.
getSize
(
find
.
text
(
's'
)).
height
,
16.0
);
expect
(
tester
.
getTopLeft
(
find
.
text
(
'text'
)).
dy
,
8.0
);
expect
(
tester
.
getTopLeft
(
find
.
text
(
'p'
)).
dy
,
8.0
);
expect
(
tester
.
getTopLeft
(
find
.
text
(
'p'
)).
dx
,
12.0
);
expect
(
tester
.
getTopLeft
(
find
.
text
(
's'
)).
dy
,
8.0
);
expect
(
tester
.
getTopRight
(
find
.
text
(
's'
)).
dx
,
788.0
);
// layout is a row: [p text s]
expect
(
tester
.
getTopLeft
(
find
.
text
(
'p'
)).
dx
,
12.0
);
expect
(
tester
.
getTopRight
(
find
.
text
(
'p'
)).
dx
,
lessThanOrEqualTo
(
tester
.
getTopLeft
(
find
.
text
(
'text'
)).
dx
));
expect
(
tester
.
getTopRight
(
find
.
text
(
'text'
)).
dx
,
lessThanOrEqualTo
(
tester
.
getTopLeft
(
find
.
text
(
's'
)).
dx
));
expect
(
getBorderBottom
(
tester
),
32.0
);
expect
(
getBorderWeight
(
tester
),
2.0
);
});
expect
(
find
.
text
(
'prefix'
),
findsOneWidget
);
expect
(
find
.
text
(
'suffix'
),
findsOneWidget
);
testWidgets
(
'InputDecorator with empty InputDecoration'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
buildInputDecorator
()
);
// Test again without error, so helper style gets used.
await
tester
.
pumpWidget
(
new
MaterialApp
(
home:
const
Material
(
child:
const
DefaultTextStyle
(
style:
const
TextStyle
(),
child:
const
Center
(
child:
const
TextField
(
decoration:
const
InputDecoration
(
labelText:
'label'
,
labelStyle:
const
TextStyle
(),
helperText:
'helper'
,
helperStyle:
const
TextStyle
(),
// Overall height for this InputDecorator is 40dps:
// 12 - top padding
// 16 - input text (ahem font size 16dps)
// 12 - bottom padding
expect
(
tester
.
getSize
(
find
.
byType
(
InputDecorator
)),
const
Size
(
800.0
,
40.0
));
expect
(
tester
.
getSize
(
find
.
text
(
'text'
)).
height
,
16.0
);
expect
(
tester
.
getTopLeft
(
find
.
text
(
'text'
)).
dy
,
12.0
);
expect
(
getBorderBottom
(
tester
),
40.0
);
expect
(
getBorderWeight
(
tester
),
1.0
);
});
testWidgets
(
'InputDecorator.collapsed'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
buildInputDecorator
(
// isEmpty: false (default),
// isFocused: false (default)
decoration:
const
InputDecoration
.
collapsed
(
hintText:
'hint'
,
hintStyle:
const
TextStyle
(),
prefixText:
'prefix'
,
prefixStyle:
const
TextStyle
(),
suffixText:
'suffix'
,
suffixStyle:
const
TextStyle
(),
counterText:
'counter'
,
counterStyle:
const
TextStyle
(),
),
),
);
// Overall height for this InputDecorator is 16dps:
// 16 - input text (ahem font size 16dps)
expect
(
tester
.
getSize
(
find
.
byType
(
InputDecorator
)),
const
Size
(
800.0
,
16.0
));
expect
(
tester
.
getSize
(
find
.
text
(
'text'
)).
height
,
16.0
);
expect
(
tester
.
getTopLeft
(
find
.
text
(
'text'
)).
dy
,
0.0
);
expect
(
getHintOpacity
(
tester
),
0.0
);
expect
(
getBorderWeight
(
tester
),
0.0
);
// The hint should appear
await
tester
.
pumpWidget
(
buildInputDecorator
(
isEmpty:
true
,
isFocused:
true
,
decoration:
const
InputDecoration
.
collapsed
(
hintText:
'hint'
,
),
),
);
await
tester
.
pumpAndSettle
();
expect
(
tester
.
getSize
(
find
.
byType
(
InputDecorator
)),
const
Size
(
800.0
,
16.0
));
expect
(
tester
.
getSize
(
find
.
text
(
'text'
)).
height
,
16.0
);
expect
(
tester
.
getTopLeft
(
find
.
text
(
'text'
)).
dy
,
0.0
);
expect
(
tester
.
getSize
(
find
.
text
(
'hint'
)).
height
,
16.0
);
expect
(
tester
.
getTopLeft
(
find
.
text
(
'hint'
)).
dy
,
0.0
);
expect
(
getBorderWeight
(
tester
),
0.0
);
});
testWidgets
(
'InputDecorator with baseStyle'
,
(
WidgetTester
tester
)
async
{
// Setting the baseStyle of the InputDecoration and the style of the input
// text child to a smaller font reduces the InputDecoration's vertical size.
const
TextStyle
style
=
const
TextStyle
(
fontFamily:
'Ahem'
,
fontSize:
10.0
);
await
tester
.
pumpWidget
(
buildInputDecorator
(
isEmpty:
true
,
isFocused:
false
,
baseStyle:
style
,
decoration:
const
InputDecoration
(
hintText:
'hint'
,
labelText:
'label'
,
),
));
child:
const
Text
(
'text'
,
style:
style
),
),
);
// Overall height for this InputDecorator is 45.5dps. When the label is
// floating the layout is:
//
// 12 - top padding
// 7.5 - floating label (ahem font size 10dps * 0.75 = 7.5)
// 4 - floating label / input text gap
// 10 - input text (ahem font size 10dps)
// 12 - bottom padding
//
// When the label is not floating, it's vertically centered.
//
// 17.75 - top padding
// 10 - label (ahem font size 10dps)
// 17.75 - bottom padding (empty input text still appears here)
expect
(
tester
.
getSize
(
find
.
byType
(
InputDecorator
)),
const
Size
(
800.0
,
45.5
));
expect
(
tester
.
getSize
(
find
.
text
(
'hint'
)).
height
,
10.0
);
expect
(
tester
.
getSize
(
find
.
text
(
'label'
)).
height
,
10.0
);
expect
(
tester
.
getSize
(
find
.
text
(
'text'
)).
height
,
10.0
);
expect
(
tester
.
getTopLeft
(
find
.
text
(
'hint'
)).
dy
,
23.5
);
expect
(
tester
.
getTopLeft
(
find
.
text
(
'label'
)).
dy
,
17.75
);
expect
(
tester
.
getTopLeft
(
find
.
text
(
'text'
)).
dy
,
23.5
);
});
expect
(
find
.
text
(
'label'
),
findsOneWidget
);
expect
(
find
.
text
(
'helper'
),
findsOneWidget
);
testWidgets
(
'InputDecorator with empty style overrides'
,
(
WidgetTester
tester
)
async
{
// Same as not specifying any style overrides
await
tester
.
pumpWidget
(
buildInputDecorator
(
// isEmpty: false (default)
// isFocused: false (default)
decoration:
const
InputDecoration
(
labelText:
'label'
,
hintText:
'hint'
,
helperText:
'helper'
,
counterText:
'counter'
,
labelStyle:
const
TextStyle
(),
hintStyle:
const
TextStyle
(),
errorStyle:
const
TextStyle
(),
helperStyle:
const
TextStyle
(),
filled:
true
,
),
),
);
// Overall height for this InputDecorator is 76dps. When the label is
// floating the layout is:
// 12 - top padding
// 12 - floating label (ahem font size 16dps * 0.75 = 12)
// 4 - floating label / input text gap
// 16 - input text (ahem font size 16dps)
// 12 - bottom padding
// 8 - below the border padding
// 12 - help/error/counter text (ahem font size 12dps)
// Label is floating because isEmpty is false.
expect
(
tester
.
getSize
(
find
.
byType
(
InputDecorator
)),
const
Size
(
800.0
,
76.0
));
expect
(
tester
.
getTopLeft
(
find
.
text
(
'text'
)).
dy
,
28.0
);
expect
(
tester
.
getBottomLeft
(
find
.
text
(
'text'
)).
dy
,
44.0
);
expect
(
tester
.
getTopLeft
(
find
.
text
(
'label'
)).
dy
,
12.0
);
expect
(
tester
.
getBottomLeft
(
find
.
text
(
'label'
)).
dy
,
24.0
);
expect
(
getBorderBottom
(
tester
),
56.0
);
expect
(
getBorderWeight
(
tester
),
1.0
);
expect
(
tester
.
getTopLeft
(
find
.
text
(
'helper'
)),
const
Offset
(
12.0
,
64.0
));
expect
(
tester
.
getTopRight
(
find
.
text
(
'counter'
)),
const
Offset
(
788.0
,
64.0
));
});
testWidgets
(
'InputDecorator.toString()'
,
(
WidgetTester
tester
)
async
{
final
Widget
child
=
const
InputDecorator
(
key:
const
Key
(
'key'
),
decoration:
const
InputDecoration
(),
baseStyle:
const
TextStyle
(),
textAlign:
TextAlign
.
center
,
isFocused:
false
,
isEmpty:
false
,
child:
const
Placeholder
(),
);
expect
(
child
.
toString
(),
"InputDecorator-[<'key'>](decoration: InputDecoration(border: UnderlineInputBorder()), baseStyle: TextStyle(<all styles inherited>), isFocused: false, isEmpty: false)"
,
);
});
}
packages/flutter/test/material/text_field_test.dart
View file @
d188a8ff
...
...
@@ -104,6 +104,15 @@ Future<Null> skipPastScrollingAnimation(WidgetTester tester) async {
await
tester
.
pump
(
const
Duration
(
milliseconds:
200
));
}
double
getOpacity
(
WidgetTester
tester
,
Finder
finder
)
{
return
tester
.
widget
<
Opacity
>(
find
.
ancestor
(
of:
finder
,
matching:
find
.
byType
(
Opacity
),
)
).
opacity
;
}
void
main
(
)
{
final
MockClipboard
mockClipboard
=
new
MockClipboard
();
SystemChannels
.
platform
.
setMockMethodCallHandler
(
mockClipboard
.
handleMethodCall
);
...
...
@@ -1006,33 +1015,26 @@ void main() {
);
// Neither the prefix or the suffix should initially be visible, only the hint.
expect
(
find
.
text
(
'Prefix'
),
findsNothing
);
expect
(
find
.
text
(
'Suffix'
),
findsNothing
);
expect
(
find
.
text
(
'Hint'
),
findsOneWidget
);
expect
(
getOpacity
(
tester
,
find
.
text
(
'Prefix'
)),
0.0
);
expect
(
getOpacity
(
tester
,
find
.
text
(
'Suffix'
)),
0.0
);
expect
(
getOpacity
(
tester
,
find
.
text
(
'Hint'
)),
1.0
);
await
tester
.
tap
(
find
.
byKey
(
secondKey
));
await
tester
.
pump
();
await
tester
.
pump
AndSettle
();
// Focus the Input. The hint
should display, but not the prefix and suffix.
expect
(
find
.
text
(
'Prefix'
),
findsNothing
);
expect
(
find
.
text
(
'Suffix'
),
findsNothing
);
expect
(
find
.
text
(
'Hint'
),
findsOneWidget
);
// Focus the Input. The hint
, prefix, and suffix should appear
expect
(
getOpacity
(
tester
,
find
.
text
(
'Prefix'
)),
1.0
);
expect
(
getOpacity
(
tester
,
find
.
text
(
'Suffix'
)),
1.0
);
expect
(
getOpacity
(
tester
,
find
.
text
(
'Hint'
)),
1.0
);
// Enter some text, and the hint should disappear and the prefix and suffix
// should
appear.
// should
continue to be visible
await
tester
.
enterText
(
find
.
byKey
(
secondKey
),
'Hi'
);
await
tester
.
pump
();
await
tester
.
pump
(
const
Duration
(
seconds:
1
));
await
tester
.
pumpAndSettle
();
expect
(
find
.
text
(
'Prefix'
),
findsOneWidget
);
expect
(
find
.
text
(
'Suffix'
),
findsOneWidget
);
// It's onstage, but animated to zero opacity.
expect
(
find
.
text
(
'Hint'
),
findsOneWidget
);
final
Element
target
=
tester
.
element
(
find
.
text
(
'Hint'
));
final
Opacity
opacity
=
target
.
ancestorWidgetOfExactType
(
Opacity
);
expect
(
opacity
,
isNotNull
);
expect
(
opacity
.
opacity
,
equals
(
0.0
));
expect
(
getOpacity
(
tester
,
find
.
text
(
'Prefix'
)),
1.0
);
expect
(
getOpacity
(
tester
,
find
.
text
(
'Suffix'
)),
1.0
);
expect
(
getOpacity
(
tester
,
find
.
text
(
'Hint'
)),
0.0
);
// Check and make sure that the right styles were applied.
final
Text
prefixText
=
tester
.
widget
(
find
.
text
(
'Prefix'
));
...
...
@@ -1077,27 +1079,25 @@ void main() {
),
);
// Not focused. The prefix
should not display
, but the label should.
expect
(
find
.
text
(
'Prefix'
),
findsNothing
);
expect
(
find
.
text
(
'Suffix'
),
findsNothing
);
// Not focused. The prefix
and suffix should not appear
, but the label should.
expect
(
getOpacity
(
tester
,
find
.
text
(
'Prefix'
)),
0.0
);
expect
(
getOpacity
(
tester
,
find
.
text
(
'Suffix'
)),
0.0
);
expect
(
find
.
text
(
'Label'
),
findsOneWidget
);
// Focus the input. The label, prefix, and suffix should appear.
await
tester
.
tap
(
find
.
byKey
(
secondKey
));
await
tester
.
pump
();
await
tester
.
pump
AndSettle
();
// Focus the input. The label should display, and also the prefix.
expect
(
find
.
text
(
'Prefix'
),
findsOneWidget
);
expect
(
find
.
text
(
'Suffix'
),
findsOneWidget
);
expect
(
getOpacity
(
tester
,
find
.
text
(
'Prefix'
)),
1.0
);
expect
(
getOpacity
(
tester
,
find
.
text
(
'Suffix'
)),
1.0
);
expect
(
find
.
text
(
'Label'
),
findsOneWidget
);
// Enter some text, and the label should stay and the prefix should
// remain.
// Enter some text. The label, prefix, and suffix should remain visible.
await
tester
.
enterText
(
find
.
byKey
(
secondKey
),
'Hi'
);
await
tester
.
pump
();
await
tester
.
pump
(
const
Duration
(
seconds:
1
));
await
tester
.
pumpAndSettle
();
expect
(
find
.
text
(
'Prefix'
),
findsOneWidget
);
expect
(
find
.
text
(
'Suffix'
),
findsOneWidget
);
expect
(
getOpacity
(
tester
,
find
.
text
(
'Prefix'
)),
1.0
);
expect
(
getOpacity
(
tester
,
find
.
text
(
'Suffix'
)),
1.0
);
expect
(
find
.
text
(
'Label'
),
findsOneWidget
);
// Check and make sure that the right styles were applied.
...
...
@@ -1148,21 +1148,25 @@ void main() {
expect
(
newPos
.
dy
,
lessThan
(
pos
.
dy
));
});
testWidgets
(
'
No space between Input icon and text
'
,
(
WidgetTester
tester
)
async
{
testWidgets
(
'
Icon is separated from input/label by 16+12
'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
overlay
(
child:
const
TextField
(
decoration:
const
InputDecoration
(
icon:
const
Icon
(
Icons
.
phone
),
labelText:
'label'
,
filled:
true
,
),
),
),
);
final
double
iconRight
=
tester
.
getTopRight
(
find
.
byType
(
Icon
)).
dx
;
expect
(
iconRight
,
equals
(
tester
.
getTopLeft
(
find
.
text
(
'label'
)).
dx
));
expect
(
iconRight
,
equals
(
tester
.
getTopLeft
(
find
.
byType
(
EditableText
)).
dx
));
// Per https://material.io/guidelines/components/text-fields.html#text-fields-layout
// There's a 16 dps gap between the right edge of the icon and the text field's
// container, and the 12dps more padding between the left edge of the container
// and the left edge of the input and label.
expect
(
iconRight
+
28.0
,
equals
(
tester
.
getTopLeft
(
find
.
text
(
'label'
)).
dx
));
expect
(
iconRight
+
28.0
,
equals
(
tester
.
getTopLeft
(
find
.
byType
(
EditableText
)).
dx
));
});
testWidgets
(
'Collapsed hint text placement'
,
(
WidgetTester
tester
)
async
{
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment