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
988bfc16
Unverified
Commit
988bfc16
authored
Nov 16, 2018
by
xster
Committed by
GitHub
Nov 16, 2018
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
iOS tap handling on CupertinoTextField (#24034)
parent
0155ee71
Changes
4
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
527 additions
and
17 deletions
+527
-17
text_field.dart
packages/flutter/lib/src/cupertino/text_field.dart
+52
-5
editable.dart
packages/flutter/lib/src/rendering/editable.dart
+61
-11
editable_text.dart
packages/flutter/lib/src/widgets/editable_text.dart
+1
-1
text_field_test.dart
packages/flutter/test/cupertino/text_field_test.dart
+413
-0
No files found.
packages/flutter/lib/src/cupertino/text_field.dart
View file @
988bfc16
// Copyright 2018 The Chromium Authors. All rights reserved.
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// found in the LICENSE file.
import
'dart:async'
;
import
'package:flutter/gestures.dart'
show
kDoubleTapTimeout
,
kDoubleTapSlop
;
import
'package:flutter/rendering.dart'
;
import
'package:flutter/rendering.dart'
;
import
'package:flutter/services.dart'
;
import
'package:flutter/services.dart'
;
import
'package:flutter/widgets.dart'
;
import
'package:flutter/widgets.dart'
;
...
@@ -418,6 +420,13 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
...
@@ -418,6 +420,13 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
FocusNode
_focusNode
;
FocusNode
_focusNode
;
FocusNode
get
_effectiveFocusNode
=>
widget
.
focusNode
??
(
_focusNode
??=
FocusNode
());
FocusNode
get
_effectiveFocusNode
=>
widget
.
focusNode
??
(
_focusNode
??=
FocusNode
());
// Is shortly after a previous single tap when not null.
Timer
_doubleTapTimer
;
Offset
_lastTapOffset
;
// True if second tap down of a double tap is detected. Used to discard
// subsequent tap up / tap hold of the same tap.
bool
_isDoubleTap
=
false
;
@override
@override
void
initState
()
{
void
initState
()
{
super
.
initState
();
super
.
initState
();
...
@@ -447,6 +456,7 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
...
@@ -447,6 +456,7 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
void
dispose
()
{
void
dispose
()
{
_focusNode
?.
dispose
();
_focusNode
?.
dispose
();
_controller
?.
removeListener
(
updateKeepAlive
);
_controller
?.
removeListener
(
updateKeepAlive
);
_doubleTapTimer
?.
cancel
();
super
.
dispose
();
super
.
dispose
();
}
}
...
@@ -456,17 +466,54 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
...
@@ -456,17 +466,54 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
RenderEditable
get
_renderEditable
=>
_editableTextKey
.
currentState
.
renderEditable
;
RenderEditable
get
_renderEditable
=>
_editableTextKey
.
currentState
.
renderEditable
;
// The down handler is force-run on success of a single tap and optimistically
// run before a long press success.
void
_handleTapDown
(
TapDownDetails
details
)
{
void
_handleTapDown
(
TapDownDetails
details
)
{
_renderEditable
.
handleTapDown
(
details
);
_renderEditable
.
handleTapDown
(
details
);
// This isn't detected as a double tap gesture in the gesture recognizer
// because it's 2 single taps, each of which may do different things depending
// on whether it's a single tap, the first tap of a double tap, the second
// tap held down, a clean double tap etc.
if
(
_doubleTapTimer
!=
null
&&
_isWithinDoubleTapTolerance
(
details
.
globalPosition
))
{
// If there was already a previous tap, the second down hold/tap is a
// double tap.
_renderEditable
.
selectWord
(
cause:
SelectionChangedCause
.
doubleTap
);
_doubleTapTimer
.
cancel
();
_doubleTapTimeout
();
_isDoubleTap
=
true
;
}
}
}
void
_handleTap
()
{
void
_handleTapUp
(
TapUpDetails
details
)
{
_renderEditable
.
handleTap
();
if
(!
_isDoubleTap
)
{
_renderEditable
.
selectWordEdge
(
cause:
SelectionChangedCause
.
tap
);
_lastTapOffset
=
details
.
globalPosition
;
_doubleTapTimer
=
Timer
(
kDoubleTapTimeout
,
_doubleTapTimeout
);
_requestKeyboard
();
_requestKeyboard
();
}
}
_isDoubleTap
=
false
;
}
void
_handleLongPress
()
{
void
_handleLongPress
()
{
_renderEditable
.
handleLongPress
();
if
(!
_isDoubleTap
)
{
_renderEditable
.
selectPosition
(
cause:
SelectionChangedCause
.
longPress
);
}
_isDoubleTap
=
false
;
}
void
_doubleTapTimeout
()
{
_doubleTapTimer
=
null
;
_lastTapOffset
=
null
;
}
bool
_isWithinDoubleTapTolerance
(
Offset
secondTapOffset
)
{
assert
(
secondTapOffset
!=
null
);
if
(
_lastTapOffset
==
null
)
{
return
false
;
}
final
Offset
difference
=
secondTapOffset
-
_lastTapOffset
;
return
difference
.
distance
<=
kDoubleTapSlop
;
}
}
@override
@override
...
@@ -648,7 +695,7 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
...
@@ -648,7 +695,7 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
child:
GestureDetector
(
child:
GestureDetector
(
behavior:
HitTestBehavior
.
translucent
,
behavior:
HitTestBehavior
.
translucent
,
onTapDown:
_handleTapDown
,
onTapDown:
_handleTapDown
,
onTap
:
_handleTa
p
,
onTap
Up:
_handleTapU
p
,
onLongPress:
_handleLongPress
,
onLongPress:
_handleLongPress
,
excludeFromSemantics:
true
,
excludeFromSemantics:
true
,
child:
_addTextDependentAttachments
(
paddedEditable
),
child:
_addTextDependentAttachments
(
paddedEditable
),
...
...
packages/flutter/lib/src/rendering/editable.dart
View file @
988bfc16
...
@@ -32,6 +32,10 @@ enum SelectionChangedCause {
...
@@ -32,6 +32,10 @@ enum SelectionChangedCause {
/// of the cursor) to change.
/// of the cursor) to change.
tap
,
tap
,
/// The user tapped twice in quick succession on the text and that caused
/// the selection (or the location of the cursor) to change.
doubleTap
,
/// The user long-pressed the text and that caused the selection (or the
/// The user long-pressed the text and that caused the selection (or the
/// location of the cursor) to change.
/// location of the cursor) to change.
longPress
,
longPress
,
...
@@ -190,7 +194,7 @@ class RenderEditable extends RenderBox {
...
@@ -190,7 +194,7 @@ class RenderEditable extends RenderBox {
/// If true [handleEvent] does nothing and it's assumed that this
/// If true [handleEvent] does nothing and it's assumed that this
/// renderer will be notified of input gestures via [handleTapDown],
/// renderer will be notified of input gestures via [handleTapDown],
/// [handleTap], and [handleLongPress].
/// [handleTap],
[handleDoubleTap],
and [handleLongPress].
///
///
/// The default value of this property is false.
/// The default value of this property is false.
bool
ignorePointer
;
bool
ignorePointer
;
...
@@ -1081,18 +1085,23 @@ class RenderEditable extends RenderBox {
...
@@ -1081,18 +1085,23 @@ class RenderEditable extends RenderBox {
/// When [ignorePointer] is true, an ancestor widget must respond to tap
/// When [ignorePointer] is true, an ancestor widget must respond to tap
/// events by calling this method.
/// events by calling this method.
void
handleTap
()
{
void
handleTap
()
{
_layoutText
(
constraints
.
maxWidth
);
selectPosition
(
cause:
SelectionChangedCause
.
tap
);
assert
(
_lastTapDownPosition
!=
null
);
if
(
onSelectionChanged
!=
null
)
{
final
TextPosition
position
=
_textPainter
.
getPositionForOffset
(
globalToLocal
(
_lastTapDownPosition
));
onSelectionChanged
(
TextSelection
.
fromPosition
(
position
),
this
,
SelectionChangedCause
.
tap
);
}
}
}
void
_handleTap
()
{
void
_handleTap
()
{
assert
(!
ignorePointer
);
assert
(!
ignorePointer
);
handleTap
();
handleTap
();
}
}
/// If [ignorePointer] is false (the default) then this method is called by
/// the internal gesture recognizer's [DoubleTapGestureRecognizer.onDoubleTap]
/// callback.
///
/// When [ignorePointer] is true, an ancestor widget must respond to double
/// tap events by calling this method.
void
handleDoubleTap
()
{
selectWord
(
cause:
SelectionChangedCause
.
doubleTap
);
}
/// If [ignorePointer] is false (the default) then this method is called by
/// If [ignorePointer] is false (the default) then this method is called by
/// the internal gesture recognizer's [LongPressRecognizer.onLongPress]
/// the internal gesture recognizer's [LongPressRecognizer.onLongPress]
/// callback.
/// callback.
...
@@ -1100,16 +1109,57 @@ class RenderEditable extends RenderBox {
...
@@ -1100,16 +1109,57 @@ class RenderEditable extends RenderBox {
/// When [ignorePointer] is true, an ancestor widget must respond to long
/// When [ignorePointer] is true, an ancestor widget must respond to long
/// press events by calling this method.
/// press events by calling this method.
void
handleLongPress
()
{
void
handleLongPress
()
{
selectWord
(
cause:
SelectionChangedCause
.
longPress
);
}
void
_handleLongPress
()
{
assert
(!
ignorePointer
);
handleLongPress
();
}
/// Move selection to the location of the last tap down.
void
selectPosition
({
@required
SelectionChangedCause
cause
})
{
assert
(
cause
!=
null
);
_layoutText
(
constraints
.
maxWidth
);
_layoutText
(
constraints
.
maxWidth
);
assert
(
_lastTapDownPosition
!=
null
);
assert
(
_lastTapDownPosition
!=
null
);
if
(
onSelectionChanged
!=
null
)
{
if
(
onSelectionChanged
!=
null
)
{
final
TextPosition
position
=
_textPainter
.
getPositionForOffset
(
globalToLocal
(
_lastTapDownPosition
));
final
TextPosition
position
=
_textPainter
.
getPositionForOffset
(
globalToLocal
(
_lastTapDownPosition
));
onSelectionChanged
(
_selectWordAtOffset
(
position
),
this
,
SelectionChangedCause
.
longPress
);
onSelectionChanged
(
TextSelection
.
fromPosition
(
position
),
this
,
cause
);
}
}
/// Select a word around the location of the last tap down.
void
selectWord
({
@required
SelectionChangedCause
cause
})
{
assert
(
cause
!=
null
);
_layoutText
(
constraints
.
maxWidth
);
assert
(
_lastTapDownPosition
!=
null
);
if
(
onSelectionChanged
!=
null
)
{
final
TextPosition
position
=
_textPainter
.
getPositionForOffset
(
globalToLocal
(
_lastTapDownPosition
));
onSelectionChanged
(
_selectWordAtOffset
(
position
),
this
,
cause
);
}
}
/// Move the selection to the beginning or end of a word.
void
selectWordEdge
({
@required
SelectionChangedCause
cause
})
{
assert
(
cause
!=
null
);
_layoutText
(
constraints
.
maxWidth
);
assert
(
_lastTapDownPosition
!=
null
);
if
(
onSelectionChanged
!=
null
)
{
final
TextPosition
position
=
_textPainter
.
getPositionForOffset
(
globalToLocal
(
_lastTapDownPosition
));
final
TextRange
word
=
_textPainter
.
getWordBoundary
(
position
);
if
(
position
.
offset
-
word
.
start
<=
1
)
{
onSelectionChanged
(
TextSelection
.
collapsed
(
offset:
word
.
start
,
affinity:
TextAffinity
.
downstream
),
this
,
cause
,
);
}
else
{
onSelectionChanged
(
TextSelection
.
collapsed
(
offset:
word
.
end
,
affinity:
TextAffinity
.
upstream
),
this
,
cause
,
);
}
}
}
}
void
_handleLongPress
()
{
assert
(!
ignorePointer
);
handleLongPress
();
}
}
TextSelection
_selectWordAtOffset
(
TextPosition
position
)
{
TextSelection
_selectWordAtOffset
(
TextPosition
position
)
{
...
...
packages/flutter/lib/src/widgets/editable_text.dart
View file @
988bfc16
...
@@ -740,7 +740,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
...
@@ -740,7 +740,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
final
bool
longPress
=
cause
==
SelectionChangedCause
.
longPress
;
final
bool
longPress
=
cause
==
SelectionChangedCause
.
longPress
;
if
(
cause
!=
SelectionChangedCause
.
keyboard
&&
(
_value
.
text
.
isNotEmpty
||
longPress
))
if
(
cause
!=
SelectionChangedCause
.
keyboard
&&
(
_value
.
text
.
isNotEmpty
||
longPress
))
_selectionOverlay
.
showHandles
();
_selectionOverlay
.
showHandles
();
if
(
longPress
)
if
(
longPress
||
cause
==
SelectionChangedCause
.
doubleTap
)
_selectionOverlay
.
showToolbar
();
_selectionOverlay
.
showToolbar
();
if
(
widget
.
onSelectionChanged
!=
null
)
if
(
widget
.
onSelectionChanged
!=
null
)
widget
.
onSelectionChanged
(
selection
,
cause
);
widget
.
onSelectionChanged
(
selection
,
cause
);
...
...
packages/flutter/test/cupertino/text_field_test.dart
View file @
988bfc16
This diff is collapsed.
Click to expand it.
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