扣丁書屋

打造Flutter高性能富文本編輯器——協議篇

閑魚作為一個二手閑置交易平臺,賣家發布商品產出優質的供給尤為重要;商品發布器希望擁有富文本編輯能力,讓用戶簡單便捷的方式產出更加優質的內容;Flutter本身沒有富文本編輯器的能力的,只有最基礎的文本編輯器TextField;對于更加復雜的場景,比如支持自定義表情、主題、有序段落等能力,目前flutter組件是無法滿足我們的業務訴求,另外在交互體驗上與Native仍然存在一定的差距;為了解決業務中面臨的以上問題,我們決定設計并實現一個Flutter場景下高性能、可擴展的富文本編輯器。

富文本編輯器整體架構設計

首先我們來看一看整體的架構設計分層:

自下而上主要分四層:

  1. 1. 協議層:主要負責Model的定義、Selection描述、Commond事件邏輯處理,以及協議Normalizing校驗;
  2. 2. 能力擴展層:能力擴展層提供豐富的plugin能力,既有內置的plugin,如:純文本轉換,undo/redo等能力,同時也非常方便的支持業務層自定義的擴展,例如支持站外H5頁面展示的model to HTML的序列化plugin;
  3. 3. 渲染層:渲染層主要實現將富文本Model轉換成Flutter Widget渲染,以及光標、選區、ToolBar等計算和渲染,以及用戶手勢交互事件等;
  4. 4. 業務擴展層:在Mural的設計之初,可擴展就是設計過程中非常重要的一部分,我們為業務方提供了非常靈活、功能強大的擴展能力,通過自定義Node、Plugin、Normalizing,實現如自定義表情、主題、段落、語法高亮等能力;

協議層設計

富文本編輯器對大家來說并不陌生,發展至今,已經涌現出非常多有優秀的開源富文本編輯器;當我們想要做Flutter富文本協議的時候,第一個想法就是先了解優秀的開源富文本編輯器方案,避免閉門造車;

目前比較優秀的開源富文本編輯器,如CKEditor、Quill、Prosemirror、Draft、Slate等等;在了解和對比過后,我們決定使用Slate作為我們的富文本編輯器的協議;

why Slatejs

我們為什么選擇Slate?

插件是一等公民,能夠很好的滿足我們對于擴展性的要求;

Slatjs在設計上支持嵌套結構,可以滿足復雜的業務場景;

與Dom相同的Data model,對于后面flutter渲染層的實現,也變得更加方便;

直觀的指令設計,能夠非常好的支持plugin的自定義擴展;

Slate在設計上,協議層與渲染層是有明確的核心劃分,這讓我們可以復用Slate協議層的設計,渲染層交給flutter來處理;

除了上面的原因,我們選擇Slate另外一個很重要的原因,就是它的單元測試覆蓋率和完整度,讓我們對它的穩定性更有信心;

Slate協議層設計

協議層的整體架構設計如下圖:

下面我們就以Slate為例,來看一看富文本編輯器的協議層設計,需要定義的核心概念和模塊:

  1. 1. 嵌套Model定義;
  2. 2. 原子能力Operation設計;
  3. 3. 秩序維護者Normalizing的設計;

協議層設計——嵌套Model設計

Slate定義了三種類型的Node節點:

  • ? Editor:包含整個文檔內容的根節點;
  • ? Element:在自定義域中擁有語義的容器節點;
  • ? Text:包含文檔文本的葉子節點;

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屬性中:

我們以下面這這段富文本為例:

最終這樣一段富文本對應的Mode定義如下:

可以看到,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的處理過程:

對選中文本加粗這樣一個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規則:

每一次Commond之后,Editor都會調用normalizeNode方法,在Normalizing的過程中,發現存在協議結構錯誤,需要進行錯誤修復;

Normalizing的另一個強大之處在于,我們可以通過自定義Normalizing,添加自定義的校驗規則,實現自定義的需求;在后面的業務擴展章節會,我們會具體講解如何通過自定義Normalizing快速實現一個自定義主題的能力;

總結

目前Mural已經在閑魚商品發布、商品詳情、消息等場景落地,支持了自定義表情、主題等業務能力,用戶體驗方面也有了非常大的提升。

本次主要介紹了富文本編輯器Mural整體的架構設計以及協議層的設計;后續我們會系列文章的方式介紹Mural在渲染層的設計、自定義擴展設計,以及交互體驗、性能方面的優化實踐,敬請期待!

參考鏈接:

[1] Slate:https://github.com/ianstormtaylor/slate


https://mp.weixin.qq.com/s/q7md3MnsW8LqU9-21Xn81Q

最多閱讀

如何有效定位Flutter內存問題? 2年以前  |  13007次閱讀
Flutter的手勢GestureDetector分析詳解 3年以前  |  9285次閱讀
Flutter插件詳解及其發布插件 3年以前  |  8228次閱讀
在Flutter中添加資源和圖片 4年以前  |  6341次閱讀
發布Flutter開發的iOS程序 4年以前  |  5500次閱讀
Flutter 狀態管理指南之 Provider 3年以前  |  5459次閱讀
Flutter for Web詳細介紹 3年以前  |  5259次閱讀
在Flutter中發起HTTP網絡請求 4年以前  |  4995次閱讀
使用Inspector檢查用戶界面 4年以前  |  4874次閱讀
Flutter路由詳解 3年以前  |  4472次閱讀
Flutter Widget框架概述 4年以前  |  3978次閱讀
為Flutter應用程序添加交互 4年以前  |  3964次閱讀
JSON和序列化 4年以前  |  3819次閱讀
推薦5個Flutter重磅開源項目! 2年以前  |  3736次閱讀
Flutter框架概覽 4年以前  |  3667次閱讀
處理文本輸入 4年以前  |  3586次閱讀
使用自定義字體 4年以前  |  3549次閱讀

手機掃碼閱讀
18禁止午夜福利体验区,人与动人物xxxx毛片人与狍,色男人窝网站聚色窝
<蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <文本链> <文本链> <文本链> <文本链> <文本链> <文本链>