Android記一次優(yōu)化文字縮進(jìn)控件的坑

前言

項(xiàng)目中個(gè)人負(fù)責(zé)的多個(gè)列表頁用到類似微博及小紅書如下圖的這種超過縮進(jìn)行數(shù)文末添加" ...全文" 展開的控件熔脂。在頁面的優(yōu)化同城中逸吵,通過systrace跟蹤發(fā)現(xiàn)項(xiàng)目中該自定義控件有個(gè)方法會反復(fù)調(diào)用多次(具體以下詳解) 峦萎,本以為這種控件應(yīng)該是有比較成熟的解決方案,于是github一頓搜索,唯一一個(gè)星數(shù)上千的庫就是ExpandableTextView,查看后實(shí)現(xiàn)原理是:2個(gè)控件不是span的形式添加到textview尾部,然后獲取4行時(shí)textview需顯示的高度倒彰,按鈕點(diǎn)下時(shí)動畫控制view的height屬性;且并無文末添加“...全文”的功能莱睁,與需求不符待讳。無奈只能自己動手,優(yōu)化現(xiàn)有控件仰剿。

image.png

image.png

思路及原理

  1. 發(fā)現(xiàn)其實(shí)這個(gè)效果與 TextView 設(shè)置 android:maxLines 之后创淡,再設(shè)置 android:ellipsize 為 end 很相似,只是 … 替換換成了 …展開 南吮,遺憾的是系統(tǒng)并沒有提供直接替換 … 的API琳彩。

但是,在涉及到 android:ellipsize 屬性處理的 TextView 的源碼中可以看到使用了 StaticLayout 了一個(gè)可以幫助我們實(shí)現(xiàn)效果的工具類 StaticLayout,StaticLayout 是android中處理文字換行的一個(gè)工具類露乏。

有BoringLayout碧浊、StaticLayout 和 DynamicLayout 三個(gè)工具類

  • BoringLayout 是單行顯示時(shí)使用的
  • StaticLayout 是針對不可以變的文本(不同系統(tǒng)版本構(gòu)造方式不大一樣,)
  • DynamicLayout 則是針對可編輯改變的文本瘟仿,并且會更新自身箱锐。
    具體這些類及方法本文不詳細(xì)展開,有興趣的同學(xué)可自行查看相關(guān)文檔

于是得出最終方案
2:動態(tài)截取文字劳较,加上“...全文”后剛好撐滿縮進(jìn)驹止,然后將新的CharSequence設(shè)入textview即可。

細(xì)節(jié)及注意點(diǎn)

  • 一定要先測量一次如果本身文字行數(shù)就不會超過鎖進(jìn)行數(shù)观蜗,則什么都不要再做任何處理臊恋,浪費(fèi)性能,直接走textview的方法即可(需求上也是如此)
  • 記錄一份原數(shù)據(jù)墓捻,如果有些地方是顯示的是"...展開"抖仅,點(diǎn)擊后的效果是直接展開顯示全文的話
  • 新文字設(shè)置進(jìn)來時(shí),對比下當(dāng)前記錄的原數(shù)據(jù)與設(shè)入的新數(shù)據(jù)是否一致毙替,一致則不再做多余處理岸售,算是性能的優(yōu)化践樱。還取決于控件實(shí)現(xiàn)方式厂画,像原項(xiàng)目中控件的處理,是在onDraw方法(當(dāng)然做了其它限制拷邢,保證每次更改文字只觸發(fā)一次袱院,不能每次ondraw都去觸發(fā),否則性能就廢了)時(shí)才取攔截瞭稼,因?yàn)閛nDraw方法已經(jīng)在measure和layout方法之后忽洛,如果不做該操作當(dāng)控件放在listview或recyclerview列表中,當(dāng)notify或者滑動操作時(shí)环肘,會造成先高度測量差異而抖動一下欲虚。

項(xiàng)目中多次調(diào)用的方法優(yōu)化

有了以上思路后,根據(jù)systrac顯示悔雹,多次調(diào)用耗時(shí)复哆,跟蹤項(xiàng)目中調(diào)用多次的方法,發(fā)現(xiàn)他是一個(gè)循環(huán)腌零,一直去嘗試截取原文中的不同長度的文字去與“...全文”拼成后剛好布滿指定縮進(jìn)行數(shù)的文字梯找。

