上一回咱们说到RichText
是如何实现TextSpan
和WidgetSpan
混排的,这次我们把RichText
和TextField
合并起来
这是我目前修改的文件,把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
,于是我们的修改便从这里开始
![8666768af16f3d5b470ec9e407606e25.png](https://i-blog.csdnimg.cn/blog_migrate/7f899cdde2afe42fc439be23286904e8.png)
这里_Editable
继承自LeafRenderObjectWidget
,说明现在是不支持子widget的,所以我们把它替换成MultiChildrenRenderObjectWidget
,并且收集WidgetSpan的children
![f96c2a13b78fbffc81ceb0efd956840c.png](https://i-blog.csdnimg.cn/blog_migrate/90db8e658e0e53c63c13927baec158a5.jpeg)
![933985ca146598af172a73c291d12a45.png](https://i-blog.csdnimg.cn/blog_migrate/2e31667c18662428c5aceb60c0c6121f.jpeg)
![6c2577b9b3bc82af19efc66c3f8b9158.png](https://i-blog.csdnimg.cn/blog_migrate/dc49a4780285299c57bcb7f3e028174d.jpeg)
editable_text.dart
中_Editable
对应的RenderObject是editable.dart
中的RenderEditable
,我们把它替换成rich_editable.dart
中的RichRenderEditable
,然后主要修改RichRenderEditable
![5eabcddde4985aa39d523457c102a1ae.png](https://i-blog.csdnimg.cn/blog_migrate/3c83680eec277b558f7868a7a761531b.png)
![9e689aa09d7986db6607e7cf834ef5ba.png](https://i-blog.csdnimg.cn/blog_migrate/3347d10018944d15d8218908ef5b371a.png)
现在,我们进入rich_editable.dart
中修改RichRenderEditable
原来的RenderEditable
是没有ContainerRenderObjectMixin
和RenderBoxContainerDefaultsMixin
的,现在给RichRenderEditable
加上
![a443b92871410af8d1520ff8bff7a80c.png](https://i-blog.csdnimg.cn/blog_migrate/25f99222b31d40b0a2705d47565d8656.png)
![65b4ef3ddc7284abb0d06ec4955442aa.png](https://i-blog.csdnimg.cn/blog_migrate/ace1374bb5811a9942e3bee08f3b382e.jpeg)
接下来进入layout流程
先来看一下RenderEditable
的performLayout()
![a068fe3699cdea061f13445622faf44c.png](https://i-blog.csdnimg.cn/blog_migrate/43caa4dcdaf6525ce7819917c7a996de.jpeg)
在这个方法中,大致的流程是:首先对文本进行布局,就是红框中的代码,接下来就是计算当前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
,如下
![9a153b86a3d3faf5847c90a8914acc6d.png](https://i-blog.csdnimg.cn/blog_migrate/fd7d250098348cf0f77e95c8ea86a923.jpeg)
红框中的三个方法我们在上篇文章中已经详细阐述过了
接着是paint流程 先看RenderEditable
的paint()
方法
![3c74d12d76b0dbd291f17da367ddd529.png](https://i-blog.csdnimg.cn/blog_migrate/f171e34455bed64b5af7d10ab63b0dad.jpeg)
如果文本需要滚动,则先裁剪部分,然后_paintContents
,如果不需要滚动,则直接_paintContents
,因此我们去看_paintContents
![b4444198a6ffc15ac46e426c78a38042.png](https://i-blog.csdnimg.cn/blog_migrate/2c1ceccf220aabc6e7e92ebd7c3a5f5c.jpeg)
红框中的内容就是在绘制文本,这里也是我们要替换的地方,替换成下面这个方法,也同样是RenderParagraph
中的逻辑
![8c6454f48f62ce0ec893859bf4d5b8d1.png](https://i-blog.csdnimg.cn/blog_migrate/fa80c0861d722d6747b7fd42de5285aa.jpeg)
到这里,paint流程就替换完毕了。
接下来,再把computeMin/MaxIntrinsicHeight/Widget
这一系列方法的逻辑融合一下就可以了
在这个过程中,我遇到了两个头疼的问题,一个是当RichTextField重绘的时候,有可能会崩溃,是因为没有重新_textPainter.layout引起的,具体的原因不细说了,大家如果有兴趣的话可以看我的源码,里面有个叫rich_text_painter.dart的文件,看了应该就是明白了。另一个问题是TextSpan和WidgetSpan总是重叠在一起,似乎_textPainter.layout在高度方面不起作用一样,这个问题我找了很久,最终发现是在EditableText中设置了一个strutStyle的默认值,这个默认值会固定文本高度。虽然这两个问题被我轻描淡写的几句话带过,但是实际过程中花的时间并不少,文章只是对逻辑融合的大体框架做一个介绍,这种细枝末节的问题就不再赘述了,但是我相信这种问题会随着以后的深入越来越多。
最后,我们来看下效果
我在main.dart中写了这样的代码,代码仅做demo,其中的细节问题请忽略
![c364eba2753cf0574af4fbcd2c98df51.png](https://i-blog.csdnimg.cn/blog_migrate/8dfaf75335511a07034a4ce883754d01.jpeg)
![a02b53504ef817d0c1ca8852b3458f99.png](https://i-blog.csdnimg.cn/blog_migrate/a1b3657dcb7235fd2406bb9219b9df4d.jpeg)
大体意思是我输入一串字符,然后显示出来,接着后面显示一个小闹钟,接着是一段666666,最后是一个大一点的小图标
在我输入一串“good”之后,显示效果如下:
![ffe8cb97f721e40f6e1272993257f978.png](https://i-blog.csdnimg.cn/blog_migrate/e6b11aa2477aad5e165645fe4b645fd6.jpeg)
打开Flutter Inspector看一下边界
![5f0d7e8ca6d32cc89e4c8166e057f425.png](https://i-blog.csdnimg.cn/blog_migrate/8ed09d294ce631c219edfd72af60de07.jpeg)
目前的效果就是这样,还仅仅是可以显示WidgeSpan,还不能做到输入删除等等操作。我们慢慢来。
接下来,我们来看看如何处理光标的问题
github地址:https://github.com/Fearimdly/rich_text_field