Allow customization of TextSpan in EditableText (#16909)
* Allow customization of TextSpan in EditableText * Addressed PR comments * Added test with custom-styled EditableText subclass * More code style fixes
This commit is contained in:
parent
8b8d368d2b
commit
50a1d91ff3
@ -674,8 +674,8 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
|
||||
onPaste: _hasFocus && controls?.canPaste(this) == true ? () => controls.handlePaste(this) : null,
|
||||
child: new _Editable(
|
||||
key: _editableKey,
|
||||
textSpan: buildTextSpan(),
|
||||
value: _value,
|
||||
style: widget.style,
|
||||
cursorColor: widget.cursorColor,
|
||||
showCursor: _showCursor,
|
||||
hasFocus: _hasFocus,
|
||||
@ -685,7 +685,6 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
|
||||
textAlign: widget.textAlign,
|
||||
textDirection: _textDirection,
|
||||
obscureText: widget.obscureText,
|
||||
obscureShowCharacterAtIndex: _obscureShowCharTicksPending > 0 ? _obscureLatestCharIndex : null,
|
||||
autocorrect: widget.autocorrect,
|
||||
offset: offset,
|
||||
onSelectionChanged: _handleSelectionChanged,
|
||||
@ -697,13 +696,46 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Builds [TextSpan] from current editing value.
|
||||
///
|
||||
/// By default makes text in composing range appear as underlined.
|
||||
/// Descendants can override this method to customize appearance of text.
|
||||
TextSpan buildTextSpan() {
|
||||
if (!widget.obscureText && _value.composing.isValid) {
|
||||
final TextStyle composingStyle = widget.style.merge(
|
||||
const TextStyle(decoration: TextDecoration.underline),
|
||||
);
|
||||
|
||||
return new TextSpan(
|
||||
style: widget.style,
|
||||
children: <TextSpan>[
|
||||
new TextSpan(text: _value.composing.textBefore(_value.text)),
|
||||
new TextSpan(
|
||||
style: composingStyle,
|
||||
text: _value.composing.textInside(_value.text),
|
||||
),
|
||||
new TextSpan(text: _value.composing.textAfter(_value.text)),
|
||||
]);
|
||||
}
|
||||
|
||||
String text = _value.text;
|
||||
if (widget.obscureText) {
|
||||
text = RenderEditable.obscuringCharacter * text.length;
|
||||
final int o =
|
||||
_obscureShowCharTicksPending > 0 ? _obscureLatestCharIndex : null;
|
||||
if (o != null && o >= 0 && o < text.length)
|
||||
text = text.replaceRange(o, o + 1, _value.text.substring(o, o + 1));
|
||||
}
|
||||
return new TextSpan(style: widget.style, text: text);
|
||||
}
|
||||
}
|
||||
|
||||
class _Editable extends LeafRenderObjectWidget {
|
||||
const _Editable({
|
||||
Key key,
|
||||
this.textSpan,
|
||||
this.value,
|
||||
this.style,
|
||||
this.cursorColor,
|
||||
this.showCursor,
|
||||
this.hasFocus,
|
||||
@ -713,7 +745,6 @@ class _Editable extends LeafRenderObjectWidget {
|
||||
this.textAlign,
|
||||
@required this.textDirection,
|
||||
this.obscureText,
|
||||
this.obscureShowCharacterAtIndex,
|
||||
this.autocorrect,
|
||||
this.offset,
|
||||
this.onSelectionChanged,
|
||||
@ -723,8 +754,8 @@ class _Editable extends LeafRenderObjectWidget {
|
||||
assert(rendererIgnoresPointer != null),
|
||||
super(key: key);
|
||||
|
||||
final TextSpan textSpan;
|
||||
final TextEditingValue value;
|
||||
final TextStyle style;
|
||||
final Color cursorColor;
|
||||
final ValueNotifier<bool> showCursor;
|
||||
final bool hasFocus;
|
||||
@ -734,7 +765,6 @@ class _Editable extends LeafRenderObjectWidget {
|
||||
final TextAlign textAlign;
|
||||
final TextDirection textDirection;
|
||||
final bool obscureText;
|
||||
final int obscureShowCharacterAtIndex;
|
||||
final bool autocorrect;
|
||||
final ViewportOffset offset;
|
||||
final SelectionChangedHandler onSelectionChanged;
|
||||
@ -744,7 +774,7 @@ class _Editable extends LeafRenderObjectWidget {
|
||||
@override
|
||||
RenderEditable createRenderObject(BuildContext context) {
|
||||
return new RenderEditable(
|
||||
text: _styledTextSpan,
|
||||
text: textSpan,
|
||||
cursorColor: cursorColor,
|
||||
showCursor: showCursor,
|
||||
hasFocus: hasFocus,
|
||||
@ -765,7 +795,7 @@ class _Editable extends LeafRenderObjectWidget {
|
||||
@override
|
||||
void updateRenderObject(BuildContext context, RenderEditable renderObject) {
|
||||
renderObject
|
||||
..text = _styledTextSpan
|
||||
..text = textSpan
|
||||
..cursorColor = cursorColor
|
||||
..showCursor = showCursor
|
||||
..hasFocus = hasFocus
|
||||
@ -781,32 +811,4 @@ class _Editable extends LeafRenderObjectWidget {
|
||||
..ignorePointer = rendererIgnoresPointer
|
||||
..obscureText = obscureText;
|
||||
}
|
||||
|
||||
TextSpan get _styledTextSpan {
|
||||
if (!obscureText && value.composing.isValid) {
|
||||
final TextStyle composingStyle = style.merge(
|
||||
const TextStyle(decoration: TextDecoration.underline)
|
||||
);
|
||||
|
||||
return new TextSpan(
|
||||
style: style,
|
||||
children: <TextSpan>[
|
||||
new TextSpan(text: value.composing.textBefore(value.text)),
|
||||
new TextSpan(
|
||||
style: composingStyle,
|
||||
text: value.composing.textInside(value.text)
|
||||
),
|
||||
new TextSpan(text: value.composing.textAfter(value.text))
|
||||
]);
|
||||
}
|
||||
|
||||
String text = value.text;
|
||||
if (obscureText) {
|
||||
text = RenderEditable.obscuringCharacter * text.length;
|
||||
final int o = obscureShowCharacterAtIndex;
|
||||
if (o != null && o >= 0 && o < text.length)
|
||||
text = text.replaceRange(o, o + 1, value.text.substring(o, o + 1));
|
||||
}
|
||||
return new TextSpan(style: style, text: text);
|
||||
}
|
||||
}
|
||||
|
@ -766,6 +766,50 @@ void main() {
|
||||
});
|
||||
});
|
||||
|
||||
testWidgets('allows customizing text style in subclasses', (WidgetTester tester) async {
|
||||
controller.text = 'Hello World';
|
||||
|
||||
await tester.pumpWidget(new MaterialApp(
|
||||
home: new CustomStyleEditableText(
|
||||
controller: controller,
|
||||
focusNode: focusNode,
|
||||
style: textStyle,
|
||||
cursorColor: cursorColor,
|
||||
),
|
||||
));
|
||||
|
||||
// Simulate selection change via tap to show handles.
|
||||
final RenderEditable render = tester.allRenderObjects.firstWhere((RenderObject o) => o.runtimeType == RenderEditable);
|
||||
expect(render.text.style.fontStyle, FontStyle.italic);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
class MockTextSelectionControls extends Mock implements TextSelectionControls {}
|
||||
|
||||
class CustomStyleEditableText extends EditableText {
|
||||
CustomStyleEditableText({
|
||||
TextEditingController controller,
|
||||
Color cursorColor,
|
||||
FocusNode focusNode,
|
||||
TextStyle style,
|
||||
}): super(
|
||||
controller:controller,
|
||||
cursorColor: cursorColor,
|
||||
focusNode: focusNode,
|
||||
style: style,
|
||||
);
|
||||
@override
|
||||
CustomStyleEditableTextState createState() =>
|
||||
new CustomStyleEditableTextState();
|
||||
}
|
||||
|
||||
class CustomStyleEditableTextState extends EditableTextState {
|
||||
@override
|
||||
TextSpan buildTextSpan() {
|
||||
return new TextSpan(
|
||||
style: const TextStyle(fontStyle: FontStyle.italic),
|
||||
text: widget.controller.value.text,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user