一開始看到此處一臉懵逼,為啥要一直循環(huán)遍歷去嘗試益涧,而不是直接先通過StaticLayout.getLineEnd方法锈锤,直接獲取縮進(jìn)行數(shù)的末尾offset,然后截取原文字,再對這個(gè)截取后的文字久免,刪減“...全文”的長度浅辙,最后再將這個(gè)刪減后的文字拼接上"...全文",不就是我們想要的最終結(jié)果妄壶,不就可以了
大概如下

...
 int lineEnd = getLayout().getLineEnd(mCollapsedLines - 1);
 CharSequence suffix = "...全文";
 int newEnd = lineEnd - suffix.length() - 1;
int end = newEnd > 0 ? newEnd : lineEnd;
CharSequence finalSequence = note.subSequence(0, end);
...

然而摔握,實(shí)際結(jié)果令人啪啪打臉[捂臉哭],會有的還有間距丁寄,有的超過氨淌。因?yàn)槁┝艘粋€(gè)重要因素,就是同樣length的文字伊磺,在繪制時(shí)所占用的寬度不一定一致盛正。

各種搜索網(wǎng)上其它大佬的解決方案,也都是只能遍歷一直去嘗試屑埋,看截到多少能剛好鋪滿豪筝。

1:

TextPaint paint = getPaint();
int maxWidth = mCollapsedLines * (getMeasuredWidth() - getPaddingLeft() - getPaddingRight());
while (paint.measureText(note.substring(0, end) + suffix) > maxWidth)
    end--;
note = note.substring(0, end);

而且這代碼還有個(gè)問題就是忽律了各種span長度問題
2:ExpandableText-Example

  //計(jì)算原文截取位置
                int endPos = layout.getLineEnd(maxLines - 1);
                if (originalText.length() <= endPos) {
                    mCloseSpannableStr = charSequenceToSpannable(originalText);
                } else {
                    mCloseSpannableStr = charSequenceToSpannable(originalText.subSequence(0, endPos));
                }
                SpannableStringBuilder tempText2 = charSequenceToSpannable(mCloseSpannableStr).append(ELLIPSIS_STRING);
                if (mOpenSuffixSpan != null) {
                    tempText2.append(mOpenSuffixSpan);
                }
                //循環(huán)判斷,收起內(nèi)容添加展開后綴后的內(nèi)容
                Layout tempLayout = createStaticLayout(tempText2);
                while (tempLayout.getLineCount() > maxLines) {
                    int lastSpace = mCloseSpannableStr.length() - 1;
                    if (lastSpace == -1) {
                        break;
                    }
                    if (originalText.length() <= lastSpace) {
                        mCloseSpannableStr = charSequenceToSpannable(originalText);
                    } else {
                        mCloseSpannableStr = charSequenceToSpannable(originalText.subSequence(0, lastSpace));
                    }
                    tempText2 = charSequenceToSpannable(mCloseSpannableStr).append(ELLIPSIS_STRING);
                    if (mOpenSuffixSpan != null) {
                        tempText2.append(mOpenSuffixSpan);
                    }
                    tempLayout = createStaticLayout(tempText2);

                }

還有其它多個(gè)庫摘能,不一一鏈接续崖,區(qū)別在于如何去測量,如果去逼近求出最終字符串而已团搞。所以現(xiàn)在能優(yōu)化的重點(diǎn)就在于严望,如何盡量地去減少遍歷的次數(shù)。
上方庫的方法比較簡單逻恐,也與項(xiàng)目中用到的方法類似像吻。即:通過StaticLayout.getLineEnd方法,直接獲取縮進(jìn)行數(shù)的末尾offset复隆,然后截取原文字拨匆,直接拼接上"...全文"span,然后依次往前遞減字符去逼近挽拂。
項(xiàng)目中的是直接全字段二分查找去逼近惭每,以上開源庫方法做為備用方案。經(jīng)試驗(yàn)亏栈,在文字長度不是很長時(shí)台腥,效率比備用方法高不少;當(dāng)文字長度過長時(shí)仑扑,備用方法則優(yōu)勢明顯览爵。
其實(shí)還可以進(jìn)一部優(yōu)化,即二分查找法的起始位置不要全字串二分镇饮,從 截取后的文字蜓竹,刪減“...全文”的長度,開始到最后拼接上的 這個(gè)小范圍去二分查找。
優(yōu)化后打log方法及效果如下:

         //優(yōu)化前方式
        CharSequence destStr = tailorText(text,false);
        long newEnd = System.currentTimeMillis();
        //優(yōu)化后方式
        CharSequence destStrNewMethod = tailorText(text,true);
        long newEnd2 = System.currentTimeMillis();
        Log.d(TAG, ("oldMethod--->"+(newEnd - startTime))+"|NewMethod="+(newEnd2-newEnd) + "ms");
