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
78e94155
Unverified
Commit
78e94155
authored
Sep 28, 2022
by
LongCatIsLooong
Committed by
GitHub
Sep 28, 2022
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
[`RenderEditable`] report real height when `maxLines == 1`. (#112029)
parent
6e02485b
Changes
7
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
154 additions
and
74 deletions
+154
-74
text_painter.dart
packages/flutter/lib/src/painting/text_painter.dart
+1
-1
editable.dart
packages/flutter/lib/src/rendering/editable.dart
+85
-43
shifted_box.dart
packages/flutter/lib/src/rendering/shifted_box.dart
+13
-22
editable_text.dart
packages/flutter/lib/src/widgets/editable_text.dart
+2
-2
text_field_test.dart
packages/flutter/test/cupertino/text_field_test.dart
+7
-3
text_field_test.dart
packages/flutter/test/material/text_field_test.dart
+8
-3
editable_test.dart
packages/flutter/test/rendering/editable_test.dart
+38
-0
No files found.
packages/flutter/lib/src/painting/text_painter.dart
View file @
78e94155
...
...
@@ -1154,7 +1154,7 @@ class TextPainter {
/// Valid only after [layout] has been called.
List
<
ui
.
LineMetrics
>
computeLineMetrics
()
{
assert
(
_debugAssertTextLayoutIsValid
);
return
_lineMetricsCache
??=
_paragraph
!.
computeLineMetrics
();
return
_lineMetricsCache
??=
_paragraph
!.
computeLineMetrics
();
}
bool
_disposed
=
false
;
...
...
packages/flutter/lib/src/rendering/editable.dart
View file @
78e94155
...
...
@@ -318,6 +318,7 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin,
textDirection:
textDirection
,
textScaleFactor:
textScaleFactor
,
locale:
locale
,
maxLines:
maxLines
==
1
?
1
:
null
,
strutStyle:
strutStyle
,
textHeightBehavior:
textHeightBehavior
,
textWidthBasis:
textWidthBasis
,
...
...
@@ -781,8 +782,7 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin,
// Returns the obscured text when [obscureText] is true. See
// [obscureText] and [obscuringCharacter].
String
get
_plainText
{
_cachedPlainText
??=
_textPainter
.
text
!.
toPlainText
(
includeSemanticsLabels:
false
);
return
_cachedPlainText
!;
return
_cachedPlainText
??=
_textPainter
.
text
!.
toPlainText
(
includeSemanticsLabels:
false
);
}
/// The text to display.
...
...
@@ -794,8 +794,10 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin,
if
(
_textPainter
.
text
==
value
)
{
return
;
}
_textPainter
.
text
=
value
;
_cachedPlainText
=
null
;
_cachedLineBreakCount
=
null
;
_textPainter
.
text
=
value
;
_cachedAttributedValue
=
null
;
_cachedCombinedSemanticsInfos
=
null
;
_extractPlaceholderSpans
(
value
);
...
...
@@ -965,6 +967,11 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin,
return
;
}
_maxLines
=
value
;
// Special case maxLines == 1 to keep only the first line so we can get the
// height of the first line in case there are hard line breaks in the text.
// See the `_preferredHeight` method.
_textPainter
.
maxLines
=
value
==
1
?
1
:
null
;
markNeedsTextLayout
();
}
...
...
@@ -1790,42 +1797,72 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin,
/// This does not require the layout to be updated.
double
get
preferredLineHeight
=>
_textPainter
.
preferredLineHeight
;
double
_preferredHeight
(
double
width
)
{
// Lock height to maxLines if needed.
final
bool
lockedMax
=
maxLines
!=
null
&&
minLines
==
null
;
final
bool
lockedBoth
=
minLines
!=
null
&&
minLines
==
maxLines
;
final
bool
singleLine
=
maxLines
==
1
;
if
(
singleLine
||
lockedMax
||
lockedBoth
)
{
return
preferredLineHeight
*
maxLines
!;
}
// Clamp height to minLines or maxLines if needed.
final
bool
minLimited
=
minLines
!=
null
&&
minLines
!
>
1
;
final
bool
maxLimited
=
maxLines
!=
null
;
if
(
minLimited
||
maxLimited
)
{
_layoutText
(
maxWidth:
width
);
if
(
minLimited
&&
_textPainter
.
height
<
preferredLineHeight
*
minLines
!)
{
return
preferredLineHeight
*
minLines
!;
}
if
(
maxLimited
&&
_textPainter
.
height
>
preferredLineHeight
*
maxLines
!)
{
return
preferredLineHeight
*
maxLines
!;
int
?
_cachedLineBreakCount
;
// TODO(LongCatIsLooong): see if we can let ui.Paragraph estimate the number
// of lines
int
_countHardLineBreaks
(
String
text
)
{
final
int
?
cachedValue
=
_cachedLineBreakCount
;
if
(
cachedValue
!=
null
)
{
return
cachedValue
;
}
int
count
=
0
;
for
(
int
index
=
0
;
index
<
text
.
length
;
index
+=
1
)
{
switch
(
text
.
codeUnitAt
(
index
))
{
case
0x000A
:
// LF
case
0x0085
:
// NEL
case
0x000B
:
// VT
case
0x000C
:
// FF, treating it as a regular line separator
case
0x2028
:
// LS
case
0x2029
:
// PS
count
+=
1
;
}
}
return
_cachedLineBreakCount
=
count
;
}
// Set the height based on the content.
if
(
width
==
double
.
infinity
)
{
final
String
text
=
_plainText
;
int
lines
=
1
;
for
(
int
index
=
0
;
index
<
text
.
length
;
index
+=
1
)
{
// Count explicit line breaks.
if
(
text
.
codeUnitAt
(
index
)
==
0x0A
)
{
lines
+=
1
;
}
double
_preferredHeight
(
double
width
)
{
final
int
?
maxLines
=
this
.
maxLines
;
final
int
?
minLines
=
this
.
minLines
??
maxLines
;
final
double
minHeight
=
preferredLineHeight
*
(
minLines
??
0
);
if
(
maxLines
==
null
)
{
final
double
estimatedHeight
;
if
(
width
==
double
.
infinity
)
{
estimatedHeight
=
preferredLineHeight
*
(
_countHardLineBreaks
(
_plainText
)
+
1
);
}
else
{
_layoutText
(
maxWidth:
width
);
estimatedHeight
=
_textPainter
.
height
;
}
return
preferredLineHeight
*
lines
;
return
math
.
max
(
estimatedHeight
,
minHeight
);
}
// TODO(LongCatIsLooong): this is a workaround for
// https://github.com/flutter/flutter/issues/112123 .
// Use preferredLineHeight since SkParagraph currently returns an incorrect
// height.
final
TextHeightBehavior
?
textHeightBehavior
=
this
.
textHeightBehavior
;
final
bool
usePreferredLineHeightHack
=
maxLines
==
1
&&
text
?.
codeUnitAt
(
0
)
==
null
&&
strutStyle
!=
null
&&
strutStyle
!=
StrutStyle
.
disabled
&&
textHeightBehavior
!=
null
&&
(!
textHeightBehavior
.
applyHeightToFirstAscent
||
!
textHeightBehavior
.
applyHeightToLastDescent
);
// Special case maxLines == 1 since it forces the scrollable direction
// to be horizontal. Report the real height to prevent the text from being
// clipped.
if
(
maxLines
==
1
&&
!
usePreferredLineHeightHack
)
{
// The _layoutText call lays out the paragraph using infinite width when
// maxLines == 1. Also _textPainter.maxLines will be set to 1 so should
// there be any line breaks only the first line is shown.
assert
(
_textPainter
.
maxLines
==
1
);
_layoutText
(
maxWidth:
width
);
return
_textPainter
.
height
;
}
if
(
minLines
==
maxLines
)
{
return
minHeight
;
}
_layoutText
(
maxWidth:
width
);
return
math
.
max
(
preferredLineHeight
,
_textPainter
.
height
);
final
double
maxHeight
=
preferredLineHeight
*
maxLines
;
return
clampDouble
(
_textPainter
.
height
,
minHeight
,
maxHeight
);
}
@override
...
...
@@ -1852,14 +1889,17 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin,
bool
hitTestChildren
(
BoxHitTestResult
result
,
{
required
Offset
position
})
{
// Hit test text spans.
bool
hitText
=
false
;
final
Offset
effectivePosition
=
position
-
_paintOffset
;
final
TextPosition
textPosition
=
_textPainter
.
getPositionForOffset
(
effectivePosition
);
final
InlineSpan
?
span
=
_textPainter
.
text
!.
getSpanForPosition
(
textPosition
);
if
(
span
!=
null
&&
span
is
HitTestTarget
)
{
result
.
add
(
HitTestEntry
(
span
as
HitTestTarget
));
hitText
=
true
;
}
final
InlineSpan
?
textSpan
=
_textPainter
.
text
;
if
(
textSpan
!=
null
)
{
final
Offset
effectivePosition
=
position
-
_paintOffset
;
final
TextPosition
textPosition
=
_textPainter
.
getPositionForOffset
(
effectivePosition
);
final
Object
?
span
=
textSpan
.
getSpanForPosition
(
textPosition
);
if
(
span
is
HitTestTarget
)
{
result
.
add
(
HitTestEntry
(
span
));
hitText
=
true
;
}
}
// Hit test render object children
RenderBox
?
child
=
firstChild
;
int
childIndex
=
0
;
...
...
@@ -2359,7 +2399,8 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin,
final
Size
textPainterSize
=
_textPainter
.
size
;
final
double
width
=
forceLine
?
constraints
.
maxWidth
:
constraints
.
constrainWidth
(
_textPainter
.
size
.
width
+
_caretMargin
);
size
=
Size
(
width
,
constraints
.
constrainHeight
(
_preferredHeight
(
constraints
.
maxWidth
)));
final
double
preferredHeight
=
_preferredHeight
(
constraints
.
maxWidth
);
size
=
Size
(
width
,
constraints
.
constrainHeight
(
preferredHeight
));
final
Size
contentSize
=
Size
(
textPainterSize
.
width
+
_caretMargin
,
textPainterSize
.
height
);
final
BoxConstraints
painterConstraints
=
BoxConstraints
.
tight
(
contentSize
);
...
...
@@ -2595,8 +2636,9 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin,
_clipRectLayer
.
layer
=
null
;
_paintContents
(
context
,
offset
);
}
if
(
selection
!.
isValid
)
{
_paintHandleLayers
(
context
,
getEndpointsForSelection
(
selection
!),
offset
);
final
TextSelection
?
selection
=
this
.
selection
;
if
(
selection
!=
null
&&
selection
.
isValid
)
{
_paintHandleLayers
(
context
,
getEndpointsForSelection
(
selection
),
offset
);
}
}
...
...
packages/flutter/lib/src/rendering/shifted_box.dart
View file @
78e94155
...
...
@@ -31,43 +31,32 @@ abstract class RenderShiftedBox extends RenderBox with RenderObjectWithChildMixi
@override
double
computeMinIntrinsicWidth
(
double
height
)
{
if
(
child
!=
null
)
{
return
child
!.
getMinIntrinsicWidth
(
height
);
}
return
0.0
;
return
child
?.
getMinIntrinsicWidth
(
height
)
??
0.0
;
}
@override
double
computeMaxIntrinsicWidth
(
double
height
)
{
if
(
child
!=
null
)
{
return
child
!.
getMaxIntrinsicWidth
(
height
);
}
return
0.0
;
return
child
?.
getMaxIntrinsicWidth
(
height
)
??
0.0
;
}
@override
double
computeMinIntrinsicHeight
(
double
width
)
{
if
(
child
!=
null
)
{
return
child
!.
getMinIntrinsicHeight
(
width
);
}
return
0.0
;
return
child
?.
getMinIntrinsicHeight
(
width
)
??
0.0
;
}
@override
double
computeMaxIntrinsicHeight
(
double
width
)
{
if
(
child
!=
null
)
{
return
child
!.
getMaxIntrinsicHeight
(
width
);
}
return
0.0
;
return
child
?.
getMaxIntrinsicHeight
(
width
)
??
0.0
;
}
@override
double
?
computeDistanceToActualBaseline
(
TextBaseline
baseline
)
{
double
?
result
;
final
RenderBox
?
child
=
this
.
child
;
if
(
child
!=
null
)
{
assert
(!
debugNeedsLayout
);
result
=
child
!
.
getDistanceToActualBaseline
(
baseline
);
final
BoxParentData
childParentData
=
child
!
.
parentData
!
as
BoxParentData
;
result
=
child
.
getDistanceToActualBaseline
(
baseline
);
final
BoxParentData
childParentData
=
child
.
parentData
!
as
BoxParentData
;
if
(
result
!=
null
)
{
result
+=
childParentData
.
offset
.
dy
;
}
...
...
@@ -79,22 +68,24 @@ abstract class RenderShiftedBox extends RenderBox with RenderObjectWithChildMixi
@override
void
paint
(
PaintingContext
context
,
Offset
offset
)
{
final
RenderBox
?
child
=
this
.
child
;
if
(
child
!=
null
)
{
final
BoxParentData
childParentData
=
child
!
.
parentData
!
as
BoxParentData
;
context
.
paintChild
(
child
!
,
childParentData
.
offset
+
offset
);
final
BoxParentData
childParentData
=
child
.
parentData
!
as
BoxParentData
;
context
.
paintChild
(
child
,
childParentData
.
offset
+
offset
);
}
}
@override
bool
hitTestChildren
(
BoxHitTestResult
result
,
{
required
Offset
position
})
{
final
RenderBox
?
child
=
this
.
child
;
if
(
child
!=
null
)
{
final
BoxParentData
childParentData
=
child
!
.
parentData
!
as
BoxParentData
;
final
BoxParentData
childParentData
=
child
.
parentData
!
as
BoxParentData
;
return
result
.
addWithPaintOffset
(
offset:
childParentData
.
offset
,
position:
position
,
hitTest:
(
BoxHitTestResult
result
,
Offset
transformed
)
{
assert
(
transformed
==
position
-
childParentData
.
offset
);
return
child
!
.
hitTest
(
result
,
position:
transformed
);
return
child
.
hitTest
(
result
,
position:
transformed
);
},
);
}
...
...
packages/flutter/lib/src/widgets/editable_text.dart
View file @
78e94155
...
...
@@ -1139,8 +1139,8 @@ class EditableText extends StatefulWidget {
/// [TextEditingController.addListener].
///
/// [onChanged] is called before [onSubmitted] when user indicates completion
/// of editing, such as when pressing the "done" button on the keyboard. That
default
/// behavior can be overridden. See [onEditingComplete] for details.
/// of editing, such as when pressing the "done" button on the keyboard. That
///
default
behavior can be overridden. See [onEditingComplete] for details.
///
/// {@tool dartpad}
/// This example shows how onChanged could be used to check the TextField's
...
...
packages/flutter/test/cupertino/text_field_test.dart
View file @
78e94155
...
...
@@ -5149,9 +5149,13 @@ void main() {
height:
200.0
,
width:
200.0
,
child:
Center
(
child:
CupertinoTextField
(
controller:
OverflowWidgetTextEditingController
(),
clipBehavior:
Clip
.
none
,
child:
SizedBox
(
// Make sure the input field is not high enough for the WidgetSpan.
height:
50
,
child:
CupertinoTextField
(
controller:
OverflowWidgetTextEditingController
(),
clipBehavior:
Clip
.
none
,
),
),
),
),
...
...
packages/flutter/test/material/text_field_test.dart
View file @
78e94155
...
...
@@ -841,9 +841,13 @@ void main() {
height:
200
,
width:
200
,
child:
Center
(
child:
TextField
(
controller:
OverflowWidgetTextEditingController
(),
clipBehavior:
Clip
.
none
,
child:
SizedBox
(
// Make sure the input field is not high enough for the WidgetSpan.
height:
50
,
child:
TextField
(
controller:
OverflowWidgetTextEditingController
(),
clipBehavior:
Clip
.
none
,
),
),
),
),
...
...
@@ -9068,6 +9072,7 @@ void main() {
home:
Material
(
child:
Center
(
child:
TextField
(
maxLines:
null
,
controller:
controller
,
),
),
...
...
packages/flutter/test/rendering/editable_test.dart
View file @
78e94155
...
...
@@ -95,6 +95,44 @@ void main() {
}
});
test
(
'Reports the real height when maxLines is 1'
,
()
{
const
InlineSpan
tallSpan
=
TextSpan
(
style:
TextStyle
(
fontSize:
10
),
children:
<
InlineSpan
>[
TextSpan
(
text:
'TALL'
,
style:
TextStyle
(
fontSize:
100
))],
);
final
BoxConstraints
constraints
=
BoxConstraints
.
loose
(
const
Size
(
600
,
600
));
final
RenderEditable
editable
=
RenderEditable
(
textDirection:
TextDirection
.
ltr
,
startHandleLayerLink:
LayerLink
(),
endHandleLayerLink:
LayerLink
(),
offset:
ViewportOffset
.
zero
(),
textSelectionDelegate:
_FakeEditableTextState
(),
text:
tallSpan
,
);
layout
(
editable
,
constraints:
constraints
);
expect
(
editable
.
size
.
height
,
100
);
});
test
(
'Reports the height of the first line when maxLines is 1'
,
()
{
final
InlineSpan
multilineSpan
=
TextSpan
(
text:
'liiiiines
\n
'
*
10
,
style:
const
TextStyle
(
fontSize:
10
),
);
final
BoxConstraints
constraints
=
BoxConstraints
.
loose
(
const
Size
(
600
,
600
));
final
RenderEditable
editable
=
RenderEditable
(
textDirection:
TextDirection
.
ltr
,
startHandleLayerLink:
LayerLink
(),
endHandleLayerLink:
LayerLink
(),
offset:
ViewportOffset
.
zero
(),
textSelectionDelegate:
_FakeEditableTextState
(),
text:
multilineSpan
,
);
layout
(
editable
,
constraints:
constraints
);
expect
(
editable
.
size
.
height
,
10
);
});
test
(
'Editable respect clipBehavior in describeApproximatePaintClip'
,
()
{
final
String
longString
=
'a'
*
10000
;
final
RenderEditable
editable
=
RenderEditable
(
...
...
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