文本編輯在Flutter中的工作方式

本文翻譯自
原文地址:How text editing works internally in Flutter
原作者:Suragch

【個人閱讀心得】這篇文章由表及里介紹了Flutter中文本繪制和編輯的處理流程篮洁,講了各個層級所負責的內(nèi)容裳扯,能夠幫助讀者對文本處理有個大致認知坯屿,原來文本繪制和編輯在底層是很復雜的系統(tǒng)抓韩,包括如何和系統(tǒng)鍵盤打交道,怎樣布局輸入框里的內(nèi)容羽峰,以及怎么處理和底層框架交互等趟咆。想要更深入的理解添瓷,可以一步一步按照文章中介紹的層級去閱讀源碼梅屉,此外文中的引文Flutter文本渲染對渲染過程進行了詳細講解,非常推薦延伸閱讀一下鳞贷。得益于這篇文章中的Text and selection一節(jié)坯汤,我們的小伙伴優(yōu)化了TextField輸入精度計算,解決了中文輸入最大限制個數(shù)計算不準確的困擾搀愧。此外惰聂,翻譯不準確的地方歡迎大家指正。


寫給那些喜歡鉆研細節(jié)的人咱筛。

hellowo01.png

由于Flutter僅支持水平文本布局搓幌,因此在為了給傳統(tǒng)蒙古文字創(chuàng)建垂直布局的widget時,我不得不深入研究文本渲染系統(tǒng)的底層源碼迅箩。在本文中溉愁,我將分享我發(fā)現(xiàn)的,關(guān)于Flutter中文本編輯widget工作方式的知識饲趋。

文本渲染(Text rendering)

通常我們看到的關(guān)于Text widget是這樣子的:

Text(
  'Hello world',
  style: TextStyle(fontSize: 30),
),

然而拐揭,它的下面還有很多層級。

Widget層(Widgets layer)

當你使用Textwidget時奕塑,它實際創(chuàng)建的是一個RichText widget堂污。

text_richtext02.png

與Text(將String作為數(shù)據(jù)參數(shù))不同,RickText采用TextSpan(更準確的說是InlineSpan, 稍后會介紹更多相關(guān)內(nèi)容)龄砰,TextSpan需要String和一個TextStyle作為參數(shù)盟猖,因此在Text創(chuàng)建RichText之前讨衣,它需要你提供任意TextStyle并將其與APP默認的TextStyle組合,該默認TextStyle是從BuildContext中獲取的式镐,下面是上圖的更詳細版本:

richtext03.png

由于Text僅采用單個字符串和style樣式作為參數(shù)值依,因此它在TextSpan中提供給RichText的字符串只有一種樣式,但是TextSpan很有趣碟案,就像多子節(jié)點widget一樣愿险,它能將更多的文本對象作為子節(jié)點。這意味著你可以使用它來構(gòu)建整個文本樹价说,其中每個節(jié)點都可以有各自的字符串和樣式辆亏,

textspan04.png

但是這很難推導出來,因為通常你只有一個父TextSpan和一堆子節(jié)點

textspan05.png

你可以將TextSpan對象傳遞給Text.rich構(gòu)造函數(shù)鳖目,或者直接傳遞給TextRich widget扮叨,但是,如果將其直接傳遞給TextRich widget的話领迈,該樣式則不會和build context中的默認樣式組合彻磁。

閱讀掌握Flutter中的文本樣式一文,以獲得使用TextSpan設(shè)置文本樣式的實際示例狸捅。

RichText本身是MultiChildRenderObjectWidget的子類衷蜓,RichText內(nèi)部有多個子節(jié)點的原因是,你可以在一行的文本內(nèi)部散布其他的widget尘喝,上面的圖片顯示的是一個TextSpan樹磁浇,但實際上RichText內(nèi)置的是InlineSpan,InlineSpan既可以是TextSpan也可以是WidgetSpan朽褪,它是他們的父類置吓。出于本文的目的,我將僅處理TextSpan缔赠。

渲染層(Rendering layer)

Widget只是最終用于創(chuàng)建RenderObject的藍圖衍锚,RichText創(chuàng)建的渲染對象叫做RenderParagraph

renderParagraph06.png

RenderParagraph會計算文本的大小,當然也會處理其他事情嗤堰,比如命中測試以及它的任何子widget的布局戴质。

繪制層(Painting layer)

RenderParagraph并不直接繪制文本,而是創(chuàng)建一個TextPainter來管理這些工作梁棠。

textpainter07.png

與他的名字相反置森,TextPainter實際上也不繪制文本,但是它會管理周圍所有繪制相關(guān)的工作符糊,包括要繪制的畫布Canvas以及從TextSpan樹上提取樣式和字符串凫海。

此外,TextSpan還可以處理插入符的的位置男娄,這對于Text widget不那么重要行贪,但是對于接下來要講的文本編輯將會很重要漾稀。

