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
bbb95e57
Unverified
Commit
bbb95e57
authored
Jun 01, 2020
by
LongCatIsLooong
Committed by
GitHub
Jun 01, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
EditableText.bringIntoView calls showOnScreen (#58346)
parent
5267d987
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
171 additions
and
52 deletions
+171
-52
editable_text.dart
packages/flutter/lib/src/widgets/editable_text.dart
+70
-52
editable_text_test.dart
packages/flutter/test/widgets/editable_text_test.dart
+101
-0
No files found.
packages/flutter/lib/src/widgets/editable_text.dart
View file @
bbb95e57
...
...
@@ -1487,45 +1487,57 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
bool
get
_hasFocus
=>
widget
.
focusNode
.
hasFocus
;
bool
get
_isMultiline
=>
widget
.
maxLines
!=
1
;
// Calculate the new scroll offset so the cursor remains visible.
double
_getScrollOffsetForCaret
(
Rect
caretRect
)
{
double
caretStart
;
double
caretEnd
;
if
(
_isMultiline
)
{
// The caret is vertically centered within the line. Expand the caret's
// height so that it spans the line because we're going to ensure that the entire
// expanded caret is scrolled into view.
final
double
lineHeight
=
renderEditable
.
preferredLineHeight
;
final
double
caretOffset
=
(
lineHeight
-
caretRect
.
height
)
/
2
;
caretStart
=
caretRect
.
top
-
caretOffset
;
caretEnd
=
caretRect
.
bottom
+
caretOffset
;
// Finds the closest scroll offset to the current scroll offset that fully
// reveals the given caret rect. If the given rect's main axis extent is too
// large to be fully revealed in `renderEditable`, it will be centered along
// the main axis.
//
// If this is a multiline EditableText (which means the Editable can only
// scroll vertically), the given rect's height will first be extended to match
// `renderEditable.preferredLineHeight`, before the target scroll offset is
// calculated.
RevealedOffset
_getOffsetToRevealCaret
(
Rect
rect
)
{
if
(!
_scrollController
.
position
.
allowImplicitScrolling
)
return
RevealedOffset
(
offset:
_scrollController
.
offset
,
rect:
rect
);
final
Size
editableSize
=
renderEditable
.
size
;
double
additionalOffset
;
Offset
unitOffset
;
if
(!
_isMultiline
)
{
additionalOffset
=
rect
.
width
>=
editableSize
.
width
// Center `rect` if it's oversized.
?
editableSize
.
width
/
2
-
rect
.
center
.
dx
// Valid additional offsets range from (rect.right - size.width)
// to (rect.left). Pick the closest one if out of range.
:
0.0
.
clamp
(
rect
.
right
-
editableSize
.
width
,
rect
.
left
)
as
double
;
unitOffset
=
const
Offset
(
1
,
0
);
}
else
{
// Scrolls horizontally for single-line fields.
caretStart
=
caretRect
.
left
;
caretEnd
=
caretRect
.
right
;
}
// The caret is vertically centered within the line. Expand the caret's
// height so that it spans the line because we're going to ensure that the
// entire expanded caret is scrolled into view.
final
Rect
expandedRect
=
Rect
.
fromCenter
(
center:
rect
.
center
,
width:
rect
.
width
,
height:
math
.
max
(
rect
.
height
,
renderEditable
.
preferredLineHeight
),
);
double
scrollOffset
=
_scrollController
.
offset
;
final
double
viewportExtent
=
_scrollController
.
position
.
viewportDimension
;
if
(
caretStart
<
0.0
)
{
// cursor before start of bounds
scrollOffset
+=
caretStart
;
}
else
if
(
caretEnd
>=
viewportExtent
)
{
// cursor after end of bounds
scrollOffset
+=
caretEnd
-
viewportExtent
;
additionalOffset
=
expandedRect
.
height
>=
editableSize
.
height
?
editableSize
.
height
/
2
-
expandedRect
.
center
.
dy
:
0.0
.
clamp
(
expandedRect
.
bottom
-
editableSize
.
height
,
expandedRect
.
top
)
as
double
;
unitOffset
=
const
Offset
(
0
,
1
);
}
if
(
_isMultiline
)
{
// Clamp the final results to prevent programmatically scrolling to
// out-of-paragraph-bounds positions when encountering tall fonts/scripts that
// extend past the ascent.
scrollOffset
=
scrollOffset
.
clamp
(
0.0
,
renderEditable
.
maxScrollExtent
)
as
double
;
}
return
scrollOffset
;
}
// No overscrolling when encountering tall fonts/scripts that extend past
// the ascent.
final
double
targetOffset
=
(
additionalOffset
+
_scrollController
.
offset
)
.
clamp
(
_scrollController
.
position
.
minScrollExtent
,
_scrollController
.
position
.
maxScrollExtent
,
)
as
double
;
// Calculates where the `caretRect` would be if `_scrollController.offset` is set to `scrollOffset`.
Rect
_getCaretRectAtScrollOffset
(
Rect
caretRect
,
double
scrollOffset
)
{
final
double
offsetDiff
=
_scrollController
.
offset
-
scrollOffset
;
return
_isMultiline
?
caretRect
.
translate
(
0.0
,
offsetDiff
)
:
caretRect
.
translate
(
offsetDiff
,
0.0
);
final
double
offsetDelta
=
_scrollController
.
offset
-
targetOffset
;
return
RevealedOffset
(
rect:
rect
.
shift
(
unitOffset
*
offsetDelta
),
offset:
targetOffset
);
}
bool
get
_hasInputConnection
=>
_textInputConnection
!=
null
&&
_textInputConnection
.
attached
;
...
...
@@ -1684,19 +1696,15 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
if
(
_currentCaretRect
==
null
||
!
_scrollController
.
hasClients
)
{
return
;
}
final
double
scrollOffsetForCaret
=
_getScrollOffsetForCaret
(
_currentCaretRect
);
_scrollController
.
animateTo
(
scrollOffsetForCaret
,
duration:
_caretAnimationDuration
,
curve:
_caretAnimationCurve
,
);
final
Rect
newCaretRect
=
_getCaretRectAtScrollOffset
(
_currentCaretRect
,
scrollOffsetForCaret
);
// Enlarge newCaretRect by scrollPadding to ensure that caret is not
final
double
lineHeight
=
renderEditable
.
preferredLineHeight
;
// Enlarge the target rect by scrollPadding to ensure that caret is not
// positioned directly at the edge after scrolling.
double
bottomSpacing
=
widget
.
scrollPadding
.
bottom
;
if
(
_selectionOverlay
?.
selectionControls
!=
null
)
{
final
double
handleHeight
=
_selectionOverlay
.
selectionControls
.
getHandleSize
(
renderEditable
.
preferredL
ineHeight
).
height
;
.
getHandleSize
(
l
ineHeight
).
height
;
final
double
interactiveHandleHeight
=
math
.
max
(
handleHeight
,
kMinInteractiveDimension
,
...
...
@@ -1704,7 +1712,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
final
Offset
anchor
=
_selectionOverlay
.
selectionControls
.
getHandleAnchor
(
TextSelectionHandleType
.
collapsed
,
renderEditable
.
preferredL
ineHeight
,
l
ineHeight
,
);
final
double
handleCenter
=
handleHeight
/
2
-
anchor
.
dy
;
bottomSpacing
=
math
.
max
(
...
...
@@ -1712,14 +1720,20 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
bottomSpacing
,
);
}
final
Rect
inflatedRect
=
Rect
.
fromLTRB
(
newCaretRect
.
left
-
widget
.
scrollPadding
.
left
,
newCaretRect
.
top
-
widget
.
scrollPadding
.
top
,
newCaretRect
.
right
+
widget
.
scrollPadding
.
right
,
newCaretRect
.
bottom
+
bottomSpacing
,
final
EdgeInsets
caretPadding
=
widget
.
scrollPadding
.
copyWith
(
bottom:
bottomSpacing
);
final
RevealedOffset
targetOffset
=
_getOffsetToRevealCaret
(
_currentCaretRect
);
_scrollController
.
animateTo
(
targetOffset
.
offset
,
duration:
_caretAnimationDuration
,
curve:
_caretAnimationCurve
,
);
_editableKey
.
currentContext
.
findRenderObject
().
showOnScreen
(
rect:
inflatedRect
,
renderEditable
.
showOnScreen
(
rect:
caretPadding
.
inflateRect
(
targetOffset
.
rect
),
duration:
_caretAnimationDuration
,
curve:
_caretAnimationCurve
,
);
...
...
@@ -1928,7 +1942,11 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
@override
void
bringIntoView
(
TextPosition
position
)
{
_scrollController
.
jumpTo
(
_getScrollOffsetForCaret
(
renderEditable
.
getLocalRectForCaret
(
position
)));
final
Rect
localRect
=
renderEditable
.
getLocalRectForCaret
(
position
);
final
RevealedOffset
targetOffset
=
_getOffsetToRevealCaret
(
localRect
);
_scrollController
.
jumpTo
(
targetOffset
.
offset
);
renderEditable
.
showOnScreen
(
rect:
targetOffset
.
rect
);
}
/// Shows the selection toolbar at the location of the current cursor.
...
...
packages/flutter/test/widgets/editable_text_test.dart
View file @
bbb95e57
...
...
@@ -4083,6 +4083,95 @@ void main() {
expect
(
scrollable
.
controller
.
position
.
pixels
,
equals
(
renderEditable
.
maxScrollExtent
));
},
skip:
isBrowser
);
testWidgets
(
'bringIntoView brings the caret into view when in a viewport'
,
(
WidgetTester
tester
)
async
{
// Regression test for https://github.com/flutter/flutter/issues/55547.
final
TextEditingController
controller
=
TextEditingController
(
text:
testText
*
20
);
final
ScrollController
editableScrollController
=
ScrollController
();
final
ScrollController
outerController
=
ScrollController
();
await
tester
.
pumpWidget
(
MaterialApp
(
home:
Align
(
alignment:
Alignment
.
topLeft
,
child:
SizedBox
(
width:
200
,
height:
200
,
child:
SingleChildScrollView
(
controller:
outerController
,
child:
EditableText
(
maxLines:
null
,
controller:
controller
,
scrollController:
editableScrollController
,
focusNode:
FocusNode
(),
style:
textStyle
,
cursorColor:
Colors
.
blue
,
backgroundCursorColor:
Colors
.
grey
,
),
),
),
),
));
expect
(
outerController
.
offset
,
0
);
expect
(
editableScrollController
.
offset
,
0
);
final
EditableTextState
state
=
tester
.
state
<
EditableTextState
>(
find
.
byType
(
EditableText
));
state
.
bringIntoView
(
TextPosition
(
offset:
controller
.
text
.
length
));
await
tester
.
pumpAndSettle
();
// The SingleChildScrollView is scrolled instead of the EditableText to
// reveal the caret.
expect
(
outerController
.
offset
,
outerController
.
position
.
maxScrollExtent
);
expect
(
editableScrollController
.
offset
,
0
);
});
testWidgets
(
'bringIntoView does nothing if the physics prohibits implicit scrolling'
,
(
WidgetTester
tester
)
async
{
final
TextEditingController
controller
=
TextEditingController
(
text:
testText
*
20
);
final
ScrollController
scrollController
=
ScrollController
();
Future
<
void
>
buildWithPhysics
({
ScrollPhysics
physics
})
async
{
await
tester
.
pumpWidget
(
MaterialApp
(
home:
Align
(
alignment:
Alignment
.
topLeft
,
child:
SizedBox
(
width:
200
,
height:
200
,
child:
EditableText
(
maxLines:
null
,
controller:
controller
,
scrollController:
scrollController
,
focusNode:
FocusNode
(),
style:
textStyle
,
cursorColor:
Colors
.
blue
,
backgroundCursorColor:
Colors
.
grey
,
scrollPhysics:
physics
,
),
),
),
));
}
await
buildWithPhysics
();
expect
(
scrollController
.
offset
,
0
);
final
EditableTextState
state
=
tester
.
state
<
EditableTextState
>(
find
.
byType
(
EditableText
));
state
.
bringIntoView
(
TextPosition
(
offset:
controller
.
text
.
length
));
await
tester
.
pumpAndSettle
();
// Scrolled to the maxScrollExtent to reveal to caret.
expect
(
scrollController
.
offset
,
scrollController
.
position
.
maxScrollExtent
);
scrollController
.
jumpTo
(
0
);
await
buildWithPhysics
(
physics:
const
NoImplicitScrollPhysics
());
expect
(
scrollController
.
offset
,
0
);
state
.
bringIntoView
(
TextPosition
(
offset:
controller
.
text
.
length
));
await
tester
.
pumpAndSettle
();
expect
(
scrollController
.
offset
,
0
);
});
testWidgets
(
'obscured multiline fields throw an exception'
,
(
WidgetTester
tester
)
async
{
final
TextEditingController
controller
=
TextEditingController
();
expect
(
...
...
@@ -4900,3 +4989,15 @@ class _TransformedEditableTextState extends State<TransformedEditableText> {
);
}
}
class
NoImplicitScrollPhysics
extends
AlwaysScrollableScrollPhysics
{
const
NoImplicitScrollPhysics
({
ScrollPhysics
parent
})
:
super
(
parent:
parent
);
@override
bool
get
allowImplicitScrolling
=>
false
;
@override
NoImplicitScrollPhysics
applyTo
(
ScrollPhysics
ancestor
)
{
return
NoImplicitScrollPhysics
(
parent:
buildParent
(
ancestor
));
}
}
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