上一回咱们说到RichText是如何实现TextSpanWidgetSpan混排的,这次我们把RichTextTextField合并起来

这是我目前修改的文件,把rich前缀去掉就是原来的名字

.
├── cupertino
│   └── rich_text_selection.dart
├── material
│   └── rich_text_selection.dart
├── rich_editable.dart
├── rich_editable_text.dart
├── rich_text_field.dart
├── rich_text_painter.dart
└── rich_text_selection.dart

首先第一步是将原来的public类名加上Rich前缀,然后全部替换掉。虽然我这里用一句话就带过了,但是真正执行起来还需要小心谨慎,不然什么地方就可能编译不过去了,所以选个顺手的IDE比较重要。

接下来,我们从TextField开始追踪,发现最终执行edit相关逻辑的widget是editable_text.dart中的_Editable,于是我们的修改便从这里开始

7a75b10eef83781aeb1a2669769c3532.png

这里_Editable继承自LeafRenderObjectWidget,说明现在是不支持子widget的,所以我们把它替换成MultiChildrenRenderObjectWidget,并且收集WidgetSpan的children

7aef6be625e23b8ba3fef27338c48d79.png

bb36982ad0add824618c6893bb160994.png

b922c5fa3bf9821ecf3b98e767b0af39.png

editable_text.dart_Editable对应的RenderObject是editable.dart中的RenderEditable,我们把它替换成rich_editable.dart中的RichRenderEditable,然后主要修改RichRenderEditable

d7e8c61b96239aa2932b65291782f58f.png

f8fdf624e989213b7ac5c4a88ae73e94.png

现在,我们进入rich_editable.dart中修改RichRenderEditable

原来的RenderEditable是没有ContainerRenderObjectMixinRenderBoxContainerDefaultsMixin的,现在给RichRenderEditable加上

657b29a9a06493f8bb1149e0d4b1a674.png

15afc5e39bb7e0dd2a818fea16d19fd1.png

接下来进入layout流程

先来看一下RenderEditableperformLayout()

f5d8c1eed356ccba469d1392b67db1c5.png

在这个方法中,大致的流程是:首先对文本进行布局,就是红框中的代码,接下来就是计算当前widget的大小,最后是处理如果输入的文字需要滚动的时候的偏移量。

这段代码中有段注释,翻译过来是:我们在这里获取_textPainter.size,因为下一行给size赋值这行代码将触发我们验证固有大小的方法,这将更改_textPainter的布局,因为固有大小的计算具有破坏性,这意味着如果以后在此方法中使用_textPainter的属性,我们将获得不同的结果。其他_textPainter状态(例如didExceedMaxLines)也将受到影响,尽管我们目前不在这里使用。//参见具有类似问题的RenderParagraph。

说的是先调用final Size textPainterSize = _textPainter.size,是因为size = Size(width, contraints.contrainHeight(_preferredHeight(contraints.maxWidth)))这行代码会触发computeMin/MaxIntrinsicHeight/Widget这一系列方法,这些方法里面可能会重新layout一遍文本,所以之后获取的_textPainter.size的值可能会变化,因此先保存起来

我们把RenderParagraph的layout逻辑复制到RichRenderEditable,如下

b2330df3e10094c7bf134219ad2fb1b4.png

红框中的三个方法我们在上篇文章中已经详细阐述过了

接着是paint流程 先看RenderEditablepaint()方法

6fac6967724ee2b787ca96e0dae5ea64.png

如果文本需要滚动,则先裁剪部分,然后_paintContents,如果不需要滚动,则直接_paintContents,因此我们去看_paintContents

d5835cf86a4135f50fb797e900065b4f.png

红框中的内容就是在绘制文本,这里也是我们要替换的地方,替换成下面这个方法,也同样是RenderParagraph中的逻辑

12dfc336900b495b1a5c68520ad64a4f.png

到这里,paint流程就替换完毕了。

接下来,再把computeMin/MaxIntrinsicHeight/Widget这一系列方法的逻辑融合一下就可以了

在这个过程中,我遇到了两个头疼的问题,一个是当RichTextField重绘的时候,有可能会崩溃,是因为没有重新_textPainter.layout引起的,具体的原因不细说了,大家如果有兴趣的话可以看我的源码,里面有个叫rich_text_painter.dart的文件,看了应该就是明白了。另一个问题是TextSpan和WidgetSpan总是重叠在一起,似乎_textPainter.layout在高度方面不起作用一样,这个问题我找了很久,最终发现是在EditableText中设置了一个strutStyle的默认值,这个默认值会固定文本高度。虽然这两个问题被我轻描淡写的几句话带过,但是实际过程中花的时间并不少,文章只是对逻辑融合的大体框架做一个介绍,这种细枝末节的问题就不再赘述了,但是我相信这种问题会随着以后的深入越来越多。

最后,我们来看下效果

我在main.dart中写了这样的代码,代码仅做demo,其中的细节问题请忽略

fb9e09269f158d551f5c139319828e01.png

32fa3561c15ca33ac3c0c311b85440d5.png

大体意思是我输入一串字符,然后显示出来,接着后面显示一个小闹钟,接着是一段666666,最后是一个大一点的小图标

在我输入一串“good”之后,显示效果如下:

c8083b464cfc03643069ba0e7c914b43.png

打开Flutter Inspector看一下边界

2ef4e30c6082617ddbebbd9b0d69c031.png

目前的效果就是这样,还仅仅是可以显示WidgeSpan,还不能做到输入删除等等操作。我们慢慢来。

接下来,我们来看看如何处理光标的问题

github地址:https://github.com/Fearimdly/rich_text_field

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