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
0911e5d3
Commit
0911e5d3
authored
Jan 24, 2016
by
Adam Barth
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #1352 from abarth/input_features
Add material design features to Input
parents
b240cda8
5f3b2d48
Changes
11
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
369 additions
and
220 deletions
+369
-220
main.dart
examples/address_book/lib/main.dart
+31
-69
meal.dart
examples/fitness/lib/meal.dart
+1
-1
measurement.dart
examples/fitness/lib/measurement.dart
+1
-1
settings.dart
examples/fitness/lib/settings.dart
+1
-1
stock_home.dart
examples/stocks/lib/stock_home.dart
+2
-2
input.dart
packages/flutter/lib/src/material/input.dart
+162
-97
theme_data.dart
packages/flutter/lib/src/material/theme_data.dart
+19
-6
box_painter.dart
packages/flutter/lib/src/painting/box_painter.dart
+45
-2
editable_line.dart
packages/flutter/lib/src/rendering/editable_line.dart
+13
-12
editable.dart
packages/flutter/lib/src/widgets/editable.dart
+89
-24
input_test.dart
packages/flutter/test/widget/input_test.dart
+5
-5
No files found.
examples/address_book/lib/main.dart
View file @
0911e5d3
...
...
@@ -4,79 +4,41 @@
import
'package:flutter/material.dart'
;
class
Field
extends
StatelessComponent
{
Field
({
Key
key
,
this
.
inputKey
,
this
.
icon
,
this
.
placeholder
})
:
super
(
key:
key
);
final
GlobalKey
inputKey
;
final
String
icon
;
final
String
placeholder
;
Widget
build
(
BuildContext
context
)
{
return
new
Row
(
children:
<
Widget
>[
new
Padding
(
padding:
const
EdgeDims
.
symmetric
(
horizontal:
16.0
),
child:
new
Icon
(
icon:
icon
)
),
new
Flexible
(
child:
new
Input
(
key:
inputKey
,
placeholder:
placeholder
)
)
]
);
}
}
final
GlobalKey
_kNameKey
=
new
GlobalKey
(
debugLabel:
'name field'
);
final
GlobalKey
_kPhoneKey
=
new
GlobalKey
(
debugLabel:
'phone field'
);
final
GlobalKey
_kEmailKey
=
new
GlobalKey
(
debugLabel:
'email field'
);
final
GlobalKey
_kAddressKey
=
new
GlobalKey
(
debugLabel:
'address field'
);
final
GlobalKey
_kRingtoneKey
=
new
GlobalKey
(
debugLabel:
'ringtone field'
);
final
GlobalKey
_kNoteKey
=
new
GlobalKey
(
debugLabel:
'note field'
);
class
AddressBookHome
extends
StatelessComponent
{
Widget
buildToolBar
(
BuildContext
context
)
{
return
new
ToolBar
(
right:
<
Widget
>[
new
IconButton
(
icon:
"navigation/check"
)]
);
}
Widget
buildFloatingActionButton
(
BuildContext
context
)
{
return
new
FloatingActionButton
(
child:
new
Icon
(
icon:
'image/photo_camera'
),
backgroundColor:
Theme
.
of
(
context
).
accentColor
);
}
static
final
GlobalKey
nameKey
=
new
GlobalKey
(
debugLabel:
'name field'
);
static
final
GlobalKey
phoneKey
=
new
GlobalKey
(
debugLabel:
'phone field'
);
static
final
GlobalKey
emailKey
=
new
GlobalKey
(
debugLabel:
'email field'
);
static
final
GlobalKey
addressKey
=
new
GlobalKey
(
debugLabel:
'address field'
);
static
final
GlobalKey
ringtoneKey
=
new
GlobalKey
(
debugLabel:
'ringtone field'
);
static
final
GlobalKey
noteKey
=
new
GlobalKey
(
debugLabel:
'note field'
);
Widget
buildBody
(
BuildContext
context
)
{
return
new
Block
(
children:
<
Widget
>[
new
AspectRatio
(
aspectRatio:
16.0
/
9.0
,
child:
new
Container
(
decoration:
new
BoxDecoration
(
backgroundColor:
Colors
.
purple
[
300
])
)
),
new
Field
(
inputKey:
nameKey
,
icon:
"social/person"
,
placeholder:
"Name"
),
new
Field
(
inputKey:
phoneKey
,
icon:
"communication/phone"
,
placeholder:
"Phone"
),
new
Field
(
inputKey:
emailKey
,
icon:
"communication/email"
,
placeholder:
"Email"
),
new
Field
(
inputKey:
addressKey
,
icon:
"maps/place"
,
placeholder:
"Address"
),
new
Field
(
inputKey:
ringtoneKey
,
icon:
"av/volume_up"
,
placeholder:
"Ringtone"
),
new
Field
(
inputKey:
noteKey
,
icon:
"content/add"
,
placeholder:
"Add note"
),
]);
}
Widget
build
(
BuildContext
context
)
{
return
new
Scaffold
(
toolBar:
buildToolBar
(
context
),
body:
buildBody
(
context
),
floatingActionButton:
buildFloatingActionButton
(
context
)
toolBar:
new
ToolBar
(
center:
new
Text
(
'Edit contact'
),
right:
<
Widget
>[
new
IconButton
(
icon:
'navigation/check'
)
]
),
body:
new
Block
(
children:
<
Widget
>[
new
AspectRatio
(
aspectRatio:
16.0
/
9.0
,
child:
new
Container
(
decoration:
new
BoxDecoration
(
backgroundColor:
Colors
.
purple
[
300
])
)
),
new
Input
(
key:
_kNameKey
,
icon:
'social/person'
,
labelText:
'Name'
,
style:
Typography
.
black
.
display1
),
new
Input
(
key:
_kPhoneKey
,
icon:
'communication/phone'
,
hintText:
'Phone'
),
new
Input
(
key:
_kEmailKey
,
icon:
'communication/email'
,
hintText:
'Email'
),
new
Input
(
key:
_kAddressKey
,
icon:
'maps/place'
,
hintText:
'Address'
),
new
Input
(
key:
_kRingtoneKey
,
icon:
'av/volume_up'
,
hintText:
'Ringtone'
),
new
Input
(
key:
_kNoteKey
,
icon:
'content/add'
,
hintText:
'Add note'
),
]
),
floatingActionButton:
new
FloatingActionButton
(
child:
new
Icon
(
icon:
'image/photo_camera'
)
)
);
}
}
...
...
examples/fitness/lib/meal.dart
View file @
0911e5d3
...
...
@@ -90,7 +90,7 @@ class MealFragmentState extends State<MealFragment> {
new
Input
(
key:
descriptionKey
,
autofocus:
true
,
placeholder
:
'Describe meal'
,
hintText
:
'Describe meal'
,
onChanged:
_handleDescriptionChanged
),
],
...
...
examples/fitness/lib/measurement.dart
View file @
0911e5d3
...
...
@@ -142,7 +142,7 @@ class MeasurementFragmentState extends State<MeasurementFragment> {
new
Input
(
key:
weightKey
,
autofocus:
true
,
placeholder
:
'Enter weight'
,
hintText
:
'Enter weight'
,
keyboardType:
KeyboardType
.
number
,
onChanged:
_handleWeightChanged
),
...
...
examples/fitness/lib/settings.dart
View file @
0911e5d3
...
...
@@ -63,7 +63,7 @@ class SettingsFragmentState extends State<SettingsFragment> {
content:
new
Input
(
key:
weightGoalKey
,
autofocus:
true
,
placeholder
:
'Goal weight in lbs'
,
hintText
:
'Goal weight in lbs'
,
keyboardType:
KeyboardType
.
number
,
onChanged:
_handleGoalWeightChanged
),
...
...
examples/stocks/lib/stock_home.dart
View file @
0911e5d3
...
...
@@ -237,7 +237,7 @@ class StockHomeState extends State<StockHome> {
center:
new
Input
(
key:
searchFieldKey
,
autofocus:
true
,
placeholder
:
'Search stocks'
,
hintText
:
'Search stocks'
,
onChanged:
_handleSearchQueryChanged
),
backgroundColor:
Theme
.
of
(
context
).
canvasColor
...
...
@@ -254,7 +254,7 @@ class StockHomeState extends State<StockHome> {
new
Input
(
key:
companyNameKey
,
autofocus:
true
,
placeholder
:
'Company Name'
hintText
:
'Company Name'
),
]
);
...
...
packages/flutter/lib/src/material/input.dart
View file @
0911e5d3
...
...
@@ -2,71 +2,87 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'package:flutter/animation.dart'
;
import
'package:flutter/rendering.dart'
;
import
'package:flutter/services.dart'
;
import
'package:flutter/widgets.dart'
;
import
'colors.dart'
;
import
'debug.dart'
;
import
'icon.dart'
;
import
'theme.dart'
;
export
'package:flutter/rendering.dart'
show
ValueChanged
;
export
'package:flutter/services.dart'
show
KeyboardType
;
/// A material design text input
widget
.
class
Input
extends
S
crollable
{
/// A material design text input
field
.
class
Input
extends
S
tatefulComponent
{
Input
({
GlobalKey
key
,
this
.
initialValue
:
''
,
this
.
placeholder
,
this
.
keyboardType
:
KeyboardType
.
text
,
this
.
icon
,
this
.
labelText
,
this
.
hintText
,
this
.
errorText
,
this
.
style
,
this
.
hideText
:
false
,
this
.
isDense
:
false
,
this
.
autofocus
:
false
,
this
.
onChanged
,
this
.
keyboardType
:
KeyboardType
.
text
,
this
.
onSubmitted
})
:
super
(
key:
key
,
initialScrollOffset:
0.0
,
scrollDirection:
Axis
.
horizontal
)
{
})
:
super
(
key:
key
)
{
assert
(
key
!=
null
);
}
/// Initial editable text for the
widget
.
/// Initial editable text for the
input field
.
final
String
initialValue
;
/// The type of keyboard to use for editing the text.
final
KeyboardType
keyboardType
;
/// Hint text to show when the widget doesn't contain editable text.
final
String
placeholder
;
/// An icon to show adjacent to the input field.
final
String
icon
;
/// Text to show above the input field.
final
String
labelText
;
/// Text to show inline in the input field when it would otherwise be empty.
final
String
hintText
;
/// Text to show when the input text is invalid.
final
String
errorText
;
/// The style to use for the text being edited.
final
TextStyle
style
;
/// Whether to hide the text being edited (e.g., for passwords).
final
bool
hideText
;
/// Whether the input
widget
is part of a dense form (i.e., uses less vertical space).
/// Whether the input
field
is part of a dense form (i.e., uses less vertical space).
final
bool
isDense
;
/// Whether this input
widget
should focus itself is nothing else is already focused.
/// Whether this input
field
should focus itself is nothing else is already focused.
final
bool
autofocus
;
/// Called when the text being edited changes.
final
ValueChanged
<
String
>
onChanged
;
/// Called when the user indicates that they are done editing the text in the
widget
.
/// Called when the user indicates that they are done editing the text in the
field
.
final
ValueChanged
<
String
>
onSubmitted
;
InputState
createState
()
=>
new
InputState
();
_InputState
createState
()
=>
new
_
InputState
();
}
class
InputState
extends
ScrollableState
<
Input
>
{
const
Duration
_kTransitionDuration
=
const
Duration
(
milliseconds:
200
);
const
Curve
_kTransitionCurve
=
Curves
.
ease
;
class
_InputState
extends
State
<
Input
>
{
String
_value
;
EditableString
_editableString
;
KeyboardHandle
_keyboardHandle
=
KeyboardHandle
.
unattached
;
double
_contentWidth
=
0.0
;
double
_containerWidth
=
0.0
;
// Used by tests.
EditableString
get
editableValue
=>
_editableString
;
void
initState
()
{
...
...
@@ -79,6 +95,23 @@ class InputState extends ScrollableState<Input> {
);
}
void
dispose
()
{
if
(
_keyboardHandle
.
attached
)
_keyboardHandle
.
release
();
super
.
dispose
();
}
void
_attachOrDetachKeyboard
(
bool
focused
)
{
if
(
focused
&&
!
_keyboardHandle
.
attached
)
{
_keyboardHandle
=
keyboard
.
show
(
_editableString
.
stub
,
config
.
keyboardType
);
_keyboardHandle
.
setText
(
_editableString
.
text
);
_keyboardHandle
.
setSelection
(
_editableString
.
selection
.
start
,
_editableString
.
selection
.
end
);
}
else
if
(!
focused
&&
_keyboardHandle
.
attached
)
{
_keyboardHandle
.
release
();
}
}
void
_handleTextUpdated
()
{
if
(
_value
!=
_editableString
.
text
)
{
setState
(()
{
...
...
@@ -95,49 +128,124 @@ class InputState extends ScrollableState<Input> {
config
.
onSubmitted
(
_value
);
}
Widget
build
Content
(
BuildContext
context
)
{
Widget
build
(
BuildContext
context
)
{
assert
(
debugCheckHasMaterial
(
context
));
ThemeData
themeData
=
Theme
.
of
(
context
);
bool
focused
=
Focus
.
at
(
context
,
autofocus:
config
.
autofocus
);
if
(
focused
&&
!
_keyboardHandle
.
attached
)
{
_keyboardHandle
=
keyboard
.
show
(
_editableString
.
stub
,
config
.
keyboardType
);
_keyboardHandle
.
setText
(
_editableString
.
text
);
_keyboardHandle
.
setSelection
(
_editableString
.
selection
.
start
,
_editableString
.
selection
.
end
);
}
else
if
(!
focused
&&
_keyboardHandle
.
attached
)
{
_keyboardHandle
.
release
();
}
_attachOrDetachKeyboard
(
focused
);
TextStyle
textStyle
=
themeData
.
text
.
subhead
;
List
<
Widget
>
textChildren
=
<
Widget
>[];
TextStyle
textStyle
=
config
.
style
??
themeData
.
text
.
subhead
;
Color
focusHighlightColor
=
themeData
.
accentColor
;
if
(
themeData
.
primarySwatch
!=
null
)
focusHighlightColor
=
focused
?
themeData
.
primarySwatch
[
400
]
:
themeData
.
hintColor
;
double
topPadding
=
config
.
isDense
?
12.0
:
16.0
;
if
(
config
.
placeholder
!=
null
&&
_value
.
isEmpty
)
{
Widget
child
=
new
Opacity
(
key:
const
ValueKey
<
String
>(
'placeholder'
),
child:
new
Text
(
config
.
placeholder
,
style:
textStyle
),
opacity:
themeData
.
hintOpacity
);
textChildren
.
add
(
child
);
List
<
Widget
>
stackChildren
=
<
Widget
>[];
bool
hasInlineLabel
=
config
.
labelText
!=
null
&&
!
focused
&&
!
_value
.
isNotEmpty
;
if
(
config
.
labelText
!=
null
)
{
TextStyle
labelStyle
=
hasInlineLabel
?
themeData
.
text
.
subhead
.
copyWith
(
color:
themeData
.
hintColor
)
:
themeData
.
text
.
caption
.
copyWith
(
color:
focused
?
focusHighlightColor
:
themeData
.
hintColor
);
double
topPaddingIncrement
=
themeData
.
text
.
caption
.
fontSize
+
(
config
.
isDense
?
4.0
:
8.0
);
double
top
=
topPadding
;
if
(
hasInlineLabel
)
top
+=
topPaddingIncrement
+
textStyle
.
fontSize
-
labelStyle
.
fontSize
;
stackChildren
.
add
(
new
AnimatedPositioned
(
left:
0.0
,
top:
top
,
duration:
_kTransitionDuration
,
curve:
_kTransitionCurve
,
child:
new
Text
(
config
.
labelText
,
style:
labelStyle
)
));
topPadding
+=
topPaddingIncrement
;
}
Color
focusHighlightColor
=
themeData
.
accentColor
;
Color
cursorColor
=
themeData
.
accentColor
;
if
(
themeData
.
primarySwatch
!=
null
)
{
cursorColor
=
themeData
.
primarySwatch
[
200
];
focusHighlightColor
=
focused
?
themeData
.
primarySwatch
[
400
]
:
themeData
.
hintColor
;
if
(
config
.
hintText
!=
null
&&
_value
.
isEmpty
&&
!
hasInlineLabel
)
{
TextStyle
hintStyle
=
themeData
.
text
.
subhead
.
copyWith
(
color:
themeData
.
hintColor
);
stackChildren
.
add
(
new
Positioned
(
left:
0.0
,
top:
topPadding
+
textStyle
.
fontSize
-
hintStyle
.
fontSize
,
child:
new
Text
(
config
.
hintText
,
style:
hintStyle
)
));
}
Color
cursorColor
=
themeData
.
primarySwatch
==
null
?
themeData
.
accentColor
:
themeData
.
primarySwatch
[
200
];
EdgeDims
margin
=
new
EdgeDims
.
only
(
bottom:
config
.
isDense
?
4.0
:
8.0
);
EdgeDims
padding
=
new
EdgeDims
.
only
(
top:
topPadding
,
bottom:
8.0
);
Color
borderColor
=
focusHighlightColor
;
double
borderWidth
=
focused
?
2.0
:
1.0
;
if
(
config
.
errorText
!=
null
)
{
borderColor
=
themeData
.
errorColor
;
borderWidth
=
2.0
;
if
(!
config
.
isDense
)
{
margin
=
const
EdgeDims
.
only
(
bottom:
15.0
);
padding
=
new
EdgeDims
.
only
(
top:
topPadding
,
bottom:
1.0
);
}
}
textChildren
.
add
(
new
RawEditableLine
(
value:
_editableString
,
focused:
focused
,
style:
textStyle
,
hideText:
config
.
hideText
,
cursorColor:
cursorColor
,
onContentSizeChanged:
_handleContentSizeChanged
,
scrollOffset:
scrollOffsetVector
stackChildren
.
add
(
new
AnimatedContainer
(
margin:
margin
,
padding:
padding
,
duration:
_kTransitionDuration
,
curve:
_kTransitionCurve
,
decoration:
new
BoxDecoration
(
border:
new
Border
(
bottom:
new
BorderSide
(
color:
borderColor
,
width:
borderWidth
)
)
),
child:
new
RawEditableLine
(
value:
_editableString
,
focused:
focused
,
style:
textStyle
,
hideText:
config
.
hideText
,
cursorColor:
cursorColor
)
));
if
(
config
.
errorText
!=
null
&&
!
config
.
isDense
)
{
TextStyle
errorStyle
=
themeData
.
text
.
caption
.
copyWith
(
color:
themeData
.
errorColor
);
stackChildren
.
add
(
new
Positioned
(
left:
0.0
,
bottom:
0.0
,
child:
new
Text
(
config
.
errorText
,
style:
errorStyle
)
));
}
Widget
child
=
new
Stack
(
children:
stackChildren
);
if
(
config
.
icon
!=
null
)
{
double
iconSize
=
config
.
isDense
?
18.0
:
24.0
;
double
iconTop
=
topPadding
+
(
textStyle
.
fontSize
-
iconSize
)
/
2.0
;
child
=
new
Row
(
alignItems:
FlexAlignItems
.
start
,
children:
[
new
Container
(
margin:
new
EdgeDims
.
only
(
right:
16.0
,
top:
iconTop
),
width:
config
.
isDense
?
40.0
:
48.0
,
child:
new
Icon
(
icon:
config
.
icon
,
color:
focused
?
focusHighlightColor
:
Colors
.
black45
,
size:
config
.
isDense
?
IconSize
.
s18
:
IconSize
.
s24
)
),
new
Flexible
(
child:
child
)
]
);
}
return
new
GestureDetector
(
behavior:
HitTestBehavior
.
opaque
,
onTap:
()
{
...
...
@@ -149,53 +257,10 @@ class InputState extends ScrollableState<Input> {
// we'll get told to rebuild and we'll take care of the keyboard then
}
},
child:
new
SizeObserver
(
onSizeChanged:
_handleContainerSizeChanged
,
child:
new
Container
(
child:
new
Stack
(
children:
textChildren
),
margin:
config
.
isDense
?
const
EdgeDims
.
symmetric
(
vertical:
4.0
)
:
const
EdgeDims
.
symmetric
(
vertical:
8.0
),
padding:
const
EdgeDims
.
symmetric
(
vertical:
8.0
),
decoration:
new
BoxDecoration
(
border:
new
Border
(
bottom:
new
BorderSide
(
color:
focusHighlightColor
,
width:
focused
?
2.0
:
1.0
)
)
)
)
child:
new
Padding
(
padding:
const
EdgeDims
.
symmetric
(
horizontal:
16.0
),
child:
child
)
);
}
void
dispose
()
{
if
(
_keyboardHandle
.
attached
)
_keyboardHandle
.
release
();
super
.
dispose
();
}
ScrollBehavior
createScrollBehavior
()
=>
new
BoundedBehavior
();
BoundedBehavior
get
scrollBehavior
=>
super
.
scrollBehavior
;
void
_handleContainerSizeChanged
(
Size
newSize
)
{
_containerWidth
=
newSize
.
width
;
_updateScrollBehavior
();
}
void
_handleContentSizeChanged
(
Size
newSize
)
{
_contentWidth
=
newSize
.
width
;
_updateScrollBehavior
();
}
void
_updateScrollBehavior
()
{
// Set the scroll offset to match the content width so that the cursor
// (which is always at the end of the text) will be visible.
scrollTo
(
scrollBehavior
.
updateExtents
(
contentExtent:
_contentWidth
,
containerExtent:
_containerWidth
,
scrollOffset:
_contentWidth
));
}
}
packages/flutter/lib/src/material/theme_data.dart
View file @
0911e5d3
...
...
@@ -46,6 +46,7 @@ class ThemeData {
this
.
indicatorColor
,
this
.
hintColor
,
this
.
hintOpacity
,
this
.
errorColor
,
this
.
text
,
this
.
primaryTextTheme
,
this
.
primaryIconTheme
...
...
@@ -66,6 +67,7 @@ class ThemeData {
assert
(
indicatorColor
!=
null
);
assert
(
hintColor
!=
null
);
assert
(
hintOpacity
!=
null
);
assert
(
errorColor
!=
null
);
assert
(
text
!=
null
);
assert
(
primaryTextTheme
!=
null
);
assert
(
primaryIconTheme
!=
null
);
...
...
@@ -88,6 +90,7 @@ class ThemeData {
Color
indicatorColor
,
Color
hintColor
,
double
hintOpacity
,
Color
errorColor
,
TextTheme
text
,
TextTheme
primaryTextTheme
,
IconThemeData
primaryIconTheme
...
...
@@ -108,6 +111,7 @@ class ThemeData {
indicatorColor
??=
accentColor
==
primaryColor
?
Colors
.
white
:
accentColor
;
hintColor
??=
isDark
?
const
Color
(
0x42FFFFFF
)
:
const
Color
(
0x4C000000
);
hintOpacity
??=
hintColor
!=
null
?
hintColor
.
alpha
/
0xFF
:
isDark
?
0.26
:
0.30
;
errorColor
??=
Colors
.
red
[
700
];
text
??=
isDark
?
Typography
.
white
:
Typography
.
black
;
primaryTextTheme
??=
primaryColorBrightness
==
ThemeBrightness
.
dark
?
Typography
.
white
:
Typography
.
black
;
primaryIconTheme
??=
primaryColorBrightness
==
ThemeBrightness
.
dark
?
const
IconThemeData
(
color:
IconThemeColor
.
white
)
:
const
IconThemeData
(
color:
IconThemeColor
.
black
);
...
...
@@ -128,6 +132,7 @@ class ThemeData {
indicatorColor:
indicatorColor
,
hintColor:
hintColor
,
hintOpacity:
hintOpacity
,
errorColor:
errorColor
,
text:
text
,
primaryTextTheme:
primaryTextTheme
,
primaryIconTheme:
primaryIconTheme
...
...
@@ -182,6 +187,9 @@ class ThemeData {
final
Color
hintColor
;
final
double
hintOpacity
;
/// The color to use for input validation errors.
final
Color
errorColor
;
/// Text with a color that contrasts with the card and canvas colors.
final
TextTheme
text
;
...
...
@@ -217,6 +225,7 @@ class ThemeData {
indicatorColor:
Color
.
lerp
(
begin
.
indicatorColor
,
end
.
indicatorColor
,
t
),
hintColor:
Color
.
lerp
(
begin
.
hintColor
,
end
.
hintColor
,
t
),
hintOpacity:
lerpDouble
(
begin
.
hintOpacity
,
end
.
hintOpacity
,
t
),
errorColor:
Color
.
lerp
(
begin
.
errorColor
,
end
.
errorColor
,
t
),
text:
TextTheme
.
lerp
(
begin
.
text
,
end
.
text
,
t
),
primaryTextTheme:
TextTheme
.
lerp
(
begin
.
primaryTextTheme
,
end
.
primaryTextTheme
,
t
),
primaryIconTheme:
IconThemeData
.
lerp
(
begin
.
primaryIconTheme
,
end
.
primaryIconTheme
,
t
)
...
...
@@ -243,6 +252,7 @@ class ThemeData {
(
otherData
.
indicatorColor
==
indicatorColor
)
&&
(
otherData
.
hintColor
==
hintColor
)
&&
(
otherData
.
hintOpacity
==
hintOpacity
)
&&
(
otherData
.
errorColor
==
errorColor
)
&&
(
otherData
.
text
==
text
)
&&
(
otherData
.
primaryTextTheme
==
primaryTextTheme
)
&&
(
otherData
.
primaryIconTheme
==
primaryIconTheme
);
...
...
@@ -263,12 +273,15 @@ class ThemeData {
disabledColor
,
accentColor
,
accentColorBrightness
,
indicatorColor
,
hintColor
,
hintOpacity
,
text
,
primaryTextTheme
,
primaryIconTheme
hashValues
(
// Too many values.
indicatorColor
,
hintColor
,
hintOpacity
,
errorColor
,
text
,
primaryTextTheme
,
primaryIconTheme
)
);
}
...
...
packages/flutter/lib/src/painting/box_painter.dart
View file @
0911e5d3
...
...
@@ -29,6 +29,25 @@ class BorderSide {
/// A black border side of zero width.
static
const
none
=
const
BorderSide
(
width:
0.0
);
BorderSide
copyWith
({
Color
color
,
double
width
})
{
return
new
BorderSide
(
color:
color
??
this
.
color
,
width:
width
??
this
.
width
);
}
static
BorderSide
lerp
(
BorderSide
a
,
BorderSide
b
,
double
t
)
{
assert
(
a
!=
null
);
assert
(
b
!=
null
);
return
new
BorderSide
(
color:
Color
.
lerp
(
a
.
color
,
b
.
color
,
t
),
width:
ui
.
lerpDouble
(
a
.
width
,
b
.
width
,
t
)
);
}
bool
operator
==(
dynamic
other
)
{
if
(
identical
(
this
,
other
))
return
true
;
...
...
@@ -79,6 +98,30 @@ class Border {
return
new
EdgeDims
.
TRBL
(
top
.
width
,
right
.
width
,
bottom
.
width
,
left
.
width
);
}
Border
scale
(
double
t
)
{
return
new
Border
(
top:
top
.
copyWith
(
width:
t
*
top
.
width
),
right:
right
.
copyWith
(
width:
t
*
right
.
width
),
bottom:
bottom
.
copyWith
(
width:
t
*
bottom
.
width
),
left:
left
.
copyWith
(
width:
t
*
left
.
width
)
);
}
static
Border
lerp
(
Border
a
,
Border
b
,
double
t
)
{
if
(
a
==
null
&&
b
==
null
)
return
null
;
if
(
a
==
null
)
return
b
.
scale
(
t
);
if
(
b
==
null
)
return
a
.
scale
(
1.0
-
t
);
return
new
Border
(
top:
BorderSide
.
lerp
(
a
.
top
,
b
.
top
,
t
),
right:
BorderSide
.
lerp
(
a
.
right
,
b
.
right
,
t
),
bottom:
BorderSide
.
lerp
(
a
.
bottom
,
b
.
bottom
,
t
),
left:
BorderSide
.
lerp
(
a
.
left
,
b
.
left
,
t
)
);
}
bool
operator
==(
dynamic
other
)
{
if
(
identical
(
this
,
other
))
return
true
;
...
...
@@ -718,7 +761,7 @@ class BoxDecoration extends Decoration {
return
new
BoxDecoration
(
backgroundColor:
Color
.
lerp
(
null
,
backgroundColor
,
factor
),
backgroundImage:
backgroundImage
,
border:
border
,
border:
Border
.
lerp
(
null
,
border
,
factor
)
,
borderRadius:
ui
.
lerpDouble
(
null
,
borderRadius
,
factor
),
boxShadow:
BoxShadow
.
lerpList
(
null
,
boxShadow
,
factor
),
gradient:
gradient
,
...
...
@@ -740,7 +783,7 @@ class BoxDecoration extends Decoration {
return
new
BoxDecoration
(
backgroundColor:
Color
.
lerp
(
a
.
backgroundColor
,
b
.
backgroundColor
,
t
),
backgroundImage:
b
.
backgroundImage
,
border:
b
.
border
,
border:
Border
.
lerp
(
a
.
border
,
b
.
border
,
t
)
,
borderRadius:
ui
.
lerpDouble
(
a
.
borderRadius
,
b
.
borderRadius
,
t
),
boxShadow:
BoxShadow
.
lerpList
(
a
.
boxShadow
,
b
.
boxShadow
,
t
),
gradient:
b
.
gradient
,
...
...
packages/flutter/lib/src/rendering/editable_line.dart
View file @
0911e5d3
...
...
@@ -22,13 +22,14 @@ class RenderEditableLine extends RenderBox {
RenderEditableLine
({
StyledTextSpan
text
,
Color
cursorColor
,
bool
showCursor
,
this
.
onContentSizeChanged
,
Offset
scrollOffset
bool
showCursor
:
false
,
Offset
paintOffset:
Offset
.
zero
,
this
.
onContentSizeChanged
})
:
_textPainter
=
new
TextPainter
(
text
),
_cursorColor
=
cursorColor
,
_showCursor
=
showCursor
,
_scrollOffset
=
scrollOffset
{
_paintOffset
=
paintOffset
{
assert
(!
showCursor
||
cursorColor
!=
null
);
// TODO(abarth): These min/max values should be the default for TextPainter.
_textPainter
..
minWidth
=
0.0
...
...
@@ -71,12 +72,12 @@ class RenderEditableLine extends RenderBox {
markNeedsPaint
();
}
Offset
get
scrollOffset
=>
_scroll
Offset
;
Offset
_
scroll
Offset
;
void
set
scroll
Offset
(
Offset
value
)
{
if
(
_
scroll
Offset
==
value
)
Offset
get
paintOffset
=>
_paint
Offset
;
Offset
_
paint
Offset
;
void
set
paint
Offset
(
Offset
value
)
{
if
(
_
paint
Offset
==
value
)
return
;
_
scroll
Offset
=
value
;
_
paint
Offset
=
value
;
markNeedsPaint
();
}
...
...
@@ -155,12 +156,12 @@ class RenderEditableLine extends RenderBox {
}
void
_paintContents
(
PaintingContext
context
,
Offset
offset
)
{
_textPainter
.
paint
(
context
.
canvas
,
offset
-
_scroll
Offset
);
_textPainter
.
paint
(
context
.
canvas
,
offset
+
_paint
Offset
);
if
(
_showCursor
)
{
Rect
cursorRect
=
new
Rect
.
fromLTWH
(
offset
.
dx
+
_
contentSize
.
width
-
_kCursorWidth
-
_scrollOffset
.
dx
,
offset
.
dy
+
_
kCursorHeightOffset
-
_scrollOffset
.
dy
,
offset
.
dx
+
_
paintOffset
.
dx
+
_contentSize
.
width
-
_kCursorWidth
,
offset
.
dy
+
_
paintOffset
.
dy
+
_kCursorHeightOffset
,
_kCursorWidth
,
size
.
height
-
2.0
*
_kCursorHeightOffset
);
...
...
packages/flutter/lib/src/widgets/editable.dart
View file @
0911e5d3
...
...
@@ -11,26 +11,39 @@ import 'package:flutter/rendering.dart';
import
'basic.dart'
;
import
'framework.dart'
;
import
'scrollable.dart'
;
import
'scroll_behavior.dart'
;
const
Duration
_kCursorBlinkHalfPeriod
=
const
Duration
(
milliseconds:
500
);
/// A range of characters in a string of tet.
class
TextRange
{
const
TextRange
({
this
.
start
,
this
.
end
});
/// A text range that starts and ends at position.
const
TextRange
.
collapsed
(
int
position
)
:
start
=
position
,
end
=
position
;
/// A text range that contains nothing and is not in the text.
const
TextRange
.
empty
()
:
start
=
-
1
,
end
=
-
1
;
/// The index of the first character in the range.
final
int
start
;
/// The next index after the characters in this range.
final
int
end
;
/// Whether this range represents a valid position in the text.
bool
get
isValid
=>
start
>=
0
&&
end
>=
0
;
/// Whether this range is empty (but still potentially placed inside the text).
bool
get
isCollapsed
=>
start
==
end
;
}
/// A string that can be manipulated by a keyboard.
class
EditableString
implements
KeyboardClient
{
EditableString
({
this
.
text
:
''
,
this
.
onUpdated
,
this
.
onSubmitted
})
{
assert
(
onUpdated
!=
null
);
...
...
@@ -39,23 +52,35 @@ class EditableString implements KeyboardClient {
selection
=
new
TextRange
(
start:
text
.
length
,
end:
text
.
length
);
}
/// The current text being edited.
String
text
;
// The range of text that is still being composed.
TextRange
composing
=
const
TextRange
.
empty
();
/// The range of text that is currently selected.
TextRange
selection
;
/// Called whenever the text changes.
final
VoidCallback
onUpdated
;
/// Called whenever the user indicates they are done editing the string.
final
VoidCallback
onSubmitted
;
/// A keyboard client stub that can be attached to a keyboard service.
KeyboardClientStub
stub
;
/// The text before the given range.
String
textBefore
(
TextRange
range
)
{
return
text
.
substring
(
0
,
range
.
start
);
}
/// The text after the given range.
String
textAfter
(
TextRange
range
)
{
return
text
.
substring
(
range
.
end
);
}
/// The text inside the given range.
String
textInside
(
TextRange
range
)
{
return
text
.
substring
(
range
.
start
,
range
.
end
);
}
...
...
@@ -141,35 +166,72 @@ class EditableString implements KeyboardClient {
}
}
class
RawEditableLine
extends
StatefulComponent
{
/// A basic single-line input control.
///
/// This control is not intended to be used directly. Instead, consider using
/// [Input], which provides focus management and material design.
class
RawEditableLine
extends
Scrollable
{
RawEditableLine
({
Key
key
,
this
.
value
,
this
.
focused
:
false
,
this
.
hideText
:
false
,
this
.
style
,
this
.
cursorColor
,
this
.
onContentSizeChanged
,
this
.
scrollOffset
})
:
super
(
key:
key
);
this
.
cursorColor
})
:
super
(
key:
key
,
initialScrollOffset:
0.0
,
scrollDirection:
Axis
.
horizontal
);
/// The editable string being displayed in this widget.
final
EditableString
value
;
/// Whether this widget is focused.
final
bool
focused
;
/// Whether to hide the text being edited (e.g., for passwords).
final
bool
hideText
;
/// The text style to use for the editable text.
final
TextStyle
style
;
/// The color to use when painting the cursor.
final
Color
cursorColor
;
final
SizeChangedCallback
onContentSizeChanged
;
final
Offset
scrollOffset
;
RawEditableTextState
createState
()
=>
new
RawEditableTextState
();
}
class
RawEditableTextState
extends
State
<
RawEditableLine
>
{
// TODO(abarth): Move the cursor timer into RenderEditableLine so we can
// remove this extra widget.
class
RawEditableTextState
extends
ScrollableState
<
RawEditableLine
>
{
Timer
_cursorTimer
;
bool
_showCursor
=
false
;
double
_contentWidth
=
0.0
;
double
_containerWidth
=
0.0
;
ScrollBehavior
createScrollBehavior
()
=>
new
BoundedBehavior
();
BoundedBehavior
get
scrollBehavior
=>
super
.
scrollBehavior
;
void
_handleContainerSizeChanged
(
Size
newSize
)
{
_containerWidth
=
newSize
.
width
;
_updateScrollBehavior
();
}
void
_handleContentSizeChanged
(
Size
newSize
)
{
_contentWidth
=
newSize
.
width
;
_updateScrollBehavior
();
}
void
_updateScrollBehavior
()
{
// Set the scroll offset to match the content width so that the cursor
// (which is always at the end of the text) will be visible.
scrollTo
(
scrollBehavior
.
updateExtents
(
contentExtent:
_contentWidth
,
containerExtent:
_containerWidth
,
scrollOffset:
_contentWidth
));
}
/// Whether the blinking cursor is actually visible at this precise moment
/// (it's hidden half the time, since it blinks).
bool
get
cursorCurrentlyVisible
=>
_showCursor
;
...
...
@@ -202,7 +264,7 @@ class RawEditableTextState extends State<RawEditableLine> {
_showCursor
=
false
;
}
Widget
build
(
BuildContext
context
)
{
Widget
build
Content
(
BuildContext
context
)
{
assert
(
config
.
style
!=
null
);
assert
(
config
.
focused
!=
null
);
assert
(
config
.
cursorColor
!=
null
);
...
...
@@ -212,14 +274,17 @@ class RawEditableTextState extends State<RawEditableLine> {
else
if
(!
config
.
focused
&&
_cursorTimer
!=
null
)
_stopCursorTimer
();
return
new
_EditableLineWidget
(
value:
config
.
value
,
style:
config
.
style
,
cursorColor:
config
.
cursorColor
,
showCursor:
_showCursor
,
hideText:
config
.
hideText
,
onContentSizeChanged:
config
.
onContentSizeChanged
,
scrollOffset:
config
.
scrollOffset
return
new
SizeObserver
(
onSizeChanged:
_handleContainerSizeChanged
,
child:
new
_EditableLineWidget
(
value:
config
.
value
,
style:
config
.
style
,
cursorColor:
config
.
cursorColor
,
showCursor:
_showCursor
,
hideText:
config
.
hideText
,
onContentSizeChanged:
_handleContentSizeChanged
,
paintOffset:
new
Offset
(-
scrollOffset
,
0.0
)
)
);
}
}
...
...
@@ -233,7 +298,7 @@ class _EditableLineWidget extends LeafRenderObjectWidget {
this
.
showCursor
,
this
.
hideText
,
this
.
onContentSizeChanged
,
this
.
scroll
Offset
this
.
paint
Offset
})
:
super
(
key:
key
);
final
EditableString
value
;
...
...
@@ -242,7 +307,7 @@ class _EditableLineWidget extends LeafRenderObjectWidget {
final
bool
showCursor
;
final
bool
hideText
;
final
SizeChangedCallback
onContentSizeChanged
;
final
Offset
scroll
Offset
;
final
Offset
paint
Offset
;
RenderEditableLine
createRenderObject
()
{
return
new
RenderEditableLine
(
...
...
@@ -250,7 +315,7 @@ class _EditableLineWidget extends LeafRenderObjectWidget {
cursorColor:
cursorColor
,
showCursor:
showCursor
,
onContentSizeChanged:
onContentSizeChanged
,
scrollOffset:
scroll
Offset
paintOffset:
paint
Offset
);
}
...
...
@@ -260,7 +325,7 @@ class _EditableLineWidget extends LeafRenderObjectWidget {
renderObject
.
cursorColor
=
cursorColor
;
renderObject
.
showCursor
=
showCursor
;
renderObject
.
onContentSizeChanged
=
onContentSizeChanged
;
renderObject
.
scrollOffset
=
scroll
Offset
;
renderObject
.
paintOffset
=
paint
Offset
;
}
StyledTextSpan
get
_styledTextSpan
{
...
...
packages/flutter/test/widget/input_test.dart
View file @
0911e5d3
...
...
@@ -41,7 +41,7 @@ void main() {
child:
new
Material
(
child:
new
Input
(
key:
inputKey
,
placeholder
:
'Placeholder'
,
hintText
:
'Placeholder'
,
onChanged:
(
String
value
)
{
inputValue
=
value
;
}
)
)
...
...
@@ -81,7 +81,7 @@ void main() {
child:
new
Material
(
child:
new
Input
(
key:
inputKey
,
placeholder
:
'Placeholder'
hintText
:
'Placeholder'
)
)
);
...
...
@@ -123,7 +123,7 @@ void main() {
child:
new
Material
(
child:
new
Input
(
key:
inputKey
,
placeholder
:
'Placeholder'
hintText
:
'Placeholder'
)
)
);
...
...
@@ -133,7 +133,7 @@ void main() {
const
String
testValue
=
'ABC'
;
mockKeyboard
.
client
.
commitText
(
testValue
,
testValue
.
length
);
InputState
input
=
tester
.
findStateOfType
(
InputState
)
;
dynamic
input
=
inputKey
.
currentState
;
// Delete characters and verify that the selection follows the length
// of the text.
...
...
@@ -159,7 +159,7 @@ void main() {
child:
new
Input
(
key:
inputKey
,
hideText:
true
,
placeholder
:
'Placeholder'
hintText
:
'Placeholder'
)
)
);
...
...
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