打造Flutter高效能富文字編輯器——協議篇
閒魚作為一個二手閒置交易平臺,賣家釋出商品產出優質的供給尤為重要;商品釋出器希望擁有富文字編輯能力,讓使用者簡單便捷的方式產出更加優質的內容;Flutter本身沒有富文字編輯器的能力的,只有最基礎的文字編輯器TextField;對於更加複雜的場景,比如支援自定義表情、主題、有序段落等能力,目前flutter元件是無法滿足我們的業務訴求,另外在互動體驗上與Native仍然存在一定的差距;為了解決業務中面臨的以上問題,我們決定設計並實現一個Flutter場景下高效能、可擴充套件的富文字編輯器。
富文字編輯器整體架構設計
首先我們來看一看整體的架構設計分層:
![](http://mdimg.wxwenku.com/getimg/ccdf080c7af7e8a10e9b88444af98393e9bc80ded7af6d8dcf55a56de134a85e164baac5fdd4cfb20f0ca7bd01e44309.jpg)
自下而上主要分四層:
-
1. 協議層:主要負責Model的定義、Selection描述、Commond事件邏輯處理,以及協議Normalizing校驗;
-
2. 能力擴充套件層:能力擴充套件層提供豐富的plugin能力,既有內建的plugin,如:純文字轉換,undo/redo等能力,同時也非常方便的支援業務層自定義的擴充套件,例如支援站外H5頁面展示的model to HTML的序列化plugin;
-
3. 渲染層:渲染層主要實現將富文字Model轉換成Flutter Widget渲染,以及游標、選區、ToolBar等計算和渲染,以及使用者手勢互動事件等;
-
4. 業務擴充套件層:在Mural的設計之初,可擴充套件就是設計過程中非常重要的一部分,我們為業務方提供了非常靈活、功能強大的擴充套件能力,通過自定義Node、Plugin、Normalizing,實現如自定義表情、主題、段落、語法高亮等能力;
協議層設計
富文字編輯器對大家來說並不陌生,發展至今,已經湧現出非常多有優秀的開源富文字編輯器;當我們想要做Flutter富文字協議的時候,第一個想法就是先了解優秀的開源富文字編輯器方案,避免閉門造車;
目前比較優秀的開源富文字編輯器,如CKEditor、Quill、Prosemirror、Draft、Slate等等;在瞭解和對比過後,我們決定使用Slate作為我們的富文字編輯器的協議;
why Slatejs
我們為什麼選擇 Slate ?
![](http://mdimg.wxwenku.com/getimg/6b990ce30fa9193e296dd37902816f4bb318d1a3395bc5a734807490ec99d400d9d04e4b1b849ed816c6a84e65cd9fab.jpg)
外掛是一等公民,能夠很好的滿足我們對於擴充套件性的要求;
S latjs 在設計上支援巢狀結構,可以滿足復 雜的業務場景;
與Dom相同的Data model,對於後面flutter渲染層的實現,也變得更加方便;
直觀的指令設計,能夠非常好的支援plugin的自定義擴充套件;
Slate在設計上,協議層與渲染層是有明確的核心劃分,這讓我們可以複用Slate協議層的設計,渲染層交給flutter來處理;
除了上面的原因,我們選擇Slate另外一個很重要的原因,就是它的單元測試覆蓋率和完整度,讓我們對它的穩定性更有信心;
Slate協議層設計
協議層的整體架構設計如下圖:
![](http://mdimg.wxwenku.com/getimg/ccdf080c7af7e8a10e9b88444af98393543cb8722c2ba4c04c3145baf3d9e2f5d677baddda854dd867290cb9c976aff1.jpg)
下面我們就以Slate為例,來看一看富文字編輯器的協議層設計,需要定義的核心概念和模組:
-
1. 巢狀Model定義;
-
2. 原子能力Operation設計;
-
3. 秩序維護者Normalizing的設計;
協議層設計——巢狀Model設計
Slate定義了三種類型的Node節點:
-
• Editor:包含整個文件內容的根節點;
-
• Element:在自定義域中擁有語義的容器節點;
-
• Text:包含文件文字的葉子節點;
![](http://mdimg.wxwenku.com/getimg/6b990ce30fa9193e296dd37902816f4b2d84c63353ce1793a8348479aa52d04fc9beeefed81217b20257c6ddfd184860.jpg)
Editor
Editor抽象介面定義如下:
Element
Element節點比較特殊,既是Ancentor節點,作為容器節點包含子節點;同時又是Descendant節點,可以作為其他容器節點的子節點存在。
-
• 塊(Blocks):Element預設為Block型別的節點,也就是獨立的一個段落;在Slate協議設計中,一個段落是不允許存在換行符的,當輸入換行符的時候,就會生成一個新的Block型別的Element;
-
• 行內(Inlines):同時Element也可以是Inline型別的節點,作為另外一個Element的巢狀子節點存在,作為行內元素渲染在一行;
-
• 空元素(Void):Element也可以是Void型別,這裡Void與HTML中Void的是同一個概念:如果某個Node為Void,則表示這個Node節點是不可編輯狀態,游標無法定位到節點內部,會被整體輸入和刪除;比如:@某個人、主題、富文字中的圖片或者影片等等;
Text
Text節點是樹中的最低階葉子節點,描述了文字內容以及其他自定義的渲染元素;所有的自定義屬性都包含在 properties
屬性中:
我們以下面這這段 富 文字為例:
![](http://mdimg.wxwenku.com/getimg/6b990ce30fa9193e296dd37902816f4bd4a71558453f21061fb233f65cacb82579625a0e2207311457eb03f387d1e133.jpg)
最終這樣一段富文字對應的Mode定義如下:
![](http://mdimg.wxwenku.com/getimg/ccdf080c7af7e8a10e9b88444af983932bbc7b1b707434dc39c7e0730bee706f1c3ec30e806a2d3edf2018692530f4ff.jpg)
可以看到,Model的樹形結構還是比較簡單的,所有的屬性都存放在properties欄位中,這也非常方便實現自定義擴充套件;Flutter渲染層根據Node節點的Type以及properties屬性,將富文字內容渲染到螢幕上;
協議層設計——原子能力Operation
接下來需要富文字Commond協議的設計,使用者的每一次的文字輸入、刪除、文字加粗、換行等操作都是一次Command指令;Slate抽象定義了九個最基本的Operations,協議層所有的Commond指令,最終在協議層,都會轉換成一個或者多個operation操作:
-
• insert_node:插入Node節點;
-
• insert_text:插入文字;
-
• merge_node:合併相同屬性的Node節點;
-
• move_node:移動Node;
-
• remove_node:刪除Node;
-
• remove_text:刪除文字;
-
• set_node:設定Node屬性;
-
• set_selection:設定Selection;
-
• split_node:拆分Node;
下面我們通過 對選中文字加粗
操作為例,來了解Slate協議層Commond的處理過程:
![](http://mdimg.wxwenku.com/getimg/6b990ce30fa9193e296dd37902816f4b7188ec353de7ab26fe7442f13481a92b18e3c895f69d4b13c8101b431d199c4f.jpg)
對選中文字加粗
這樣一個Commond,協議層會將這個Commond拆解成三個Opeartion:
-
•
split_node
:將一個Text Node拆分成三個Text Node; -
•
set_selection
:更新游標選擇區域Selection; -
•
set_node
:設定需要加粗Text Node節點properties的加粗屬性;
當一個Commond被協議層拆分成一個或者多個Opeartion執行之後,會執行一個非常重要的操作——Normalizing;
秩序維護者——Normalizing
每一次Command操作,絕大部分情況會對Model進行相應修改;我們需要一個秩序維護者——Normalizing,時刻保證對協議Model修改過之後,保持資料結構的正確性;
Slate定義了幾個基本的內建 Normalizing
規則:
![](http://mdimg.wxwenku.com/getimg/6b990ce30fa9193e296dd37902816f4b336f409050e84f0718f3318f6fe42062cf1e1b9b7ad253282e38b0ead678352f.jpg)
每一次Commond之後,Editor都會呼叫 normalizeNode
方法,在Normalizing的過程中,發現存在協議結構錯誤,需要進行錯誤修復;
Normalizing
的另一個強大之處在於,我們可以通過自定義 Normalizing
,新增自定義的校驗規則,實現自定義的需求;在後面的業務擴充套件章節會,我們會具體講解如何通過自定義Normalizing快速實現一個自定義主題的能力;
總結
目前Mural已經在閒魚商品釋出、商品詳情、訊息等場景落地,支援了自定義表情、主題等業務能力,使用者體驗方面也有了非常大的提升。
本次主要介紹了富文字編輯器Mural整體的架構設計以及協議層的設計;後續我們會系列文章的方式介紹Mural在渲染層的設計、自定義擴充套件設計,以及互動體驗、效能方面的優化實踐,敬請期待!
參考連結:
[1] Slate:http://github.com/ianstormtaylor/slate
- 詳解閒魚推薦系統(長文收藏)
- KUN 應用開發流程【實用教程】
- 大終端領域的新物種-KUN
- 大終端領域的新物種-KUN
- 一次夜間介面超時的解決過程
- 如何寫出有效的單元測試
- Kraken中事件通道原理分析
- 我在閒魚做搭建——魔魚搭投編輯器介紹
- Flutter富文字編輯器系列文章3——互動篇
- Flutter富文字編輯器系列文章3——互動篇
- 打造Flutter高效能富文字編輯器——渲染篇
- 節日獻禮:Flutter圖片庫重磅開源!
- 節日獻禮:Flutter圖片庫重磅開源!
- 關於閒魚測試資料構造,我有幾條心得
- 關於閒魚測試資料構造,我有幾條心得
- 打造Flutter高效能富文字編輯器——協議篇
- 打造Flutter高效能富文字編輯器——協議篇
- 閒魚前端技術體系的背後——魔魚(良心推薦,從思路到實踐)
- 閒魚如何保障交易鏈路質量
- Flutter 音影片開發的新思路