基礎(chǔ)層(Foundation layer)

每個人都以為穿過重重困難,即將真正繪制文本的時候建瘫,實際則不止于此崭捍。

在Flutter框架的最底層,你將會看到一個ParagraphBuilder和一個Paragraph對象啰脚,TextPainter內(nèi)部會創(chuàng)建ParagraphBuilder殷蛇,而ParagraphBuilder又會用于生成Paragraph。

paragraph08.png

ParagraphBuilder中包含一點邏輯橄浓,但是Paragraph幾乎不包含任何邏輯粒梦,這兩個對象將大部分工作傳給Flutter engine。

Flutter引擎層(Flutter engine layer)

Flutter引擎是用C/C++寫的荸实,因此對于使用Dart的Flutter開發(fā)者匀们,通常不會直接使用,處理文本繪制的庫叫做LibTxt准给。

libtxt09.png

LibTxt當前已經(jīng)被Skia的SkParagraph模塊替換泄朴,可以關(guān)注這個issue跟蹤進展。

總結(jié)

下面的圖片展示了渲染工作的所有圖層

layer10.png

閱讀Flutter文本渲染這篇文章露氮,深入了解以上內(nèi)容祖灰。【譯者按:強烈推薦閱讀沦辙,之后也會翻譯出來或者總結(jié)學習筆記】

文本編輯(Text editing)

我的一個問題是文本渲染和文本編輯在架構(gòu)層上有多少是共享的夫植,答案是從TextPainter向下的所有內(nèi)容,這就很方便了油讯,意味著我們只需要了解它上層的內(nèi)容。

Material和Cupertino層(Material and Cupertino layers)

當你想要在Flutter中輸入和編輯的時候延欠,TextField可能是用戶首先想到的widghet陌兑。

TextField(
  decoration: InputDecoration(
    hintText: 'Search',
  ),
),

然而,TextField只是組成Material庫的一部分由捎,這是widget層更上的一層兔综,與之對應(yīng)的在Cupertino庫中widget稱為CupertinoTextField

textfield11.png

假如你使用TextField.adaptive構(gòu)造函數(shù)的話,它將會在iOS和macOS創(chuàng)建CupertinoTextField狞玛,但是在其它平臺創(chuàng)建TextField软驰。

textfieldadaptive12.png

你可能也想知道TextFormField,但這僅是一個TextField心肪,包裝了一些邏輯锭亏,使保存和驗證更加容易。據(jù)我所知硬鞍,沒有CupertinoTextFormField慧瘤。

與Text這種無狀態(tài)(stateless)widget不同戴已,TextField和CupertinoTextField是有狀態(tài)的(statefull)widget。這是因為他們需要持續(xù)跟蹤諸如TextEditingController锅减、焦點(focus)糖儡、鼠標懸停(mouse hovering)、手勢(gestures)等之類的東西怔匣。

無論使用哪種握联,TextField和Cupertino TextField都將最終創(chuàng)建一個EditableText widget。

editabletext13.png

然后我們就進入到了widget層每瞒。

Widget層(Widgets layer)

在這一層拴疤,你將不會再具有某些上一層提供的功能,例如占位符文本和標簽独泞,不過這里有很多其他屬性呐矾。

EditableText管理文本和選擇區(qū)域,與鍵盤懦砂、光標和滾動事件進行通信蜒犯。

文本和選擇區(qū)域(Text and selection)

你可能之前使用過TextEditingController,盡管它本身并不是一個widget荞膘,但是它屬于widget層罚随,用來處理EditableText。繼承自ValueNotifier羽资,當TextEditingValue發(fā)生改變的時候淘菩,通知它的監(jiān)聽者。

TextEditingValue對象有三個部分組成屠升,文本潮改、選擇區(qū)域和編輯區(qū)域(text, selection, and composing)。

texteditingvalue14.png
  • text:這里是用戶已經(jīng)輸入的任何字符串腹暖。
  • selection:這是一個TextSelection對象汇在,通過它你可以知道當前所選擇的光標位置和選擇范圍,除了選擇的開始和結(jié)束值外脏答,TextSelection也包含文本方向和光標在換行處的精確位置糕殉。
  • composing:這是你正在編輯單詞的TextRange(僅包含開始和結(jié)束的偏移量)。你知道當你在輸入某些內(nèi)容的時候殖告,鍵盤會提出一些建議嗎阿蝶?如果你選擇鍵盤建議,則帶下劃線的文本將會被你選擇的鍵盤建議文本替換掉黄绩。
composing15.png

與系統(tǒng)鍵盤進行通信(Communicating with the system keyboard)
當彈出系統(tǒng)軟鍵盤時羡洁,它屬于底層系統(tǒng),F(xiàn)lutter的Editable widget需要一種與該系統(tǒng)通信的方式宝与,無論是獲取信息(用戶使用鍵盤輸入)還是發(fā)送信息(如更改選擇區(qū)域焚廊,或者讓鍵盤消失)冶匹。

