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
b63ced55
Unverified
Commit
b63ced55
authored
Oct 23, 2018
by
xster
Committed by
GitHub
Oct 23, 2018
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add a CupertinoTextField (#23194)
parent
dc36195c
Changes
12
Hide whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
1664 additions
and
92 deletions
+1664
-92
cupertino.dart
examples/flutter_gallery/lib/demo/cupertino/cupertino.dart
+1
-0
cupertino_text_field_demo.dart
...gallery/lib/demo/cupertino/cupertino_text_field_demo.dart
+189
-0
demos.dart
examples/flutter_gallery/lib/gallery/demos.dart
+7
-0
cupertino.dart
packages/flutter/lib/cupertino.dart
+1
-0
app.dart
packages/flutter/lib/src/cupertino/app.dart
+1
-1
icons.dart
packages/flutter/lib/src/cupertino/icons.dart
+40
-0
text_field.dart
packages/flutter/lib/src/cupertino/text_field.dart
+660
-0
text_selection.dart
packages/flutter/lib/src/cupertino/text_selection.dart
+5
-2
text_field.dart
packages/flutter/lib/src/material/text_field.dart
+16
-78
editable_text.dart
packages/flutter/lib/src/widgets/editable_text.dart
+53
-11
text_selection.dart
packages/flutter/lib/src/widgets/text_selection.dart
+3
-0
text_field_test.dart
packages/flutter/test/cupertino/text_field_test.dart
+688
-0
No files found.
examples/flutter_gallery/lib/demo/cupertino/cupertino.dart
View file @
b63ced55
...
...
@@ -11,3 +11,4 @@ export 'cupertino_refresh_demo.dart';
export
'cupertino_segmented_control_demo.dart'
;
export
'cupertino_slider_demo.dart'
;
export
'cupertino_switch_demo.dart'
;
export
'cupertino_text_field_demo.dart'
;
examples/flutter_gallery/lib/demo/cupertino/cupertino_text_field_demo.dart
0 → 100644
View file @
b63ced55
// Copyright 2018 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
'package:flutter/cupertino.dart'
;
class
CupertinoTextFieldDemo
extends
StatefulWidget
{
static
const
String
routeName
=
'/cupertino/text_fields'
;
@override
_CupertinoTextFieldDemoState
createState
()
{
return
_CupertinoTextFieldDemoState
();
}
}
class
_CupertinoTextFieldDemoState
extends
State
<
CupertinoTextFieldDemo
>
{
TextEditingController
_chatTextController
;
TextEditingController
_locationTextController
;
@override
void
initState
()
{
super
.
initState
();
_chatTextController
=
TextEditingController
();
_locationTextController
=
TextEditingController
(
text:
'Montreal, Canada'
);
}
Widget
_buildChatTextField
()
{
return
CupertinoTextField
(
controller:
_chatTextController
,
textCapitalization:
TextCapitalization
.
sentences
,
placeholder:
'Text Message'
,
decoration:
BoxDecoration
(
border:
Border
.
all
(
width:
0.0
,
color:
CupertinoColors
.
inactiveGray
,
),
borderRadius:
BorderRadius
.
circular
(
15.0
),
),
maxLines:
null
,
keyboardType:
TextInputType
.
multiline
,
prefix:
const
Padding
(
padding:
EdgeInsets
.
symmetric
(
horizontal:
4.0
)),
suffix:
Padding
(
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
4.0
),
child:
CupertinoButton
(
color:
CupertinoColors
.
activeGreen
,
minSize:
0.0
,
child:
const
Icon
(
CupertinoIcons
.
up_arrow
,
size:
21.0
,
color:
CupertinoColors
.
white
,
),
padding:
const
EdgeInsets
.
all
(
2.0
),
borderRadius:
BorderRadius
.
circular
(
15.0
),
onPressed:
()=>
setState
(()=>
_chatTextController
.
clear
()),
),
),
autofocus:
true
,
suffixMode:
OverlayVisibilityMode
.
editing
,
onSubmitted:
(
String
text
)=>
setState
(()=>
_chatTextController
.
clear
()),
);
}
Widget
_buildNameField
()
{
return
const
CupertinoTextField
(
prefix:
Icon
(
CupertinoIcons
.
person_solid
,
color:
CupertinoColors
.
lightBackgroundGray
,
size:
28.0
,
),
padding:
EdgeInsets
.
symmetric
(
horizontal:
6.0
,
vertical:
12.0
),
clearButtonMode:
OverlayVisibilityMode
.
editing
,
textCapitalization:
TextCapitalization
.
words
,
autocorrect:
false
,
decoration:
BoxDecoration
(
border:
Border
(
bottom:
BorderSide
(
width:
0.0
,
color:
CupertinoColors
.
inactiveGray
)),
),
placeholder:
'Name'
,
);
}
Widget
_buildEmailField
()
{
return
const
CupertinoTextField
(
prefix:
Icon
(
CupertinoIcons
.
mail_solid
,
color:
CupertinoColors
.
lightBackgroundGray
,
size:
28.0
,
),
padding:
EdgeInsets
.
symmetric
(
horizontal:
6.0
,
vertical:
12.0
),
clearButtonMode:
OverlayVisibilityMode
.
editing
,
keyboardType:
TextInputType
.
emailAddress
,
autocorrect:
false
,
decoration:
BoxDecoration
(
border:
Border
(
bottom:
BorderSide
(
width:
0.0
,
color:
CupertinoColors
.
inactiveGray
)),
),
placeholder:
'Email'
,
);
}
Widget
_buildLocationField
()
{
return
CupertinoTextField
(
controller:
_locationTextController
,
prefix:
const
Icon
(
CupertinoIcons
.
location_solid
,
color:
CupertinoColors
.
lightBackgroundGray
,
size:
28.0
,
),
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
6.0
,
vertical:
12.0
),
clearButtonMode:
OverlayVisibilityMode
.
editing
,
textCapitalization:
TextCapitalization
.
words
,
decoration:
const
BoxDecoration
(
border:
Border
(
bottom:
BorderSide
(
width:
0.0
,
color:
CupertinoColors
.
inactiveGray
)),
),
placeholder:
'Location'
,
);
}
Widget
_buildPinField
()
{
return
const
CupertinoTextField
(
prefix:
Icon
(
CupertinoIcons
.
padlock_solid
,
color:
CupertinoColors
.
lightBackgroundGray
,
size:
28.0
,
),
padding:
EdgeInsets
.
symmetric
(
horizontal:
6.0
,
vertical:
12.0
),
clearButtonMode:
OverlayVisibilityMode
.
editing
,
keyboardType:
TextInputType
.
number
,
autocorrect:
false
,
obscureText:
true
,
decoration:
BoxDecoration
(
border:
Border
(
bottom:
BorderSide
(
width:
0.0
,
color:
CupertinoColors
.
inactiveGray
)),
),
placeholder:
'Create a PIN'
,
);
}
Widget
_buildTagsField
()
{
return
CupertinoTextField
(
controller:
TextEditingController
(
text:
'colleague, reading club'
),
prefix:
const
Icon
(
CupertinoIcons
.
tags_solid
,
color:
CupertinoColors
.
lightBackgroundGray
,
size:
28.0
,
),
enabled:
false
,
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
6.0
,
vertical:
12.0
),
decoration:
const
BoxDecoration
(
border:
Border
(
bottom:
BorderSide
(
width:
0.0
,
color:
CupertinoColors
.
inactiveGray
)),
),
);
}
@override
Widget
build
(
BuildContext
context
)
{
return
DefaultTextStyle
(
style:
const
TextStyle
(
fontFamily:
'.SF UI Text'
,
inherit:
false
,
fontSize:
17.0
,
color:
CupertinoColors
.
black
,
),
child:
CupertinoPageScaffold
(
navigationBar:
const
CupertinoNavigationBar
(
previousPageTitle:
'Cupertino'
,
middle:
Text
(
'Text Fields'
),
),
child:
ListView
(
children:
<
Widget
>[
Padding
(
padding:
const
EdgeInsets
.
symmetric
(
vertical:
32.0
,
horizontal:
16.0
),
child:
Column
(
children:
<
Widget
>[
_buildNameField
(),
_buildEmailField
(),
_buildLocationField
(),
_buildPinField
(),
_buildTagsField
(),
],
),
),
Padding
(
padding:
const
EdgeInsets
.
symmetric
(
vertical:
32.0
,
horizontal:
16.0
),
child:
_buildChatTextField
(),
),
],
),
),
);
}
}
examples/flutter_gallery/lib/gallery/demos.dart
View file @
b63ced55
...
...
@@ -500,6 +500,13 @@ List<GalleryDemo> _buildGalleryDemos() {
documentationUrl:
'https://docs.flutter.io/flutter/cupertino/CupertinoSwitch-class.html'
,
buildRoute:
(
BuildContext
context
)
=>
CupertinoSwitchDemo
(),
),
GalleryDemo
(
title:
'Text Fields'
,
icon:
GalleryIcons
.
text_fields_alt
,
category:
_kCupertinoComponents
,
routeName:
CupertinoTextFieldDemo
.
routeName
,
buildRoute:
(
BuildContext
context
)
=>
CupertinoTextFieldDemo
(),
),
// Media
GalleryDemo
(
...
...
packages/flutter/lib/cupertino.dart
View file @
b63ced55
...
...
@@ -28,6 +28,7 @@ export 'src/cupertino/slider.dart';
export
'src/cupertino/switch.dart'
;
export
'src/cupertino/tab_scaffold.dart'
;
export
'src/cupertino/tab_view.dart'
;
export
'src/cupertino/text_field.dart'
;
export
'src/cupertino/text_selection.dart'
;
export
'src/cupertino/thumb_painter.dart'
;
export
'widgets.dart'
;
packages/flutter/lib/src/cupertino/app.dart
View file @
b63ced55
...
...
@@ -243,7 +243,7 @@ class _CupertinoAppState extends State<CupertinoApp> {
_navigatorObservers
=
List
<
NavigatorObserver
>.
from
(
widget
.
navigatorObservers
)
..
add
(
_heroController
);
}
else
{
_navigatorObservers
=
null
;
_navigatorObservers
=
const
<
NavigatorObserver
>[]
;
}
}
...
...
packages/flutter/lib/src/cupertino/icons.dart
View file @
b63ced55
...
...
@@ -721,4 +721,44 @@ class CupertinoIcons {
/// * [group], which is similar, but not filled in.
/// * [person_solid], which is just a single person.
static
const
IconData
group_solid
=
IconData
(
0xf47c
,
fontFamily:
iconFont
,
fontPackage:
iconFontPackage
);
/// Outline of a closed mail envelope.
static
const
IconData
mail
=
IconData
(
0xf422
,
fontFamily:
iconFont
,
fontPackage:
iconFontPackage
);
/// A closed mail envelope. This icon is filled in.
static
const
IconData
mail_solid
=
IconData
(
0xf423
,
fontFamily:
iconFont
,
fontPackage:
iconFontPackage
);
/// Outline of a location pin.
static
const
IconData
location
=
IconData
(
0xf455
,
fontFamily:
iconFont
,
fontPackage:
iconFontPackage
);
/// A location pin. This icon is filled in.
static
const
IconData
location_solid
=
IconData
(
0xf456
,
fontFamily:
iconFont
,
fontPackage:
iconFontPackage
);
/// Outline of a sticker tag.
///
/// See also:
///
/// * [tags], similar but with 2 overlapping tags.
static
const
IconData
tag
=
IconData
(
0xf48c
,
fontFamily:
iconFont
,
fontPackage:
iconFontPackage
);
/// A sticker tag. This icon is filled in.
///
/// See also:
///
/// * [tags_solid], similar but with 2 overlapping tags.
static
const
IconData
tag_solid
=
IconData
(
0xf48d
,
fontFamily:
iconFont
,
fontPackage:
iconFontPackage
);
/// Outlines of 2 overlapping sticker tags.
///
/// See also:
///
/// * [tag], similar but with only one tag.
static
const
IconData
tags
=
IconData
(
0xf48e
,
fontFamily:
iconFont
,
fontPackage:
iconFontPackage
);
/// 2 overlapping sticker tags. This icon is filled in.
///
/// See also:
///
/// * [tag_solid], similar but with only one tag.
static
const
IconData
tags_solid
=
IconData
(
0xf48f
,
fontFamily:
iconFont
,
fontPackage:
iconFontPackage
);
}
packages/flutter/lib/src/cupertino/text_field.dart
0 → 100644
View file @
b63ced55
// Copyright 2018 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
'package:flutter/rendering.dart'
;
import
'package:flutter/services.dart'
;
import
'package:flutter/widgets.dart'
;
import
'colors.dart'
;
import
'icons.dart'
;
import
'text_selection.dart'
;
export
'package:flutter/services.dart'
show
TextInputType
,
TextInputAction
,
TextCapitalization
;
// Value extracted via color reader from iOS simulator.
const
BorderSide
_kDefaultRoundedBorderSide
=
BorderSide
(
color:
CupertinoColors
.
lightBackgroundGray
,
style:
BorderStyle
.
solid
,
width:
0.0
,
);
const
Border
_kDefaultRoundedBorder
=
Border
(
top:
_kDefaultRoundedBorderSide
,
bottom:
_kDefaultRoundedBorderSide
,
left:
_kDefaultRoundedBorderSide
,
right:
_kDefaultRoundedBorderSide
,
);
// Counted manually on magnified simulator.
const
BoxDecoration
_kDefaultRoundedBorderDecoration
=
BoxDecoration
(
border:
_kDefaultRoundedBorder
,
borderRadius:
BorderRadius
.
all
(
Radius
.
circular
(
4.0
)),
);
// Default iOS style from HIG specs with larger font.
const
TextStyle
_kDefaultTextStyle
=
TextStyle
(
fontFamily:
'.SF Pro Text'
,
fontSize:
17.0
,
letterSpacing:
-
0.38
,
color:
CupertinoColors
.
black
,
decoration:
TextDecoration
.
none
,
);
// Value extracted via color reader from iOS simulator.
const
Color
_kSelectionHighlightColor
=
Color
(
0x667FAACF
);
const
Color
_kInactiveTextColor
=
Color
(
0xFFC2C2C2
);
const
Color
_kDisabledBackground
=
Color
(
0xFFFAFAFA
);
/// Visibility of text field overlays based on the state of the current text entry.
///
/// Used to toggle the visibility behavior of the optional decorating widgets
/// surrounding the [EditableText] such as the clear text button.
enum
OverlayVisibilityMode
{
/// Overlay will never appear regardless of the text entry state.
never
,
/// Overlay will only appear when the current text entry is not empty.
///
/// This includes pre-filled text that the user did not type in manually. But
/// does not include text in placeholders.
editing
,
/// Overlay will only appear when the current text entry is empty.
///
/// This also includes not having pre-filled text that the user did not type
/// in manually. Texts in placeholders are ignored.
notEditing
,
/// Always show the overlay regardless of the text entry state.
always
,
}
/// An iOS-style text field.
///
/// A text field lets the user enter text, either with a hardware keyboard or with
/// an onscreen keyboard.
///
/// This widget corresponds to both a `UITextField` and an editable `UITextView`
/// on iOS.
///
/// The text field calls the [onChanged] callback whenever the user changes the
/// text in the field. If the user indicates that they are done typing in the
/// field (e.g., by pressing a button on the soft keyboard), the text field
/// calls the [onSubmitted] callback.
///
/// To control the text that is displayed in the text field, use the
/// [controller]. For example, to set the initial value of the text field, use
/// a [controller] that already contains some text such as:
///
/// ## Sample code
///
/// ```dart
/// class MyPrefilledText extends StatefulWidget {
/// @override
/// _MyPrefilledTextState createState() => _MyPrefilledTextState();
/// }
///
/// class _MyPrefilledTextState extends State<MyPrefilledText> {
/// TextEditingController _textController;
///
/// @override
/// void initState() {
/// super.initState();
/// _textController = TextEditingController(text: 'initial text');
/// }
///
/// @override
/// Widget build(BuildContext context) {
/// return CupertinoTextField(controller: _textController);
/// }
/// }
/// ```
///
/// The [controller] can also control the selection and composing region (and to
/// observe changes to the text, selection, and composing region).
///
/// The text field has an overridable [decoration] that, by default, draws a
/// rounded rectangle border around the text field. If you set the [decoration]
/// property to null, the decoration will be removed entirely.
///
/// See also:
///
/// * <https://developer.apple.com/documentation/uikit/uitextfield>
/// * [TextField], an alternative text field widget that follows the Material
/// Design UI conventions.
/// * [EditableText], which is the raw text editing control at the heart of a
/// [TextField].
class
CupertinoTextField
extends
StatefulWidget
{
/// Creates an iOS-style text field.
///
/// To provide a prefilled text entry, pass in a [TextEditingController] with
/// an initial value to the [controller] parameter.
///
/// To provide a hint placeholder text that appears when the text entry is
/// empty, pass a [String] to the [placeholder] parameter.
///
/// The [maxLines] property can be set to null to remove the restriction on
/// the number of lines. In this mode, the intrinsic height of the widget will
/// grow as the number of lines of text grows. By default, it is `1`, meaning
/// this is a single-line text field and will scroll horizontally when
/// overflown. [maxLines] must not be zero.
///
/// See also:
///
/// * [maxLength], which discusses the precise meaning of "number of
/// characters" and how it may differ from the intuitive meaning.
const
CupertinoTextField
({
Key
key
,
this
.
controller
,
this
.
focusNode
,
this
.
decoration
=
_kDefaultRoundedBorderDecoration
,
this
.
padding
=
const
EdgeInsets
.
all
(
6.0
),
this
.
placeholder
,
this
.
prefix
,
this
.
prefixMode
=
OverlayVisibilityMode
.
always
,
this
.
suffix
,
this
.
suffixMode
=
OverlayVisibilityMode
.
always
,
this
.
clearButtonMode
=
OverlayVisibilityMode
.
never
,
TextInputType
keyboardType
,
this
.
textInputAction
,
this
.
textCapitalization
=
TextCapitalization
.
none
,
this
.
style
=
_kDefaultTextStyle
,
this
.
textAlign
=
TextAlign
.
start
,
this
.
autofocus
=
false
,
this
.
obscureText
=
false
,
this
.
autocorrect
=
true
,
this
.
maxLines
=
1
,
this
.
maxLength
,
this
.
maxLengthEnforced
=
true
,
this
.
onChanged
,
this
.
onEditingComplete
,
this
.
onSubmitted
,
this
.
inputFormatters
,
this
.
enabled
,
this
.
cursorWidth
=
2.0
,
this
.
cursorRadius
,
this
.
cursorColor
=
CupertinoColors
.
activeBlue
,
this
.
keyboardAppearance
,
this
.
scrollPadding
=
const
EdgeInsets
.
all
(
20.0
),
})
:
assert
(
textAlign
!=
null
),
assert
(
autofocus
!=
null
),
assert
(
obscureText
!=
null
),
assert
(
autocorrect
!=
null
),
assert
(
maxLengthEnforced
!=
null
),
assert
(
scrollPadding
!=
null
),
assert
(
maxLines
==
null
||
maxLines
>
0
),
assert
(
maxLength
==
null
||
maxLength
>
0
),
assert
(
clearButtonMode
!=
null
),
assert
(
prefixMode
!=
null
),
assert
(
suffixMode
!=
null
),
keyboardType
=
keyboardType
??
(
maxLines
==
1
?
TextInputType
.
text
:
TextInputType
.
multiline
),
super
(
key:
key
);
/// Controls the text being edited.
///
/// If null, this widget will create its own [TextEditingController].
final
TextEditingController
controller
;
/// Controls whether this widget has keyboard focus.
///
/// If null, this widget will create its own [FocusNode].
final
FocusNode
focusNode
;
/// Controls the [BoxDecoration] of the box behind the text input.
///
/// Defaults to having a rounded rectangle grey border and can be null to have
/// no box decoration.
final
BoxDecoration
decoration
;
/// Padding around the text entry area between the [prefix] and [suffix]
/// or the clear button when [clearButtonMode] is not never.
///
/// Defaults to a padding of 6 pixels on all sides and can be null.
final
EdgeInsetsGeometry
padding
;
/// A lighter colored placeholder hint that appears on the first line of the
/// text field when the text entry is empty.
///
/// Defaults to having no placeholder text.
///
/// The text style of the placeholder text matches that of the text field's
/// main text entry except a lighter font weight and a grey font color.
final
String
placeholder
;
/// An optional [Widget] to display before the text.
final
Widget
prefix
;
/// Controls the visibility of the [prefix] widget based on the state of
/// text entry when the [prefix] argument is not null.
///
/// Defaults to [OverlayVisibilityMode.always] and cannot be null.
///
/// Has no effect when [prefix] is null.
final
OverlayVisibilityMode
prefixMode
;
/// An optional [Widget] to display after the text.
final
Widget
suffix
;
/// Controls the visibility of the [suffix] widget based on the state of
/// text entry when the [suffix] argument is not null.
///
/// Defaults to [OverlayVisibilityMode.always] and cannot be null.
///
/// Has no effect when [suffix] is null.
final
OverlayVisibilityMode
suffixMode
;
/// Show an iOS-style clear button to clear the current text entry.
///
/// Can be made to appear depending on various text states of the
/// [TextEditingController].
///
/// Will only appear if no [suffix] widget is appearing.
///
/// Defaults to never appearing and cannot be null.
final
OverlayVisibilityMode
clearButtonMode
;
/// {@macro flutter.widgets.editableText.keyboardType}
final
TextInputType
keyboardType
;
/// The type of action button to use for the keyboard.
///
/// Defaults to [TextInputAction.newline] if [keyboardType] is
/// [TextInputType.multiline] and [TextInputAction.done] otherwise.
final
TextInputAction
textInputAction
;
/// {@macro flutter.widgets.editableText.textCapitalization}
final
TextCapitalization
textCapitalization
;
/// The style to use for the text being edited.
///
/// Also serves as a base for the [placeholder] text's style.
///
/// Defaults to a standard iOS style and cannot be null.
final
TextStyle
style
;
/// {@macro flutter.widgets.editableText.textAlign}
final
TextAlign
textAlign
;
/// {@macro flutter.widgets.editableText.autofocus}
final
bool
autofocus
;
/// {@macro flutter.widgets.editableText.obscureText}
final
bool
obscureText
;
/// {@macro flutter.widgets.editableText.autocorrect}
final
bool
autocorrect
;
/// {@macro flutter.widgets.editableText.maxLines}
final
int
maxLines
;
/// The maximum number of characters (Unicode scalar values) to allow in the
/// text field.
///
/// If set, a character counter will be displayed below the
/// field, showing how many characters have been entered and how many are
/// allowed. After [maxLength] characters have been input, additional input
/// is ignored, unless [maxLengthEnforced] is set to false. The TextField
/// enforces the length with a [LengthLimitingTextInputFormatter], which is
/// evaluated after the supplied [inputFormatters], if any.
///
/// This value must be either null or greater than zero. If set to null
/// (the default), there is no limit to the number of characters allowed.
///
/// Whitespace characters (e.g. newline, space, tab) are included in the
/// character count.
///
/// If [maxLengthEnforced] is set to false, then more than [maxLength]
/// characters may be entered, but the error counter and divider will
/// switch to the [decoration.errorStyle] when the limit is exceeded.
///
/// ## Limitations
///
/// The CupertinoTextField does not currently count Unicode grapheme clusters
/// (i.e. characters visible to the user), it counts Unicode scalar values,
/// which leaves out a number of useful possible characters (like many emoji
/// and composed characters), so this will be inaccurate in the presence of
/// those characters. If you expect to encounter these kinds of characters, be
/// generous in the maxLength used.
///
/// For instance, the character "ö" can be represented as '\u{006F}\u{0308}',
/// which is the letter "o" followed by a composed diaeresis "¨", or it can
/// be represented as '\u{00F6}', which is the Unicode scalar value "LATIN
/// SMALL LETTER O WITH DIAERESIS". In the first case, the text field will
/// count two characters, and the second case will be counted as one
/// character, even though the user can see no difference in the input.
///
/// Similarly, some emoji are represented by multiple scalar values. The
/// Unicode "THUMBS UP SIGN + MEDIUM SKIN TONE MODIFIER", "👍🏽", should be
/// counted as a single character, but because it is a combination of two
/// Unicode scalar values, '\u{1F44D}\u{1F3FD}', it is counted as two
/// characters.
///
/// See also:
///
/// * [LengthLimitingTextInputFormatter] for more information on how it
/// counts characters, and how it may differ from the intuitive meaning.
final
int
maxLength
;
/// If true, prevents the field from allowing more than [maxLength]
/// characters.
///
/// If [maxLength] is set, [maxLengthEnforced] indicates whether or not to
/// enforce the limit, or merely provide a character counter and warning when
/// [maxLength] is exceeded.
final
bool
maxLengthEnforced
;
/// {@macro flutter.widgets.editableText.onChanged}
final
ValueChanged
<
String
>
onChanged
;
/// {@macro flutter.widgets.editableText.onEditingComplete}
final
VoidCallback
onEditingComplete
;
/// {@macro flutter.widgets.editableText.onSubmitted}
final
ValueChanged
<
String
>
onSubmitted
;
/// {@macro flutter.widgets.editableText.inputFormatters}
final
List
<
TextInputFormatter
>
inputFormatters
;
/// Disables the text field when false.
///
/// Text fields in disabled states have a light grey background and don't
/// respond to touch events including the [prefix], [suffix] and the clear
/// button.
final
bool
enabled
;
/// {@macro flutter.widgets.editableText.cursorWidth}
final
double
cursorWidth
;
/// {@macro flutter.widgets.editableText.cursorRadius}
final
Radius
cursorRadius
;
/// The color to use when painting the cursor.
///
/// Defaults to the standard iOS blue color. Cannot be null.
final
Color
cursorColor
;
/// The appearance of the keyboard.
///
/// This setting is only honored on iOS devices.
///
/// If null, defaults to [Brightness.light].
final
Brightness
keyboardAppearance
;
/// {@macro flutter.widgets.editableText.scrollPadding}
final
EdgeInsets
scrollPadding
;
@override
_CupertinoTextFieldState
createState
()
=>
_CupertinoTextFieldState
();
@override
void
debugFillProperties
(
DiagnosticPropertiesBuilder
properties
)
{
super
.
debugFillProperties
(
properties
);
properties
.
add
(
DiagnosticsProperty
<
TextEditingController
>(
'controller'
,
controller
,
defaultValue:
null
));
properties
.
add
(
DiagnosticsProperty
<
FocusNode
>(
'focusNode'
,
focusNode
,
defaultValue:
null
));
properties
.
add
(
DiagnosticsProperty
<
BoxDecoration
>(
'decoration'
,
decoration
));
properties
.
add
(
DiagnosticsProperty
<
EdgeInsetsGeometry
>(
'padding'
,
padding
));
properties
.
add
(
StringProperty
(
'placeholder'
,
placeholder
));
properties
.
add
(
DiagnosticsProperty
<
OverlayVisibilityMode
>(
'prefix'
,
prefix
==
null
?
null
:
prefixMode
));
properties
.
add
(
DiagnosticsProperty
<
OverlayVisibilityMode
>(
'suffix'
,
suffix
==
null
?
null
:
suffixMode
));
properties
.
add
(
DiagnosticsProperty
<
OverlayVisibilityMode
>(
'clearButtonMode'
,
clearButtonMode
));
properties
.
add
(
DiagnosticsProperty
<
TextInputType
>(
'keyboardType'
,
keyboardType
,
defaultValue:
TextInputType
.
text
));
properties
.
add
(
DiagnosticsProperty
<
TextStyle
>(
'style'
,
style
,
defaultValue:
null
));
properties
.
add
(
DiagnosticsProperty
<
bool
>(
'autofocus'
,
autofocus
,
defaultValue:
false
));
properties
.
add
(
DiagnosticsProperty
<
bool
>(
'obscureText'
,
obscureText
,
defaultValue:
false
));
properties
.
add
(
DiagnosticsProperty
<
bool
>(
'autocorrect'
,
autocorrect
,
defaultValue:
false
));
properties
.
add
(
IntProperty
(
'maxLines'
,
maxLines
,
defaultValue:
1
));
properties
.
add
(
IntProperty
(
'maxLength'
,
maxLength
,
defaultValue:
null
));
properties
.
add
(
FlagProperty
(
'maxLengthEnforced'
,
value:
maxLengthEnforced
,
ifTrue:
'max length enforced'
));
}
}
class
_CupertinoTextFieldState
extends
State
<
CupertinoTextField
>
with
AutomaticKeepAliveClientMixin
{
final
GlobalKey
<
EditableTextState
>
_editableTextKey
=
GlobalKey
<
EditableTextState
>();
TextEditingController
_controller
;
TextEditingController
get
_effectiveController
=>
widget
.
controller
??
_controller
;
FocusNode
_focusNode
;
FocusNode
get
_effectiveFocusNode
=>
widget
.
focusNode
??
(
_focusNode
??=
FocusNode
());
@override
void
initState
()
{
super
.
initState
();
if
(
widget
.
controller
==
null
)
{
_controller
=
TextEditingController
();
_controller
.
addListener
(
updateKeepAlive
);
}
}
@override
void
didUpdateWidget
(
CupertinoTextField
oldWidget
)
{
super
.
didUpdateWidget
(
oldWidget
);
if
(
widget
.
controller
==
null
&&
oldWidget
.
controller
!=
null
)
{
_controller
=
TextEditingController
.
fromValue
(
oldWidget
.
controller
.
value
);
_controller
.
addListener
(
updateKeepAlive
);
}
else
if
(
widget
.
controller
!=
null
&&
oldWidget
.
controller
==
null
)
{
_controller
=
null
;
}
final
bool
isEnabled
=
widget
.
enabled
??
true
;
final
bool
wasEnabled
=
oldWidget
.
enabled
??
true
;
if
(
wasEnabled
&&
!
isEnabled
)
{
_effectiveFocusNode
.
unfocus
();
}
}
@override
void
dispose
()
{
_focusNode
?.
dispose
();
_controller
?.
removeListener
(
updateKeepAlive
);
super
.
dispose
();
}
void
_requestKeyboard
()
{
_editableTextKey
.
currentState
?.
requestKeyboard
();
}
RenderEditable
get
_renderEditable
=>
_editableTextKey
.
currentState
.
renderEditable
;
void
_handleTapDown
(
TapDownDetails
details
)
{
_renderEditable
.
handleTapDown
(
details
);
}
void
_handleTap
()
{
_renderEditable
.
handleTap
();
_requestKeyboard
();
}
void
_handleLongPress
()
{
_renderEditable
.
handleLongPress
();
}
@override
bool
get
wantKeepAlive
=>
_controller
?.
text
?.
isNotEmpty
==
true
;
bool
_shouldShowAttachment
({
OverlayVisibilityMode
attachment
,
bool
hasText
,
})
{
switch
(
attachment
)
{
case
OverlayVisibilityMode
.
never
:
return
false
;
case
OverlayVisibilityMode
.
always
:
return
true
;
case
OverlayVisibilityMode
.
editing
:
return
hasText
;
case
OverlayVisibilityMode
.
notEditing
:
return
!
hasText
;
}
assert
(
false
);
return
null
;
}
bool
_showPrefixWidget
(
TextEditingValue
text
)
{
return
widget
.
prefix
!=
null
&&
_shouldShowAttachment
(
attachment:
widget
.
prefixMode
,
hasText:
text
.
text
.
isNotEmpty
,
);
}
bool
_showSuffixWidget
(
TextEditingValue
text
)
{
return
widget
.
suffix
!=
null
&&
_shouldShowAttachment
(
attachment:
widget
.
suffixMode
,
hasText:
text
.
text
.
isNotEmpty
,
);
}
bool
_showClearButton
(
TextEditingValue
text
)
{
return
_shouldShowAttachment
(
attachment:
widget
.
clearButtonMode
,
hasText:
text
.
text
.
isNotEmpty
,
);
}
Widget
_addTextDependentAttachments
(
Widget
editableText
)
{
// If there are no surrounding widgets, just return the core editable text
// part.
if
(
widget
.
placeholder
==
null
&&
widget
.
clearButtonMode
==
OverlayVisibilityMode
.
never
&&
widget
.
prefix
==
null
&&
widget
.
suffix
==
null
)
{
return
editableText
;
}
// Otherwise, listen to the current state of the text entry.
return
ValueListenableBuilder
<
TextEditingValue
>(
valueListenable:
_effectiveController
,
child:
editableText
,
builder:
(
BuildContext
context
,
TextEditingValue
text
,
Widget
child
)
{
final
List
<
Widget
>
rowChildren
=
<
Widget
>[];
// Insert a prefix at the front if the prefix visibility mode matches
// the current text state.
if
(
_showPrefixWidget
(
text
))
{
rowChildren
.
add
(
widget
.
prefix
);
}
final
List
<
Widget
>
stackChildren
=
<
Widget
>[];
// In the middle part, stack the placeholder on top of the main EditableText
// if needed.
if
(
widget
.
placeholder
!=
null
&&
text
.
text
.
isEmpty
)
{
stackChildren
.
add
(
Padding
(
padding:
widget
.
padding
,
child:
Text
(
widget
.
placeholder
,
maxLines:
1
,
overflow:
TextOverflow
.
ellipsis
,
style:
widget
.
style
.
merge
(
const
TextStyle
(
color:
_kInactiveTextColor
,
fontWeight:
FontWeight
.
w300
,
),
),
),
),
);
}
rowChildren
.
add
(
Expanded
(
child:
Stack
(
children:
stackChildren
..
add
(
child
))));
// First add the explicit suffix if the suffix visibility mode matches.
if
(
_showSuffixWidget
(
text
))
{
rowChildren
.
add
(
widget
.
suffix
);
// Otherwise, try to show a clear button if its visibility mode matches.
}
else
if
(
_showClearButton
(
text
))
{
rowChildren
.
add
(
GestureDetector
(
onTap:
widget
.
enabled
??
true
?
()
=>
_effectiveController
.
clear
()
:
null
,
child:
const
Padding
(
padding:
EdgeInsets
.
symmetric
(
horizontal:
6.0
),
child:
Icon
(
CupertinoIcons
.
clear_thick_circled
,
size:
18.0
,
color:
_kInactiveTextColor
,
),
),
),
);
}
return
Row
(
children:
rowChildren
);
},
);
}
@override
Widget
build
(
BuildContext
context
)
{
super
.
build
(
context
);
// See AutomaticKeepAliveClientMixin.
assert
(
debugCheckHasDirectionality
(
context
));
final
Brightness
keyboardAppearance
=
widget
.
keyboardAppearance
;
final
TextEditingController
controller
=
_effectiveController
;
final
FocusNode
focusNode
=
_effectiveFocusNode
;
final
List
<
TextInputFormatter
>
formatters
=
widget
.
inputFormatters
??
<
TextInputFormatter
>[];
final
bool
enabled
=
widget
.
enabled
??
true
;
if
(
widget
.
maxLength
!=
null
&&
widget
.
maxLengthEnforced
)
{
formatters
.
add
(
LengthLimitingTextInputFormatter
(
widget
.
maxLength
));
}
final
Widget
paddedEditable
=
Padding
(
padding:
widget
.
padding
,
child:
RepaintBoundary
(
child:
EditableText
(
key:
_editableTextKey
,
controller:
controller
,
focusNode:
focusNode
,
keyboardType:
widget
.
keyboardType
,
textInputAction:
widget
.
textInputAction
,
textCapitalization:
widget
.
textCapitalization
,
style:
widget
.
style
,
textAlign:
widget
.
textAlign
,
autofocus:
widget
.
autofocus
,
obscureText:
widget
.
obscureText
,
autocorrect:
widget
.
autocorrect
,
maxLines:
widget
.
maxLines
,
selectionColor:
_kSelectionHighlightColor
,
selectionControls:
cupertinoTextSelectionControls
,
onChanged:
widget
.
onChanged
,
onEditingComplete:
widget
.
onEditingComplete
,
onSubmitted:
widget
.
onSubmitted
,
inputFormatters:
formatters
,
rendererIgnoresPointer:
true
,
cursorWidth:
widget
.
cursorWidth
,
cursorRadius:
widget
.
cursorRadius
,
cursorColor:
widget
.
cursorColor
,
scrollPadding:
widget
.
scrollPadding
,
keyboardAppearance:
keyboardAppearance
,
),
),
);
return
Semantics
(
onTap:
()
{
if
(!
controller
.
selection
.
isValid
)
{
controller
.
selection
=
TextSelection
.
collapsed
(
offset:
controller
.
text
.
length
);
}
_requestKeyboard
();
},
child:
IgnorePointer
(
ignoring:
!
enabled
,
child:
Container
(
decoration:
widget
.
decoration
,
// The main decoration and the disabled scrim exists separately.
child:
Container
(
color:
enabled
?
null
:
_kDisabledBackground
,
child:
GestureDetector
(
behavior:
HitTestBehavior
.
translucent
,
onTapDown:
_handleTapDown
,
onTap:
_handleTap
,
onLongPress:
_handleLongPress
,
excludeFromSemantics:
true
,
child:
_addTextDependentAttachments
(
paddedEditable
),
),
),
),
),
);
}
}
packages/flutter/lib/src/cupertino/text_selection.dart
View file @
b63ced55
...
...
@@ -20,14 +20,16 @@ const double _kToolbarHeight = 36.0;
const
Color
_kToolbarBackgroundColor
=
Color
(
0xFF2E2E2E
);
const
Color
_kToolbarDividerColor
=
Color
(
0xFFB9B9B9
);
const
Color
_kHandlesColor
=
Color
(
0xFF146DDE
);
// Read off from the output on iOS 12. This color does not vary with the
// application's theme color.
const
Color
_kHandlesColor
=
Color
(
0xFF136FE0
);
// This offset is used to determine the center of the selection during a drag.
// It's slightly below the center of the text so the finger isn't entirely
// covering the text being selected.
const
Size
_kSelectionOffset
=
Size
(
20.0
,
30.0
);
const
Size
_kToolbarTriangleSize
=
Size
(
18.0
,
9.0
);
const
EdgeInsets
_kToolbarButtonPadding
=
EdgeInsets
.
symmetric
(
vertical:
10.0
,
horizontal:
21
.0
);
const
EdgeInsets
_kToolbarButtonPadding
=
EdgeInsets
.
symmetric
(
vertical:
10.0
,
horizontal:
18
.0
);
const
BorderRadius
_kToolbarBorderRadius
=
BorderRadius
.
all
(
Radius
.
circular
(
7.5
));
const
TextStyle
_kToolbarButtonFontStyle
=
TextStyle
(
...
...
@@ -121,6 +123,7 @@ class _TextSelectionToolbar extends StatelessWidget {
// avoid letting the triangle line up with any dividers.
// https://github.com/flutter/flutter/issues/11274
triangle
,
const
Padding
(
padding:
EdgeInsets
.
only
(
bottom:
10.0
)),
],
);
}
...
...
packages/flutter/lib/src/material/text_field.dart
View file @
b63ced55
...
...
@@ -154,10 +154,7 @@ class TextField extends StatefulWidget {
/// extra padding introduced by the decoration to save space for the labels).
final
InputDecoration
decoration
;
/// The type of keyboard to use for editing the text.
///
/// Defaults to [TextInputType.text] if [maxLines] is one and
/// [TextInputType.multiline] otherwise.
/// {@macro flutter.widgets.editableText.keyboardType}
final
TextInputType
keyboardType
;
/// The type of action button to use for the keyboard.
...
...
@@ -166,17 +163,7 @@ class TextField extends StatefulWidget {
/// [TextInputType.multiline] and [TextInputAction.done] otherwise.
final
TextInputAction
textInputAction
;
/// Configures how the platform keyboard will select an uppercase or
/// lowercase keyboard.
///
/// Only supports text keyboards, other keyboard types will ignore this
/// configuration. Capitalization is locale-aware.
///
/// Defaults to [TextCapitalization.none]. Must not be null.
///
/// See also:
///
/// * [TextCapitalization], for a description of each capitalization behavior.
/// {@macro flutter.widgets.editableText.textCapitalization}
final
TextCapitalization
textCapitalization
;
/// The style to use for the text being edited.
...
...
@@ -186,42 +173,19 @@ class TextField extends StatefulWidget {
/// If null, defaults to the `subhead` text style from the current [Theme].
final
TextStyle
style
;
/// How the text being edited should be aligned horizontally.
///
/// Defaults to [TextAlign.start].
/// {@macro flutter.widgets.editableText.textAlign}
final
TextAlign
textAlign
;
/// Whether this text field should focus itself if nothing else is already
/// focused.
///
/// If true, the keyboard will open as soon as this text field obtains focus.
/// Otherwise, the keyboard is only shown after the user taps the text field.
///
/// Defaults to false. Cannot be null.
// See https://github.com/flutter/flutter/issues/7035 for the rationale for this
// keyboard behavior.
/// {@macro flutter.widgets.editableText.autofocus}
final
bool
autofocus
;
/// Whether to hide the text being edited (e.g., for passwords).
///
/// When this is set to true, all the characters in the text field are
/// replaced by U+2022 BULLET characters (•).
///
/// Defaults to false. Cannot be null.
/// {@macro flutter.widgets.editableText.obscureText}
final
bool
obscureText
;
/// Whether to enable autocorrection.
///
/// Defaults to true. Cannot be null.
/// {@macro flutter.widgets.editableText.autocorrect}
final
bool
autocorrect
;
/// The maximum number of lines for the text to span, wrapping if necessary.
///
/// If this is 1 (the default), the text will not wrap, but will scroll
/// horizontally instead.
///
/// If this is null, there is no limit to the number of lines. If it is not
/// null, the value must be greater than zero.
/// {@macro flutter.widgets.editableText.maxLines}
final
int
maxLines
;
/// The maximum number of characters (Unicode scalar values) to allow in the
...
...
@@ -280,34 +244,16 @@ class TextField extends StatefulWidget {
/// [maxLength] is exceeded.
final
bool
maxLengthEnforced
;
///
Called when the text being edited changes.
///
{@macro flutter.widgets.editableText.onChanged}
final
ValueChanged
<
String
>
onChanged
;
/// Called when the user submits editable content (e.g., user presses the "done"
/// button on the keyboard).
///
/// The default implementation of [onEditingComplete] executes 2 different
/// behaviors based on the situation:
///
/// - When a completion action is pressed, such as "done", "go", "send", or
/// "search", the user's content is submitted to the [controller] and then
/// focus is given up.
///
/// - When a non-completion action is pressed, such as "next" or "previous",
/// the user's content is submitted to the [controller], but focus is not
/// given up because developers may want to immediately move focus to
/// another input widget within [onSubmitted].
///
/// Providing [onEditingComplete] prevents the aforementioned default behavior.
/// {@macro flutter.widgets.editableText.onEditingComplete}
final
VoidCallback
onEditingComplete
;
/// Called when the user indicates that they are done editing the text in the
/// field.
/// {@macro flutter.widgets.editableText.onSubmitted}
final
ValueChanged
<
String
>
onSubmitted
;
/// Optional input validation and formatting overrides.
///
/// Formatters are run in the provided order when the text input changes.
/// {@macro flutter.widgets.editableText.inputFormatters}
final
List
<
TextInputFormatter
>
inputFormatters
;
/// If false the textfield is "disabled": it ignores taps and its
...
...
@@ -317,16 +263,15 @@ class TextField extends StatefulWidget {
/// [Decoration.enabled] property.
final
bool
enabled
;
/// How thick the cursor will be.
///
/// Defaults to 2.0.
/// {@macro flutter.widgets.editableText.cursorWidth}
final
double
cursorWidth
;
/// How rounded the corners of the cursor should be.
/// By default, the cursor has a null Radius
/// {@macro flutter.widgets.editableText.cursorRadius}
final
Radius
cursorRadius
;
/// The color to use when painting the cursor.
///
/// Defaults to the theme's `cursorColor` when null.
final
Color
cursorColor
;
/// The appearance of the keyboard.
...
...
@@ -336,14 +281,7 @@ class TextField extends StatefulWidget {
/// If unset, defaults to the brightness of [ThemeData.primaryColorBrightness].
final
Brightness
keyboardAppearance
;
/// Configures padding to edges surrounding a [Scrollable] when the Textfield scrolls into view.
///
/// When this widget receives focus and is not completely visible (for example scrolled partially
/// off the screen or overlapped by the keyboard)
/// then it will attempt to make itself visible by scrolling a surrounding [Scrollable], if one is present.
/// This value controls how far from the edges of a [Scrollable] the TextField will be positioned after the scroll.
///
/// Defaults to EdgeInserts.all(20.0).
/// {@macro flutter.widgets.editableText.scrollPadding}
final
EdgeInsets
scrollPadding
;
/// {@macro flutter.widgets.editableText.enableInteractiveSelection}
...
...
packages/flutter/lib/src/widgets/editable_text.dart
View file @
b63ced55
...
...
@@ -242,22 +242,31 @@ class EditableText extends StatefulWidget {
/// Controls whether this widget has keyboard focus.
final
FocusNode
focusNode
;
/// {@template flutter.widgets.editableText.obscureText}
/// Whether to hide the text being edited (e.g., for passwords).
///
/// Defaults to false.
/// When this is set to true, all the characters in the text field are
/// replaced by U+2022 BULLET characters (•).
///
/// Defaults to false. Cannot be null.
/// {@endtemplate}
final
bool
obscureText
;
/// {@template flutter.widgets.editableText.autocorrect}
/// Whether to enable autocorrection.
///
/// Defaults to true.
/// Defaults to true. Cannot be null.
/// {@endtemplate}
final
bool
autocorrect
;
/// The text style to use for the editable text.
final
TextStyle
style
;
/// {@template flutter.widgets.editableText.textAlign}
/// How the text should be aligned horizontally.
///
/// Defaults to [TextAlign.start].
/// Defaults to [TextAlign.start] and cannot be null.
/// {@endtemplate}
final
TextAlign
textAlign
;
/// The directionality of the text.
...
...
@@ -275,6 +284,7 @@ class EditableText extends StatefulWidget {
/// Defaults to the ambient [Directionality], if any.
final
TextDirection
textDirection
;
/// {@template flutter.widgets.editableText.textCapitalization}
/// Configures how the platform keyboard will select an uppercase or
/// lowercase keyboard.
///
...
...
@@ -286,6 +296,7 @@ class EditableText extends StatefulWidget {
/// See also:
///
/// * [TextCapitalization], for a description of each capitalization behavior.
/// {@endtemplate}
final
TextCapitalization
textCapitalization
;
/// Used to select a font when the same Unicode character can
...
...
@@ -307,8 +318,11 @@ class EditableText extends StatefulWidget {
final
double
textScaleFactor
;
/// The color to use when painting the cursor.
///
/// Cannot be null.
final
Color
cursorColor
;
/// {@template flutter.widgets.editableText.maxLines}
/// The maximum number of lines for the text to span, wrapping if necessary.
///
/// If this is 1 (the default), the text will not wrap, but will scroll
...
...
@@ -316,13 +330,20 @@ class EditableText extends StatefulWidget {
///
/// If this is null, there is no limit to the number of lines. If it is not
/// null, the value must be greater than zero.
/// {@endtemplate}
final
int
maxLines
;
/// Whether this input field should focus itself if nothing else is already focused.
/// If true, the keyboard will open as soon as this input obtains focus. Otherwise,
/// the keyboard is only shown after the user taps the text field.
/// {@template flutter.widgets.editableText.autofocus}
/// Whether this text field should focus itself if nothing else is already
/// focused.
///
/// If true, the keyboard will open as soon as this text field obtains focus.
/// Otherwise, the keyboard is only shown after the user taps the text field.
///
/// Defaults to false.
/// Defaults to false. Cannot be null.
/// {@endtemplate}
// See https://github.com/flutter/flutter/issues/7035 for the rationale for this
// keyboard behavior.
final
bool
autofocus
;
/// The color to use when painting the selection.
...
...
@@ -331,15 +352,23 @@ class EditableText extends StatefulWidget {
/// Optional delegate for building the text selection handles and toolbar.
final
TextSelectionControls
selectionControls
;
/// {@template flutter.widgets.editableText.keyboardType}
/// The type of keyboard to use for editing the text.
///
/// Defaults to [TextInputType.text] if [maxLines] is one and
/// [TextInputType.multiline] otherwise.
/// {@endtemplate}
final
TextInputType
keyboardType
;
/// The type of action button to use with the soft keyboard.
final
TextInputAction
textInputAction
;
/// {@template flutter.widgets.editableText.onChanged}
/// Called when the text being edited changes.
/// {@endtemplate}
final
ValueChanged
<
String
>
onChanged
;
/// {@template flutter.widgets.editableText.onEditingComplete}
/// Called when the user submits editable content (e.g., user presses the "done"
/// button on the keyboard).
///
...
...
@@ -356,17 +385,24 @@ class EditableText extends StatefulWidget {
/// another input widget within [onSubmitted].
///
/// Providing [onEditingComplete] prevents the aforementioned default behavior.
/// {@endtemplate}
final
VoidCallback
onEditingComplete
;
/// Called when the user indicates that they are done editing the text in the field.
/// {@template flutter.widgets.editableText.onSubmitted}
/// Called when the user indicates that they are done editing the text in the
/// field.
/// {@endtemplate}
final
ValueChanged
<
String
>
onSubmitted
;
/// Called when the user changes the selection of text (including the cursor
/// location).
final
SelectionChangedCallback
onSelectionChanged
;
/// Optional input validation and formatting overrides. Formatters are run
/// in the provided order when the text input changes.
/// {@template flutter.widgets.editableText.inputFormatters}
/// Optional input validation and formatting overrides.
///
/// Formatters are run in the provided order when the text input changes.
/// {@endtemplate}
final
List
<
TextInputFormatter
>
inputFormatters
;
/// If true, the [RenderEditable] created by this widget will not handle
...
...
@@ -375,14 +411,18 @@ class EditableText extends StatefulWidget {
/// This property is false by default.
final
bool
rendererIgnoresPointer
;
/// {@template flutter.widgets.editableText.cursorWidth}
/// How thick the cursor will be.
///
/// Defaults to 2.0
/// {@endtemplate}
final
double
cursorWidth
;
/// {@template flutter.widgets.editableText.cursorRadius}
/// How rounded the corners of the cursor should be.
///
/// By default, the cursor has a Radius of zero.
/// By default, the cursor has no radius.
/// {@endtemplate}
final
Radius
cursorRadius
;
/// The appearance of the keyboard.
...
...
@@ -392,6 +432,7 @@ class EditableText extends StatefulWidget {
/// Defaults to [Brightness.light].
final
Brightness
keyboardAppearance
;
/// {@template flutter.widgets.editableText.scrollPadding}
/// Configures padding to edges surrounding a [Scrollable] when the Textfield scrolls into view.
///
/// When this widget receives focus and is not completely visible (for example scrolled partially
...
...
@@ -400,6 +441,7 @@ class EditableText extends StatefulWidget {
/// This value controls how far from the edges of a [Scrollable] the TextField will be positioned after the scroll.
///
/// Defaults to EdgeInserts.all(20.0).
/// {@endtemplate}
final
EdgeInsets
scrollPadding
;
/// {@template flutter.widgets.editableText.enableInteractiveSelection}
...
...
packages/flutter/lib/src/widgets/text_selection.dart
View file @
b63ced55
...
...
@@ -531,6 +531,9 @@ class _TextSelectionHandleOverlayState extends State<_TextSelectionHandleOverlay
onPanUpdate:
_handleDragUpdate
,
onTap:
_handleTap
,
child:
Stack
(
// Always let the selection handles draw outside of the conceptual
// box where (0,0) is the top left corner of the RenderEditable.
overflow:
Overflow
.
visible
,
children:
<
Widget
>[
Positioned
(
left:
point
.
dx
,
...
...
packages/flutter/test/cupertino/text_field_test.dart
0 → 100644
View file @
b63ced55
// Copyright 2018 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:async'
;
import
'package:flutter/cupertino.dart'
;
import
'package:flutter/rendering.dart'
;
import
'package:flutter/services.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
class
MockClipboard
{
Object
_clipboardData
=
<
String
,
dynamic
>{
'text'
:
null
,
};
Future
<
dynamic
>
handleMethodCall
(
MethodCall
methodCall
)
async
{
switch
(
methodCall
.
method
)
{
case
'Clipboard.getData'
:
return
_clipboardData
;
case
'Clipboard.setData'
:
_clipboardData
=
methodCall
.
arguments
;
break
;
}
}
}
void
main
(
)
{
final
MockClipboard
mockClipboard
=
MockClipboard
();
SystemChannels
.
platform
.
setMockMethodCallHandler
(
mockClipboard
.
handleMethodCall
);
testWidgets
(
'takes available space horizontally and takes intrinsic space vertically'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
CupertinoApp
(
home:
Center
(
child:
ConstrainedBox
(
constraints:
BoxConstraints
.
loose
(
const
Size
(
200
,
200
)),
child:
const
CupertinoTextField
(),
),
),
),
);
expect
(
tester
.
getSize
(
find
.
byType
(
CupertinoTextField
)),
const
Size
(
200
,
29
),
// 29 is the height of the default font + padding etc.
);
},
);
testWidgets
(
'multi-lined text fields are intrinsically taller'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
CupertinoApp
(
home:
Center
(
child:
ConstrainedBox
(
constraints:
BoxConstraints
.
loose
(
const
Size
(
200
,
200
)),
child:
const
CupertinoTextField
(
maxLines:
3
),
),
),
),
);
expect
(
tester
.
getSize
(
find
.
byType
(
CupertinoTextField
)),
const
Size
(
200
,
63
),
);
},
);
testWidgets
(
'default text field has a border'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
const
CupertinoApp
(
home:
Center
(
child:
CupertinoTextField
(),
),
),
);
final
BoxDecoration
decoration
=
tester
.
widget
<
DecoratedBox
>(
find
.
descendant
(
of:
find
.
byType
(
CupertinoTextField
),
matching:
find
.
byType
(
DecoratedBox
)
),
).
decoration
;
expect
(
decoration
.
borderRadius
,
BorderRadius
.
circular
(
4.0
),
);
expect
(
decoration
.
border
.
bottom
.
color
,
CupertinoColors
.
lightBackgroundGray
,
);
},
);
testWidgets
(
'decoration can be overrriden'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
const
CupertinoApp
(
home:
Center
(
child:
CupertinoTextField
(
decoration:
null
,
),
),
),
);
expect
(
find
.
descendant
(
of:
find
.
byType
(
CupertinoTextField
),
matching:
find
.
byType
(
DecoratedBox
)
),
findsNothing
,
);
},
);
testWidgets
(
'text entries are padded by default'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
CupertinoApp
(
home:
Center
(
child:
CupertinoTextField
(
controller:
TextEditingController
(
text:
'initial'
),
),
),
),
);
expect
(
tester
.
getTopLeft
(
find
.
text
(
'initial'
))
-
tester
.
getTopLeft
(
find
.
byType
(
CupertinoTextField
)),
const
Offset
(
6.0
,
6.0
),
);
},
);
testWidgets
(
'can control text content via controller'
,
(
WidgetTester
tester
)
async
{
final
TextEditingController
controller
=
TextEditingController
();
await
tester
.
pumpWidget
(
CupertinoApp
(
home:
Center
(
child:
CupertinoTextField
(
controller:
controller
,
),
),
),
);
controller
.
text
=
'controller text'
;
await
tester
.
pump
();
expect
(
find
.
text
(
'controller text'
),
findsOneWidget
);
controller
.
text
=
''
;
await
tester
.
pump
();
expect
(
find
.
text
(
'controller text'
),
findsNothing
);
},
);
testWidgets
(
'placeholders are lightly colored and disappears once typing starts'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
const
CupertinoApp
(
home:
Center
(
child:
CupertinoTextField
(
placeholder:
'placeholder'
,
),
),
),
);
final
Text
placeholder
=
tester
.
widget
(
find
.
text
(
'placeholder'
));
expect
(
placeholder
.
style
.
color
,
const
Color
(
0xFFC2C2C2
));
await
tester
.
enterText
(
find
.
byType
(
CupertinoTextField
),
'input'
);
await
tester
.
pump
();
expect
(
find
.
text
(
'placeholder'
),
findsNothing
);
},
);
testWidgets
(
'prefix widget is in front of the text'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
CupertinoApp
(
home:
Center
(
child:
CupertinoTextField
(
prefix:
const
Icon
(
CupertinoIcons
.
add
),
controller:
TextEditingController
(
text:
'input'
),
),
),
),
);
expect
(
tester
.
getTopRight
(
find
.
byIcon
(
CupertinoIcons
.
add
)).
dx
+
6.0
,
// 6px standard padding around input.
tester
.
getTopLeft
(
find
.
byType
(
EditableText
)).
dx
,
);
expect
(
tester
.
getTopLeft
(
find
.
byType
(
EditableText
)).
dx
,
tester
.
getTopLeft
(
find
.
byType
(
CupertinoTextField
)).
dx
+
tester
.
getSize
(
find
.
byIcon
(
CupertinoIcons
.
add
)).
width
+
6.0
,
);
},
);
testWidgets
(
'prefix widget respects visibility mode'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
const
CupertinoApp
(
home:
Center
(
child:
CupertinoTextField
(
prefix:
Icon
(
CupertinoIcons
.
add
),
prefixMode:
OverlayVisibilityMode
.
editing
,
),
),
),
);
expect
(
find
.
byIcon
(
CupertinoIcons
.
add
),
findsNothing
);
// The position should just be the edge of the whole text field plus padding.
expect
(
tester
.
getTopLeft
(
find
.
byType
(
EditableText
)).
dx
,
tester
.
getTopLeft
(
find
.
byType
(
CupertinoTextField
)).
dx
+
6.0
,
);
await
tester
.
enterText
(
find
.
byType
(
CupertinoTextField
),
'text input'
);
await
tester
.
pump
();
expect
(
find
.
text
(
'text input'
),
findsOneWidget
);
expect
(
find
.
byIcon
(
CupertinoIcons
.
add
),
findsOneWidget
);
// Text is now moved to the right.
expect
(
tester
.
getTopLeft
(
find
.
byType
(
EditableText
)).
dx
,
tester
.
getTopLeft
(
find
.
byType
(
CupertinoTextField
)).
dx
+
tester
.
getSize
(
find
.
byIcon
(
CupertinoIcons
.
add
)).
width
+
6.0
,
);
},
);
testWidgets
(
'suffix widget is after the text'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
const
CupertinoApp
(
home:
Center
(
child:
CupertinoTextField
(
suffix:
Icon
(
CupertinoIcons
.
add
),
),
),
),
);
expect
(
tester
.
getTopRight
(
find
.
byType
(
EditableText
)).
dx
+
6.0
,
tester
.
getTopLeft
(
find
.
byIcon
(
CupertinoIcons
.
add
)).
dx
,
// 6px standard padding around input.
);
expect
(
tester
.
getTopRight
(
find
.
byType
(
EditableText
)).
dx
,
tester
.
getTopRight
(
find
.
byType
(
CupertinoTextField
)).
dx
-
tester
.
getSize
(
find
.
byIcon
(
CupertinoIcons
.
add
)).
width
-
6.0
,
);
},
);
testWidgets
(
'suffix widget respects visibility mode'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
const
CupertinoApp
(
home:
Center
(
child:
CupertinoTextField
(
suffix:
Icon
(
CupertinoIcons
.
add
),
suffixMode:
OverlayVisibilityMode
.
notEditing
,
),
),
),
);
expect
(
find
.
byIcon
(
CupertinoIcons
.
add
),
findsOneWidget
);
await
tester
.
enterText
(
find
.
byType
(
CupertinoTextField
),
'text input'
);
await
tester
.
pump
();
expect
(
find
.
text
(
'text input'
),
findsOneWidget
);
expect
(
find
.
byIcon
(
CupertinoIcons
.
add
),
findsNothing
);
},
);
testWidgets
(
'can customize padding'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
const
CupertinoApp
(
home:
Center
(
child:
CupertinoTextField
(
padding:
EdgeInsets
.
zero
,
),
),
),
);
expect
(
tester
.
getSize
(
find
.
byType
(
EditableText
)),
tester
.
getSize
(
find
.
byType
(
CupertinoTextField
)),
);
},
);
testWidgets
(
'padding is in between prefix and suffix'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
const
CupertinoApp
(
home:
Center
(
child:
CupertinoTextField
(
padding:
EdgeInsets
.
all
(
20.0
),
prefix:
SizedBox
(
height:
100.0
,
width:
100.0
),
suffix:
SizedBox
(
height:
50.0
,
width:
50.0
),
),
),
),
);
expect
(
tester
.
getTopLeft
(
find
.
byType
(
EditableText
)).
dx
,
// Size of prefix + padding.
100.0
+
20.0
,
);
expect
(
tester
.
getTopLeft
(
find
.
byType
(
EditableText
)).
dy
,
291.5
);
expect
(
tester
.
getTopRight
(
find
.
byType
(
EditableText
)).
dx
,
800.0
-
50.0
-
20.0
,
);
await
tester
.
pumpWidget
(
const
CupertinoApp
(
home:
Center
(
child:
CupertinoTextField
(
padding:
EdgeInsets
.
all
(
30.0
),
prefix:
SizedBox
(
height:
100.0
,
width:
100.0
),
suffix:
SizedBox
(
height:
50.0
,
width:
50.0
),
),
),
),
);
expect
(
tester
.
getTopLeft
(
find
.
byType
(
EditableText
)).
dx
,
100.0
+
30.0
,
);
// Since the highest component, the prefix box, is higher than
// the text + paddings, the text's vertical position isn't affected.
expect
(
tester
.
getTopLeft
(
find
.
byType
(
EditableText
)).
dy
,
291.5
);
expect
(
tester
.
getTopRight
(
find
.
byType
(
EditableText
)).
dx
,
800.0
-
50.0
-
30.0
,
);
},
);
testWidgets
(
'clear button shows with right visibility mode'
,
(
WidgetTester
tester
)
async
{
final
TextEditingController
controller
=
TextEditingController
();
await
tester
.
pumpWidget
(
CupertinoApp
(
home:
Center
(
child:
CupertinoTextField
(
controller:
controller
,
placeholder:
'placeholder does not affect clear button'
,
clearButtonMode:
OverlayVisibilityMode
.
always
,
),
),
),
);
expect
(
find
.
byIcon
(
CupertinoIcons
.
clear_thick_circled
),
findsOneWidget
);
expect
(
tester
.
getTopRight
(
find
.
byType
(
EditableText
)).
dx
,
800.0
-
30.0
/* size of button */
-
6.0
/* padding */
,
);
await
tester
.
pumpWidget
(
CupertinoApp
(
home:
Center
(
child:
CupertinoTextField
(
controller:
controller
,
placeholder:
'placeholder does not affect clear button'
,
clearButtonMode:
OverlayVisibilityMode
.
editing
,
),
),
),
);
expect
(
find
.
byIcon
(
CupertinoIcons
.
clear_thick_circled
),
findsNothing
);
expect
(
tester
.
getTopRight
(
find
.
byType
(
EditableText
)).
dx
,
800.0
-
6.0
/* padding */
,
);
await
tester
.
enterText
(
find
.
byType
(
CupertinoTextField
),
'text input'
);
await
tester
.
pump
();
expect
(
find
.
byIcon
(
CupertinoIcons
.
clear_thick_circled
),
findsOneWidget
);
expect
(
find
.
text
(
'text input'
),
findsOneWidget
);
expect
(
tester
.
getTopRight
(
find
.
byType
(
EditableText
)).
dx
,
800.0
-
30.0
-
6.0
,
);
await
tester
.
pumpWidget
(
CupertinoApp
(
home:
Center
(
child:
CupertinoTextField
(
controller:
controller
,
placeholder:
'placeholder does not affect clear button'
,
clearButtonMode:
OverlayVisibilityMode
.
notEditing
,
),
),
),
);
expect
(
find
.
byIcon
(
CupertinoIcons
.
clear_thick_circled
),
findsNothing
);
controller
.
text
=
''
;
await
tester
.
pump
();
expect
(
find
.
byIcon
(
CupertinoIcons
.
clear_thick_circled
),
findsOneWidget
);
},
);
testWidgets
(
'clear button removes text'
,
(
WidgetTester
tester
)
async
{
final
TextEditingController
controller
=
TextEditingController
();
await
tester
.
pumpWidget
(
CupertinoApp
(
home:
Center
(
child:
CupertinoTextField
(
controller:
controller
,
placeholder:
'placeholder'
,
clearButtonMode:
OverlayVisibilityMode
.
editing
,
),
),
),
);
controller
.
text
=
'text entry'
;
await
tester
.
pump
();
await
tester
.
tap
(
find
.
byIcon
(
CupertinoIcons
.
clear_thick_circled
));
await
tester
.
pump
();
expect
(
controller
.
text
,
''
);
expect
(
find
.
text
(
'placeholder'
),
findsOneWidget
);
expect
(
find
.
text
(
'text entry'
),
findsNothing
);
expect
(
find
.
byIcon
(
CupertinoIcons
.
clear_thick_circled
),
findsNothing
);
},
);
testWidgets
(
'clear button yields precedence to suffix'
,
(
WidgetTester
tester
)
async
{
final
TextEditingController
controller
=
TextEditingController
();
await
tester
.
pumpWidget
(
CupertinoApp
(
home:
Center
(
child:
CupertinoTextField
(
controller:
controller
,
clearButtonMode:
OverlayVisibilityMode
.
always
,
suffix:
const
Icon
(
CupertinoIcons
.
add_circled_solid
),
suffixMode:
OverlayVisibilityMode
.
editing
,
),
),
),
);
expect
(
find
.
byIcon
(
CupertinoIcons
.
clear_thick_circled
),
findsOneWidget
);
expect
(
find
.
byIcon
(
CupertinoIcons
.
add_circled_solid
),
findsNothing
);
expect
(
tester
.
getTopRight
(
find
.
byType
(
EditableText
)).
dx
,
800.0
-
30.0
/* size of button */
-
6.0
/* padding */
,
);
controller
.
text
=
'non empty text'
;
await
tester
.
pump
();
expect
(
find
.
byIcon
(
CupertinoIcons
.
clear_thick_circled
),
findsNothing
);
expect
(
find
.
byIcon
(
CupertinoIcons
.
add_circled_solid
),
findsOneWidget
);
// Still just takes the space of one widget.
expect
(
tester
.
getTopRight
(
find
.
byType
(
EditableText
)).
dx
,
800.0
-
24.0
/* size of button */
-
6.0
/* padding */
,
);
},
);
testWidgets
(
'font style controls intrinsic height'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
const
CupertinoApp
(
home:
Center
(
child:
CupertinoTextField
(),
),
),
);
expect
(
tester
.
getSize
(
find
.
byType
(
CupertinoTextField
)).
height
,
29.0
,
);
await
tester
.
pumpWidget
(
const
CupertinoApp
(
home:
Center
(
child:
CupertinoTextField
(
style:
TextStyle
(
// A larger font.
fontSize:
50.0
,
),
),
),
),
);
expect
(
tester
.
getSize
(
find
.
byType
(
CupertinoTextField
)).
height
,
62.0
,
);
},
);
testWidgets
(
'RTL puts attachments to the right places'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
const
CupertinoApp
(
home:
Directionality
(
textDirection:
TextDirection
.
rtl
,
child:
Center
(
child:
CupertinoTextField
(
padding:
EdgeInsets
.
all
(
20.0
),
prefix:
Icon
(
CupertinoIcons
.
book
),
clearButtonMode:
OverlayVisibilityMode
.
always
,
),
),
),
),
);
expect
(
tester
.
getTopLeft
(
find
.
byIcon
(
CupertinoIcons
.
book
)).
dx
,
800.0
-
24.0
,
);
expect
(
tester
.
getTopRight
(
find
.
byIcon
(
CupertinoIcons
.
clear_thick_circled
)).
dx
,
24.0
,
);
},
);
testWidgets
(
'text fields with no max lines can grow'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
const
CupertinoApp
(
home:
Center
(
child:
CupertinoTextField
(
maxLines:
null
,
),
),
),
);
expect
(
tester
.
getSize
(
find
.
byType
(
CupertinoTextField
)).
height
,
29.0
,
// Initially one line high.
);
await
tester
.
enterText
(
find
.
byType
(
CupertinoTextField
),
'
\n
'
);
await
tester
.
pump
();
expect
(
tester
.
getSize
(
find
.
byType
(
CupertinoTextField
)).
height
,
46.0
,
// Initially one line high.
);
},
);
testWidgets
(
'cannot enter new lines onto single line TextField'
,
(
WidgetTester
tester
)
async
{
final
TextEditingController
controller
=
TextEditingController
();
await
tester
.
pumpWidget
(
CupertinoApp
(
home:
Center
(
child:
CupertinoTextField
(
controller:
controller
,
),
),
),
);
await
tester
.
enterText
(
find
.
byType
(
CupertinoTextField
),
'abc
\n
def'
);
expect
(
controller
.
text
,
'abcdef'
);
});
testWidgets
(
'copy paste'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
CupertinoApp
(
home:
Column
(
children:
const
<
Widget
>[
CupertinoTextField
(
placeholder:
'field 1'
,
),
CupertinoTextField
(
placeholder:
'field 2'
,
),
],
),
),
);
await
tester
.
enterText
(
find
.
widgetWithText
(
CupertinoTextField
,
'field 1'
),
"j'aime la poutine"
);
await
tester
.
pump
();
// Tap an area inside the EditableText but with no text.
await
tester
.
longPressAt
(
tester
.
getTopRight
(
find
.
text
(
"j'aime la poutine"
))
);
await
tester
.
pump
();
await
tester
.
pump
(
const
Duration
(
milliseconds:
200
));
await
tester
.
tap
(
find
.
text
(
'Select All'
));
await
tester
.
pump
();
await
tester
.
tap
(
find
.
text
(
'Cut'
));
await
tester
.
pump
();
// Placeholder 1 is back since the text is cut.
expect
(
find
.
text
(
'field 1'
),
findsOneWidget
);
expect
(
find
.
text
(
'field 2'
),
findsOneWidget
);
await
tester
.
longPress
(
find
.
text
(
'field 2'
));
await
tester
.
pump
();
await
tester
.
pump
(
const
Duration
(
milliseconds:
200
));
await
tester
.
tap
(
find
.
text
(
'Paste'
));
await
tester
.
pump
();
expect
(
find
.
text
(
'field 1'
),
findsOneWidget
);
expect
(
find
.
text
(
"j'aime la poutine"
),
findsOneWidget
);
expect
(
find
.
text
(
'field 2'
),
findsNothing
);
});
}
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