幕思城>電商行情>客服>客服知識>Flutter富文本編輯器系列文章3——交互篇

    Flutter富文本編輯器系列文章3——交互篇

    2022-11-29|10:30|發(fā)布在分類 / 客服知識| 閱讀:129

    之前的系列文章介紹了渲染層的實(shí)現(xiàn),大家可以知道Mural是基于Flutter TextField進(jìn)行渲染層的設(shè)計(jì)與實(shí)現(xiàn),然后對其底層的渲染邏輯進(jìn)行改造,從而對富文本編輯能力進(jìn)行支持。但是我們在改造過程中發(fā)現(xiàn),其實(shí)在交互方面,F(xiàn)lutter有很多相比起Native缺失的功能,本文會(huì)圍繞放大鏡模式選區(qū)反向選擇兩個(gè)比較重要的交互點(diǎn)來展開說明。

    本文將會(huì)以官方代碼來進(jìn)行講解,因?yàn)檫@些優(yōu)化思路是普適通用的,不與富文本耦合的。

    放大鏡模式

    背景與現(xiàn)狀

    對于原生控件,不管是Android側(cè)的EditText,還是iOS側(cè)的UITextField,都是默認(rèn)支持放大鏡模式的。將用戶進(jìn)行文本選擇時(shí),用戶可以通過放大鏡來進(jìn)行精確的光標(biāo)定位和選區(qū)移動(dòng)。如下圖所示:

    這無疑會(huì)對用戶體驗(yàn)起到很大的改善作用,但是目前Flutter提供的TextField控件里并沒有對該模式進(jìn)行支持,早在2017年就有人提出了相關(guān)issue。Mural的UI渲染層和Flutter TextField除了在文本的渲染機(jī)制上不同之外,其他的交互邏輯是基本保持一致的。所以我們決定模擬Android和iOS雙端的放大鏡交互,在Flutter文本編輯器中進(jìn)行放大鏡模式的支持。

    交互分析

    眾所周知,Android和iOS有著不同的設(shè)計(jì)與交互規(guī)范,文本編輯控件就是一個(gè)很好的例子,不過他們的交互也有相似的地方,我們將會(huì)求同存異,盡量滿足雙端的設(shè)計(jì)交互規(guī)范。一般來說,放大鏡控件通常在兩個(gè)場景會(huì)出現(xiàn),一就是光標(biāo)定位時(shí),二就是在選區(qū)移動(dòng)時(shí)。我們接下來對這兩個(gè)場景進(jìn)行分析:

    光標(biāo)定位

    對于Android來說,點(diǎn)擊EditText進(jìn)行聚焦之后,通常光標(biāo)下方會(huì)出現(xiàn)一個(gè)把手:通過拖曳這個(gè)把手來進(jìn)行光標(biāo)的定位,而放大鏡隨著拖曳開始而出現(xiàn),拖曳結(jié)束消失。如圖所示:

    對于iOS來說,點(diǎn)擊UITextField進(jìn)行聚焦之后,長按,光標(biāo)會(huì)變成一個(gè)浮動(dòng)游標(biāo),然后可以直接進(jìn)行拖曳,便可以進(jìn)行光標(biāo)的定位,而放大鏡隨著拖曳開始而出現(xiàn),拖曳結(jié)束消失。如圖所示:

    對于Android來說,選區(qū)移動(dòng)和光標(biāo)定位非常相似,通過雙擊或者長按EditText可以選中最近的詞,然后選區(qū)的左右兩端會(huì)出現(xiàn)兩個(gè)把手,以及選區(qū)上方會(huì)出現(xiàn)一個(gè)Toolbar,可以對選中的文本進(jìn)行復(fù)制剪切等操作。拖拽這兩個(gè)把手就可以進(jìn)行選區(qū)的移動(dòng),拖曳開始時(shí)Toolbar會(huì)消失,放大鏡出現(xiàn),拖曳結(jié)束時(shí)放大鏡消失,Toolbar重新出現(xiàn)。

    iOS和Android的選區(qū)移動(dòng)交互比較相似,不同的是,iOS只能通過雙擊UITextField才能選中最近的詞,因?yàn)殚L按手勢用于光標(biāo)定位。以及把手的樣式不一樣。

    代碼實(shí)現(xiàn)

    通過以上的分析不難發(fā)現(xiàn),放大鏡有三個(gè)特點(diǎn):

    在內(nèi)容上,放大鏡會(huì)以光標(biāo)或是單邊選區(qū)為中心,展示固定尺寸的區(qū)域內(nèi)的屏幕上的內(nèi)容。

    在位置上,放大鏡會(huì)浮動(dòng)在光標(biāo)或是單邊選區(qū)之上,保持固定的距離。

    在邏輯上,放大鏡一般隨著拖曳開始而出現(xiàn),拖曳結(jié)束而消失,以及選區(qū)移動(dòng)場景下還需要進(jìn)行Toolbar的隱藏和恢復(fù),但是雙端有一些不同的交互。

    其實(shí)還有一些其他的細(xì)節(jié)交互,比如iOS UITextField放大鏡其實(shí)是展示在觸摸點(diǎn)上方而并非光標(biāo)和單邊選區(qū)上方,并且在觸摸區(qū)域和光標(biāo)沒有重合的時(shí)候,放大鏡就會(huì)消失等。不過此處暫時(shí)以以上三個(gè)特點(diǎn)為思路來進(jìn)行實(shí)現(xiàn),后續(xù)會(huì)對沒有對齊的交互進(jìn)行進(jìn)一步的優(yōu)化與對齊。以上三個(gè)特點(diǎn)可以轉(zhuǎn)化為三個(gè)問題與解決方案:

    1.如何把放大鏡定位在光標(biāo)或單邊選區(qū)上方?

    Flutter還提供了一組叫做CompositedTransformFollower 與 CompositedTransformTarget的組件,他們通過同一個(gè)LayerLink來讓Follower與Target的相對位置保持一致,即Target的位置移動(dòng)時(shí),F(xiàn)ollower也會(huì)跟著一起移動(dòng)。而且TextField中已經(jīng)存在startHandleLayerLink和endHandleLayerLink用于展示選區(qū)的操作把手組件,所以我們直接使用這兩個(gè)LayerLink,便可以讓放大鏡吸附在光標(biāo)上方。定位代碼如下:

    可以看到,我們需要判定是把放大鏡吸附到左邊的把手上,還是右邊的把手上,而當(dāng)選區(qū)為光標(biāo)模式時(shí),光標(biāo)屬于左邊的把手。這個(gè)問題我們可以在TextSelectionOverlay中的用于展示把手組件的TextSelectionHandleOverlay組件中解決。在把手組件的_handleDragStart中把當(dāng)前的currentTextSelectionHandleType更新為當(dāng)前正在交互的把手類型就可以實(shí)現(xiàn)。偽代碼在后續(xù)介紹邏輯部分一并給出。

    可以看到Follower組件中還有一個(gè)offset參數(shù),這個(gè)用于控制Target和Follower的相對位置。可以看到我們向左偏移了半個(gè)放大鏡寬度,向上偏移了放大鏡高度再加上一個(gè)距離。這樣就可以讓放大鏡懸浮在光標(biāo)或者單邊選區(qū)正上方。

    2.如何在放大鏡內(nèi)展示屏幕上指定區(qū)域內(nèi)的內(nèi)容?

    首先會(huì)給大家介紹一個(gè)Flutter控件叫做BackdropFilter,他可以接收一個(gè)矩陣,對位置被該控件蓋?。磟軸處于它下方)的組件產(chǎn)生高斯模糊、傾斜等效果。詳細(xì)的使用和介紹可參考BackdropFilter。我們把這個(gè)控件放到Overlay上,他就可以對被其蓋住的屏幕部分進(jìn)行映射展示,但是我們并非想對該控件正下方(z軸)的內(nèi)容做高斯模糊等特效,而是想展示而是光標(biāo)附近的內(nèi)容,即位置處于它下面(y軸)的內(nèi)容。所以我們在對傳入的矩陣做translate(偏移),scale(放縮)操作,就可以把光標(biāo)和選區(qū)周圍的屏幕內(nèi)容映射到這個(gè)放大鏡中。代碼如下:

    deltaOffsetFromFocusPoint這個(gè)參數(shù)跟第一個(gè)問題中提到的相對位置有關(guān),需要先確定兩者的相對位置,然后計(jì)算出對應(yīng)的deltaOffsetFromFocusPoint,讓其剛好可以以光標(biāo)為放大鏡展示內(nèi)容的中心來進(jìn)行展示。

    3.如何處理雙端放大鏡的不同交互?

    對于雙端相同的交互,即選區(qū)出現(xiàn)時(shí)出現(xiàn)Toolbar,拖動(dòng)選區(qū)時(shí)隱藏Toolbar,展示Magnifier,拖動(dòng)結(jié)束時(shí)隱藏Magnifier,展示Toolbar。我們同樣可以在TextSelectionOverlay中的展示把手組件的TextSelectionHandleOverlay進(jìn)行改造實(shí)現(xiàn),在_handleDragStart和_handleDragEnd(新增方法)中顯示和隱藏邏輯。部分代碼如下:

    而對于雙端不同的交互,在Android中,因?yàn)楣鈽?biāo)定位可以看做選區(qū)定位的一種特殊場景,光標(biāo)下方的把手即選區(qū)中的左邊把手。無需特殊處理,而對于iOS來說,UITextField通過長按然后拖動(dòng)來進(jìn)行光標(biāo)的定位。所以我們需要對iOS進(jìn)行特殊處理,長按開始時(shí)展示放大鏡,長按結(jié)束時(shí)隱藏放大鏡。我們對TextSelectionGestureDetectorBuilder進(jìn)行改造即可。部分代碼如下:

    效果展示

    放大鏡選區(qū)支持反向選擇

    背景與現(xiàn)狀

    在平時(shí)的使用中我們注意到,iOS的UITextField是支持反選的,即在操作右邊把手時(shí),可以一直往左邊拖動(dòng),超過左邊把手時(shí),把手的位置會(huì)進(jìn)行一個(gè)互換,可以繼續(xù)操作左邊的把手。而Android很多廠商也支持了這一特性。但是我們發(fā)現(xiàn)在Flutter TextField中,這個(gè)操作是被禁止使用的。

    所以我們決定在富文本編輯器中支持選區(qū)的反向選擇。

    交互分析

    對iOS以及一些支持反向選擇的Android機(jī)型的交互進(jìn)行分析之后,以右邊把手往左邊移動(dòng)為例,有兩種交互。一種是在左右把手交匯的時(shí)候交換兩個(gè)把手的位置,繼續(xù)往前選擇移動(dòng)的是左邊樣式的把手。還有一種交互是,左右把手交匯的時(shí)候不改變兩個(gè)把手的位置,在拖動(dòng)結(jié)束之后,如果發(fā)現(xiàn)右邊把手在左邊把手的前面,再進(jìn)行交換。

    結(jié)合Flutter TextField的改造成本以及用戶的操作連續(xù)性,我們決定采用第二種交互方式,當(dāng)然iOS端應(yīng)該保持UITextField的第一種方式,這個(gè)會(huì)在后續(xù)進(jìn)行繼續(xù)對齊和優(yōu)化。

    代碼實(shí)現(xiàn)

    可能很多讀者會(huì)猜想,是不是在背景中介紹到那行代碼給刪掉,就可以實(shí)現(xiàn)這個(gè)Feature的支持。一開始和大家的想法一樣,但是出現(xiàn)了很多問題,接下來會(huì)進(jìn)行具體實(shí)現(xiàn)和分析。

    上面有說到,去除掉TextField之后,出現(xiàn)了一些問題。第一個(gè)就是,兩個(gè)把手交匯的時(shí)候,兩個(gè)把手都消失了,變成了光標(biāo)形態(tài)。原因是因?yàn)樵贔lutter TextField中,選區(qū)把手和光標(biāo)把手(僅Android,iOS光標(biāo)形態(tài)沒有把手)是在同一個(gè)地方實(shí)現(xiàn)的,當(dāng)左右選區(qū)交匯時(shí),會(huì)自動(dòng)切換成光標(biāo)形態(tài),導(dǎo)致無法進(jìn)行反選。

    如何在選區(qū)交匯時(shí)不切換為光標(biāo)形態(tài)?

    我們當(dāng)然不可能刪除這個(gè)規(guī)則,因?yàn)樵谠O(shè)定中,本來光標(biāo)就是收縮態(tài)的選區(qū),如果完全刪除,那光標(biāo)態(tài)也不可能存在了,因?yàn)樽笥疫x區(qū)收縮到一起時(shí),一定會(huì)展示左右兩個(gè)把手,這就有點(diǎn)舍本求末了。

    所以在絕大部分情況下我們是需要這個(gè)規(guī)則的,但是又想實(shí)現(xiàn)反選,自然而然會(huì)想到,設(shè)定一個(gè)標(biāo)記位來標(biāo)識我們正在操縱選區(qū)把手,當(dāng)處于這種場景下,左右把手交匯時(shí),我們就不將其轉(zhuǎn)化為光標(biāo)形態(tài)。

    1.設(shè)定標(biāo)記位表示把手拖動(dòng)狀態(tài)

    2.處于該狀態(tài)時(shí),選區(qū)收縮時(shí)展示展開態(tài)

    解決了這個(gè)問題,我們還剩下一個(gè)問題,反選完成之后,如何交換兩個(gè)把手。

    如何在反選完成之后保證正確的選區(qū)把手樣式?

    我們需要在在TextSelectionOverlay中的展示把手組件的TextSelectionHandleOverlay進(jìn)行實(shí)現(xiàn),新增一個(gè)_handleDragEnd方法,交換selection的baseOffset和extentOffset

    效果展示

    總結(jié)與展望

    縱觀整個(gè)系列文章,我們從協(xié)議層、渲染層、自定義擴(kuò)展以及交互體驗(yàn)優(yōu)化等方面,詳細(xì)介紹如何實(shí)現(xiàn)一個(gè)功能完善、可擴(kuò)展、高性能的Flutter富文本編輯器。目前Mural已經(jīng)在閑魚的多個(gè)場景落地,整體的體驗(yàn)也有了不錯(cuò)的提升。

    未來會(huì)繼續(xù)在基礎(chǔ)能力、交互體驗(yàn)、性能等方面更深入的完善富文本編輯器的能力:

    • 在基礎(chǔ)能力方面,跟隨富文本編輯器的業(yè)界標(biāo)準(zhǔn),提供更加豐富的富文本組件和擴(kuò)展Plugin能力;完善單元測試覆蓋,保證穩(wěn)定性。
    • 在交互體驗(yàn)方面,我們盡量給用戶提供iOS和Android的端側(cè)交互體驗(yàn),優(yōu)化Flutter現(xiàn)有的一些交互體驗(yàn)問題;但是還有一些功能是尚未和雙端對齊的,例如iOS的實(shí)況本文、三指復(fù)制粘貼撤銷重做等,這些都正在調(diào)研實(shí)現(xiàn)以及上線中。
    • 在性能方面,我們優(yōu)化了超長文本編輯的卡頓問題,與原生的TextField相比,卡頓有了明顯的優(yōu)化;未來會(huì)通過兩個(gè)思路進(jìn)行優(yōu)化性能:判斷Model的Dom結(jié)構(gòu)是否變化減少不必要的重復(fù)刷新渲染,以及判斷選區(qū)、ToolBar是否變化減少不必要的重復(fù)計(jì)算,來提升編輯器的渲染和編輯的性能。

    這個(gè)問題還有疑問的話,可以加幕.思.城火星老師免費(fèi)咨詢,微.信號是為: msc496。

    難題沒解決?加我微信給你講!【僅限淘寶賣家交流運(yùn)營知識,非賣家不要加我哈】
    >

    推薦閱讀:

    如何利用扣扣進(jìn)行推廣為淘寶店鋪帶來流量呢?

    一個(gè)淘寶買家在店里重復(fù)購買很多次,被扣分怎么辦?

    2021年雙12淘寶直播規(guī)則是什么?要求是什么?

    更多資訊請關(guān)注幕 思 城。

    發(fā)表評論

    別默默看了 登錄\ 注冊 一起參與討論!

      微信掃碼回復(fù)「666