下面是架構(gòu)示意圖:


keyboard16.png
  • EditableText實現(xiàn)TextInputClient,當用戶使用系統(tǒng)鍵盤輸入文本時咆瘟,它可以接收更新嚼隘。
  • TextInputClient創(chuàng)建一個TextInputConnection對象,它是一個用于將消息發(fā)送給系統(tǒng)鍵盤的接口袒餐。
  • 異步消息傳遞全部通過底層的TextInput服務(wù)飞蛹,該服務(wù)通過平臺channel與底層系統(tǒng)進行通信。
  • 在native側(cè)灸眼,plugin插件將會處理往返于TextInput的消息卧檐,每個平臺都有自己唯一的插件,用于Android系統(tǒng)的Android插件焰宣,用于iOS系統(tǒng)的iOS插件霉囚,該插件將與本機系統(tǒng)輸入控件進行通信(比如鍵盤)。
  • Flutter端和插件端各自維護自己的TextEditingValue版本匕积,并且需要保持同步盈罐。

光標(Cursor)
EditableText使用了兩個AnimationController對象為光標設(shè)置動畫。一個用于標準的光標閃爍(通過動畫的透明度實現(xiàn))闪唆,另一個用于浮動光標盅粪,這時iOS系統(tǒng)中的標準樣式,你可以在下面視頻中看到

cursor17.png

滾動(Scrolling)
在EditableText的build方法中悄蕾,內(nèi)容由Scrollable widget進行包裹了一層票顾,這允許文本垂直滾動以顯示多行文本,水平滾動以顯示單行文本帆调。傳入ScrollController可以進行進一步的定制滾動行為奠骄。

創(chuàng)建渲染對象(Creating a render object)
EditableText創(chuàng)建一個稱為RenderEditable渲染對象。

rendereditable18.png

渲染層(Rendering layer)

RenderEditable管理命中測試贷帮、文本戚揭、和光標或者選擇區(qū)域,以及從字符到字符撵枢、單詞到單詞的移動。最后精居,它使用TextPainter繪制文本和選擇區(qū)域锄禽。

textpainter19.png

這樣就完成了和開頭內(nèi)容的呼應(yīng)!如你所見靴姿,TextPainter創(chuàng)建了一個Paragraph對象沃但,該對象將將繪制工作傳給Flutter引擎。

總覽

下圖是上面內(nèi)容的總結(jié):

textsummary20.png

你可以看看它與更大的Flutter架構(gòu)是如何匹配的:

flutterarchitecural21.png

最后

很高興你能看完佛吓,假如發(fā)現(xiàn)任何錯誤宵晚,請在評論中告訴我垂攘,這樣其他讀者也能看到哈打。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末毫捣,一起剝皮案震驚了整個濱河市沼瘫,隨后出現(xiàn)的幾起案子智哀,更是在濱河造成了極大的恐慌帐萎,老刑警劉巖宠互,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件壁袄,死亡現(xiàn)場離奇詭異寓调,居然都是意外死亡铝侵,警方通過查閱死者的電腦和手機灼伤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來咪鲜,“玉大人狐赡,你說我怎么就攤上這事∨北” “怎么了颖侄?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長隆敢。 經(jīng)常有香客問我发皿,道長,這世上最難降的妖魔是什么拂蝎? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任穴墅,我火速辦了婚禮,結(jié)果婚禮上温自,老公的妹妹穿的比我還像新娘玄货。我一直安慰自己,他們只是感情好悼泌,可當我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布松捉。 她就那樣靜靜地躺著,像睡著了一般馆里。 火紅的嫁衣襯著肌膚如雪隘世。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天鸠踪,我揣著相機與錄音丙者,去河邊找鬼。 笑死营密,一個胖子當著我的面吹牛械媒,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼纷捞,長吁一口氣:“原來是場噩夢啊……” “哼痢虹!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起主儡,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤奖唯,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后缀辩,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體臭埋,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年臀玄,在試婚紗的時候發(fā)現(xiàn)自己被綠了瓢阴。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡健无,死狀恐怖荣恐,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情累贤,我是刑警寧澤叠穆,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布,位于F島的核電站臼膏,受9級特大地震影響硼被,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜渗磅,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一嚷硫、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧始鱼,春花似錦仔掸、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至会烙,卻和暖如春负懦,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背柏腻。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工密似, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人葫盼。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像村斟,于是被迫代替她去往敵國和親贫导。 傳聞我的和親對象是個殘疾皇子抛猫,可洞房花燭夜當晚...
    茶點故事閱讀 42,722評論 2 345

推薦閱讀更多精彩內(nèi)容