<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          真·富文本編輯器的演進(jìn)之路-富文本Span的邊界探究

          共 2769字,需瀏覽 6分鐘

           ·

          2021-12-10 09:35

          點(diǎn)擊上方藍(lán)字關(guān)注我,知識會給你力量


          Span是Android文本系統(tǒng)中一個非常重要的功能,對于它的一般使用,其實(shí)比較簡單,但在處理一些復(fù)雜業(yè)務(wù)時,Span的邊界問題處理就顯得非常重要了,不然很容易因?yàn)檫吔缜闆r沒有處理好,導(dǎo)致一系列很麻煩的bug。

          setSpan

          with(binding)?{
          ????val?text?=?"我真的是被Span搞裂開了"
          ????SpannableString(text).also?{
          ????????it.setSpan(StyleSpan(Typeface.BOLD_ITALIC),?0,?text.length,?Spanned.SPAN_INCLUSIVE_INCLUSIVE)
          ????????this.textview.text?=?it
          ????}
          }

          注意這里的range,start…end,end是text.length,正好將所有文字Span化,如果start…end超過0…text.length的區(qū)間,那么就會產(chǎn)生IndexOutOfBoundsException,由此可知,setSpan中的range,是一個左閉右開區(qū)間。

          [ start … end ) —— [ 0 … length )

          getSpans

          with(binding.textview)?{
          ????val?spannableString?=?SpannableString(text)
          ????val?spans?=?spannableString.getSpans(0,?length(),?StyleSpan::class.java)
          ????spans.forEach?{?span ->
          ????????val?start?=?spannableString.getSpanStart(span)
          ????????val?end?=?spannableString.getSpanEnd(span)
          ????????Log.d("xys",?"getSpans:?Start:?$start?,?End:?$end")
          ????}
          }

          與setSpan類似,我們通過getSpans來找到range里面的所有指定類型span,那么這里的start…end呢,我們先試下0…length,0…length - 1,0…length + 1,-1…length,-1…length + 1,length - 1…length + 1,-1…1這幾種情況。

          不出意外,這幾種都可以獲取出正確的Span。

          再來看看length…length + 1,-1…0這兩種情況。

          出意外了,這時候就獲取不到了。

          總結(jié)一下,來張圖就看清楚了。

          image-20211202110043219

          紅色的范圍是不可獲取,灰色的范圍是可以獲取,由此可見,getSpans比setSpan的range要復(fù)雜多了。

          總結(jié)一下,對于一個Span,范圍是0…Length-1,那么getSpans的range,start…end能獲取到Span的條件是,start…end完全落在0…Length-1的左開右閉區(qū)間里。

          最常用的方式,實(shí)際上就是:

          getSpans(length()?-?1,?length(),?StyleSpan::class.java)

          Span原理分析

          我們借助SpannableStringInternal來分析Span具體是如何作用到Text上的。

          要想把Span附加到Text上,那么肯定是對Text做了標(biāo)記,在渲染時,根據(jù)標(biāo)記來做特殊的渲染。

          123davdzz

          這是Spannable相關(guān)的類繼承關(guān)系。

          • 對于SpannedString、SpannableString來說,它們是繼承的SpannableStringInternal。
          • Span是否是可變,是通過Spanned(Span不能增刪)和Spannable(Span可以增刪)接口來區(qū)分的。

          所以核心邏輯都在SpannableStringInternal中,在它的源碼中,有幾個重要的成員變量:

          • mSpans:用來保存具體的Span對象
          • mSpanData:用來保存每個Span的數(shù)據(jù),start、end、flag

          在mSpanData中,每個Span需要三個元素來控制,所以,mSpanData的長度是3的倍數(shù),每3個元素代表一個Span,從下面這張圖就能看的很清楚了。

          img

          下面繼續(xù)來看SpannableStringInternal的構(gòu)造函數(shù)。

          SpannableStringInternal的構(gòu)造函數(shù),就是為了初始化上面的成員變量,它有兩個來源,一個本身就是SpannableStringInternal,那么直接繼承它內(nèi)部的這些變量即可,另一個是其它類型,就需要重新創(chuàng)建。

          private?static?final?int?START?=?0;
          private?static?final?int?END?=?1;
          private?static?final?int?FLAGS?=?2;
          private?static?final?int?COLUMNS?=?3;

          int?start?=?mSpanData[i?*?COLUMNS?+?START];
          int?end?=?mSpanData[i?*?COLUMNS?+?END];
          int?flag?=?mSpanData[i?*?COLUMNS?+?FLAGS];

          在了解了Text如何保存Span及其數(shù)據(jù)后,我們來看下getSpans為什么會有上面那么奇葩的設(shè)計。

          原因就在getSpans代碼中的check邏輯。

          ????public??T[]?getSpans(int?queryStart,?int?queryEnd,?Class?kind)?{
          ????????int?count?=?0;

          ????????int?spanCount?=?mSpanCount;
          ????????Object[]?spans?=?mSpans;
          ????????int[]?data?=?mSpanData;
          ????????Object[]?ret?=?null;
          ????????Object?ret1?=?null;

          ????????for?(int?i?=?0;?i?????????????int?spanStart?=?data[i?*?COLUMNS?+?START];
          ????????????int?spanEnd?=?data[i?*?COLUMNS?+?END];

          ????????????if?(spanStart?>?queryEnd)?{
          ????????????????continue;
          ????????????}
          ????????????if?(spanEnd?????????????????continue;
          ????????????}

          ????????????if?(spanStart?!=?spanEnd?&&?queryStart?!=?queryEnd)?{
          ????????????????if?(spanStart?==?queryEnd)?{
          ????????????????????continue;
          ????????????????}
          ????????????????if?(spanEnd?==?queryStart)?{
          ????????????????????continue;
          ????????????????}
          ????????????}

          就是這里的一堆判斷邏輯,導(dǎo)致了前面略顯奇葩的結(jié)果。

          看到這里,應(yīng)該就能明白了,我們傳入的range(queryStart…queryEnd)和(spanStart…spanEnd)之間究竟是怎么比較的。

          要通過check,必須依次保證下面的條件(以-1…0為例):

          • End >= SpanStart ? 0 >= 0 ?true
          • Start <= SpanEnd ? -1 <= 13 ?true
          • SpanStart != SpanEnd && Start != End ? true
          • End != SpanStart ?0 != 0 ? ?false
          • SpanEnd != Start ? 13 != -1 ?true

          由此可見,這些條件check的實(shí)際上是query的End和SpanStart,以及query的Start和SpanEnd之間的關(guān)系。

          向大家推薦下我的網(wǎng)站?https://xuyisheng.top/??點(diǎn)擊原文一鍵直達(dá)

          專注 Android-Kotlin-Flutter 歡迎大家訪問



          往期推薦


          本文原創(chuàng)公眾號:群英傳,授權(quán)轉(zhuǎn)載請聯(lián)系微信(Tomcat_xu),授權(quán)后,請在原創(chuàng)發(fā)表24小時后轉(zhuǎn)載。
          < END >
          作者:徐宜生

          更文不易,點(diǎn)個“三連”支持一下??


          瀏覽 69
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  三级址在线| av777777 | 一本色道久久综合无码欧美 | 涩涩大香蕉 | 91精品国产91久久久久久吃药 |