custom_text_edit.dart 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. import 'package:flutter/foundation.dart';
  2. import 'package:flutter/material.dart';
  3. import 'package:flutter/services.dart';
  4. class CustomTextEdit extends StatefulWidget {
  5. CustomTextEdit({
  6. Key? key,
  7. required this.child,
  8. required this.onInsert,
  9. required this.onDelete,
  10. required this.onComposing,
  11. required this.onAction,
  12. required this.onKey,
  13. required this.focusNode,
  14. this.autofocus = false,
  15. this.readOnly = false,
  16. // this.initEditingState = TextEditingValue.empty,
  17. this.inputType = TextInputType.text,
  18. this.inputAction = TextInputAction.newline,
  19. this.keyboardAppearance = Brightness.light,
  20. this.deleteDetection = false,
  21. }) : super(key: key);
  22. final Widget child;
  23. final void Function(String) onInsert;
  24. final void Function() onDelete;
  25. final void Function(String?) onComposing;
  26. final void Function(TextInputAction) onAction;
  27. final KeyEventResult Function(RawKeyEvent) onKey;
  28. final FocusNode focusNode;
  29. final bool autofocus;
  30. final bool readOnly;
  31. final TextInputType inputType;
  32. final TextInputAction inputAction;
  33. final Brightness keyboardAppearance;
  34. final bool deleteDetection;
  35. @override
  36. CustomTextEditState createState() => CustomTextEditState();
  37. }
  38. class CustomTextEditState extends State<CustomTextEdit>
  39. implements TextInputClient {
  40. TextInputConnection? _connection;
  41. @override
  42. void initState() {
  43. widget.focusNode.addListener(_onFocusChange);
  44. super.initState();
  45. }
  46. @override
  47. void didUpdateWidget(CustomTextEdit oldWidget) {
  48. super.didUpdateWidget(oldWidget);
  49. if (widget.focusNode != oldWidget.focusNode) {
  50. oldWidget.focusNode.removeListener(_onFocusChange);
  51. widget.focusNode.addListener(_onFocusChange);
  52. }
  53. if (!_shouldCreateInputConnection) {
  54. _closeInputConnectionIfNeeded();
  55. } else {
  56. if (oldWidget.readOnly && widget.focusNode.hasFocus) {
  57. _openInputConnection();
  58. }
  59. }
  60. }
  61. @override
  62. Widget build(BuildContext context) {
  63. return Focus(
  64. focusNode: widget.focusNode,
  65. autofocus: widget.autofocus,
  66. onKey: _onKey,
  67. child: widget.child,
  68. );
  69. }
  70. bool get hasInputConnection => _connection != null && _connection!.attached;
  71. void requestKeyboard() {
  72. if (widget.focusNode.hasFocus) {
  73. _openInputConnection();
  74. } else {
  75. widget.focusNode.requestFocus();
  76. }
  77. }
  78. void closeKeyboard() {
  79. if (hasInputConnection) {
  80. _connection?.close();
  81. }
  82. }
  83. void setEditingState(TextEditingValue value) {
  84. _currentEditingState = value;
  85. _connection?.setEditingState(value);
  86. }
  87. void setEditableRect(Rect rect, Rect caretRect) {
  88. if (!hasInputConnection) {
  89. return;
  90. }
  91. _connection?.setEditableSizeAndTransform(
  92. rect.size,
  93. Matrix4.translationValues(0, 0, 0),
  94. );
  95. _connection?.setCaretRect(caretRect);
  96. }
  97. void _onFocusChange() {
  98. _openOrCloseInputConnectionIfNeeded();
  99. }
  100. KeyEventResult _onKey(FocusNode focusNode, RawKeyEvent event) {
  101. if (_currentEditingState.composing.isCollapsed) {
  102. return widget.onKey(event);
  103. }
  104. return KeyEventResult.skipRemainingHandlers;
  105. }
  106. void _openOrCloseInputConnectionIfNeeded() {
  107. if (widget.focusNode.hasFocus && widget.focusNode.consumeKeyboardToken()) {
  108. _openInputConnection();
  109. } else if (!widget.focusNode.hasFocus) {
  110. _closeInputConnectionIfNeeded();
  111. }
  112. }
  113. bool get _shouldCreateInputConnection => kIsWeb || !widget.readOnly;
  114. void _openInputConnection() {
  115. if (!_shouldCreateInputConnection) {
  116. return;
  117. }
  118. if (hasInputConnection) {
  119. _connection!.show();
  120. } else {
  121. final config = TextInputConfiguration(
  122. inputType: widget.inputType,
  123. inputAction: widget.inputAction,
  124. keyboardAppearance: widget.keyboardAppearance,
  125. autocorrect: false,
  126. enableSuggestions: false,
  127. enableIMEPersonalizedLearning: false,
  128. );
  129. _connection = TextInput.attach(this, config);
  130. _connection!.show();
  131. // setEditableRect(Rect.zero, Rect.zero);
  132. _connection!.setEditingState(_initEditingState);
  133. }
  134. }
  135. void _closeInputConnectionIfNeeded() {
  136. if (_connection != null && _connection!.attached) {
  137. _connection!.close();
  138. _connection = null;
  139. }
  140. }
  141. TextEditingValue get _initEditingState => widget.deleteDetection
  142. ? const TextEditingValue(
  143. text: ' ',
  144. selection: TextSelection.collapsed(offset: 2),
  145. )
  146. : const TextEditingValue(
  147. text: '',
  148. selection: TextSelection.collapsed(offset: 0),
  149. );
  150. late var _currentEditingState = _initEditingState.copyWith();
  151. @override
  152. TextEditingValue? get currentTextEditingValue {
  153. return _currentEditingState;
  154. }
  155. @override
  156. AutofillScope? get currentAutofillScope {
  157. return null;
  158. }
  159. @override
  160. void updateEditingValue(TextEditingValue value) {
  161. _currentEditingState = value;
  162. // Get input after composing is done
  163. if (!_currentEditingState.composing.isCollapsed) {
  164. final text = _currentEditingState.text;
  165. final composingText = _currentEditingState.composing.textInside(text);
  166. widget.onComposing(composingText);
  167. return;
  168. }
  169. widget.onComposing(null);
  170. if (_currentEditingState.text.length < _initEditingState.text.length) {
  171. widget.onDelete();
  172. } else {
  173. final textDelta = _currentEditingState.text.substring(
  174. _initEditingState.text.length,
  175. );
  176. widget.onInsert(textDelta);
  177. }
  178. // Reset editing state if composing is done
  179. if (_currentEditingState.composing.isCollapsed &&
  180. _currentEditingState.text != _initEditingState.text) {
  181. _connection!.setEditingState(_initEditingState);
  182. }
  183. }
  184. @override
  185. void performAction(TextInputAction action) {
  186. // print('performAction $action');
  187. widget.onAction(action);
  188. }
  189. @override
  190. void updateFloatingCursor(RawFloatingCursorPoint point) {
  191. // print('updateFloatingCursor $point');
  192. }
  193. @override
  194. void showAutocorrectionPromptRect(int start, int end) {
  195. // print('showAutocorrectionPromptRect');
  196. }
  197. @override
  198. void connectionClosed() {
  199. // print('connectionClosed');
  200. }
  201. @override
  202. void performPrivateCommand(String action, Map<String, dynamic> data) {
  203. // print('performPrivateCommand $action');
  204. }
  205. @override
  206. void insertTextPlaceholder(Size size) {
  207. // print('insertTextPlaceholder');
  208. }
  209. @override
  210. void removeTextPlaceholder() {
  211. // print('removeTextPlaceholder');
  212. }
  213. @override
  214. void showToolbar() {
  215. // print('showToolbar');
  216. }
  217. }