image.png

這幾毫秒的時(shí)間在一個(gè)布局中并無關(guān)緊要俱济,但因?yàn)轫?xiàng)目中是放在listview及recyclerview中使用嘶是,一次滑動及來回操作便會調(diào)用反復(fù)調(diào)用多次,積累起來便很可觀蛛碌。

尾言

  • 關(guān)于源碼聂喇,由于本次只是做一些優(yōu)化思路,具體控件有很多舊的冗余代碼蔚携,未做清理希太,故不附上該控件的全部源碼
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市酝蜒,隨后出現(xiàn)的幾起案子誊辉,更是在濱河造成了極大的恐慌,老刑警劉巖亡脑,帶你破解...
    沈念sama閱讀 219,490評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件堕澄,死亡現(xiàn)場離奇詭異,居然都是意外死亡霉咨,警方通過查閱死者的電腦和手機(jī)蛙紫,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來途戒,“玉大人坑傅,你說我怎么就攤上這事」字停” “怎么了裁蚁?”我有些...
    開封第一講書人閱讀 165,830評論 0 356
  • 文/不壞的土叔 我叫張陵矢渊,是天一觀的道長继准。 經(jīng)常有香客問我,道長矮男,這世上最難降的妖魔是什么移必? 我笑而不...
    開封第一講書人閱讀 58,957評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮毡鉴,結(jié)果婚禮上崔泵,老公的妹妹穿的比我還像新娘。我一直安慰自己猪瞬,他們只是感情好憎瘸,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,974評論 6 393
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著陈瘦,像睡著了一般幌甘。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,754評論 1 307
  • 那天锅风,我揣著相機(jī)與錄音酥诽,去河邊找鬼。 笑死皱埠,一個(gè)胖子當(dāng)著我的面吹牛肮帐,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播边器,決...
    沈念sama閱讀 40,464評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼训枢,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了忘巧?” 一聲冷哼從身側(cè)響起肮砾,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎袋坑,沒想到半個(gè)月后仗处,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,847評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡枣宫,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,995評論 3 338
  • 正文 我和宋清朗相戀三年婆誓,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片也颤。...
    茶點(diǎn)故事閱讀 40,137評論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡洋幻,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出翅娶,到底是詐尸還是另有隱情文留,我是刑警寧澤,帶...
    沈念sama閱讀 35,819評論 5 346
  • 正文 年R本政府宣布竭沫,位于F島的核電站燥翅,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏蜕提。R本人自食惡果不足惜森书,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,482評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望谎势。 院中可真熱鬧凛膏,春花似錦、人聲如沸脏榆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,023評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽须喂。三九已至吁断,卻和暖如春典唇,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背胯府。 一陣腳步聲響...
    開封第一講書人閱讀 33,149評論 1 272
  • 我被黑心中介騙來泰國打工介衔, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人骂因。 一個(gè)月前我還...
    沈念sama閱讀 48,409評論 3 373
  • 正文 我出身青樓炎咖,卻偏偏與公主長得像,于是被迫代替她去往敵國和親寒波。 傳聞我的和親對象是個(gè)殘疾皇子乘盼,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,086評論 2 355

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

  • 1.badgeVaule氣泡提示 2.git終端命令方法> pwd查看全部 >cd>ls >之后桌面找到文件夾內(nèi)容...
    i得深刻方得S閱讀 4,667評論 1 9
  • 最近產(chǎn)品汪和運(yùn)營商討下來決定要做商品促銷活動,然后設(shè)計(jì)妹子給到最終的效果圖俄烁。 第一感覺就是 so so easy ...
    lovejjfg閱讀 13,707評論 6 32
  • 一六年第一天页屠,沒有回家粹胯,在杭州,沒有一個(gè)人陪辰企,自己一個(gè)人過风纠。 大學(xué)同學(xué),小學(xué)同學(xué)牢贸,一個(gè)個(gè)陸陸續(xù)續(xù)結(jié)婚生子竹观,自己一點(diǎn)...
    finityho閱讀 175評論 0 0
  • 店長要學(xué)會怎么去分解指標(biāo),怎樣去把沒完成的指標(biāo)分解到其他時(shí)間里潜索。
    23aae7e9bc8e閱讀 51評論 0 0
  • 在朋友圈里的一次次安利,“比肩《肖申克的救贖》”由驹,“刷新韓國電影新高度... 可能看之前就對它抱有...
    啟榮_b07a閱讀 319評論 2 0