關(guān)于Android 動態(tài)獲取列表中TextView的內(nèi)容文本高度問題

前言

最近項目中遇到一個需求,需要動態(tài)的計算出列表中內(nèi)容顯示的高度氓栈,然后動態(tài)顯示列表需要顯示內(nèi)容的元素,開始一想拐云,簡單的很罢猪,就是異步在加載列表之前把數(shù)據(jù)解析出來,然后算下高度叉瘩,重新再把內(nèi)容賦值給數(shù)據(jù)源就OK了膳帕,你以為呢,這樣就結(jié)束了薇缅?那你錯了危彩,問題大著呢,這樣異步請求回來的數(shù)據(jù)泳桦,可能顯示不全汤徽,可能顯示錯亂。灸撰。谒府。那么怎么解決呢,嘗試了很多方法梧奢。狱掂。演痒。

一亲轨、異步獲取TextView的高度

異步獲取TextView文本的高度有多種,網(wǎng)上都有鸟顺,簡單介紹下:

1)通過onPreDrawListener

tempTextView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
    @Override
    public boolean onPreDraw() {
        //這個回調(diào)會調(diào)用多次惦蚊,獲取完行數(shù)記得注銷監(jiān)聽
        tempTextView.getViewTreeObserver().removeOnPreDrawListener(this);
        int noteContentHeight = (int) (tempTextView.getLineCount() * textSize);
        noteTotalHeight = noteTotalHeight - noteContentHeight;
        .....
        return false;
    }
});

2)通過View 自帶的post方法,異步獲取

tempTextView.post(new Runnable() {
    @Override
    public void run() {
        tempTextView.getViewTreeObserver().removeOnPreDrawListener(this);
        int noteContentHeight = (int) (tempTextView.getLineCount() * textSize);
    }
});

以上是比較常用的方法讯嫂,需要注意的是蹦锋,文本的textSize = textSize+ LineSpacingExtra;

二欧芽、遇到問題莉掂,解決問題

但是這樣還不行,因為TextView需要繪制才能拿到內(nèi)容的行高千扔,要不然返回的全是0憎妙,所有,可以在Activity 的布局里寫一個invisible的textView曲楚,背景設(shè)置成透明的厘唾,不影響界面顯示,去加載內(nèi)容龙誊,之后就能拿到高度了抚垃。然后根據(jù)需求需要顯示的高度,做對比,不同類型的內(nèi)容高度都算出來鹤树,然后計算高度铣焊,超過就不顯示這個類型的內(nèi)容了,沒有超過就加到內(nèi)容顯示的數(shù)組罕伯。

if (index < contentList.size()) {
    ContentModel model = contentList.get(index);
    if (model.getType() == 0) {
        tempTextView.setText(model.getContent());
        tempTextView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
            @Override
            public boolean onPreDraw() {
                //這個回調(diào)會調(diào)用多次粗截,獲取完行數(shù)記得注銷監(jiān)聽
                tempTextView.getViewTreeObserver().removeOnPreDrawListener(this);
                int contentHeight = (int) (tempTextView.getLineCount() * 69);
                contentTotalHeight = contentTotalHeight - contentHeight;
                tempContentList.add(model);
                if (contentTotalHeight > 0) {
                    tempContentList.add(model);
                }
                return false;
            }
        });
    } else if (model.getType() == 1) {
        contentTotalHeight = contentTotalHeight - 89;
        if (contentTotalHeight > 0) {
            tempContentList.add(model);
         }
    } else if (model.getType() == 2) {
        contentTotalHeight = contentTotalHeight - 83;
        if (contentTotalHeight > 0) {
            tempContentList.add(model);
        }
    }
}

類似這樣的,89或者83是不同類型的高度,這里我寫固定值捣炬,根據(jù)需求來計算熊昌,當(dāng)然可能不止3種類型,理論上這樣湿酸,是不是就可以把要顯示的內(nèi)容放到內(nèi)容顯示的列表里婿屹,返回再把數(shù)據(jù)重新賦值給源數(shù)據(jù),顯示就可以了推溃。但是實際結(jié)果是昂利,文字有不顯示的,有顯示錯亂的铁坎,原因很簡單蜂奸,tempTextView.getViewTreeObserver().addOnPreDrawListener()這樣,或者tempTextView.post(new Runnable() {})獲取高度都是異步的操作硬萍,列表數(shù)據(jù)其他模塊都加載完了扩所,這個結(jié)果有可能都沒有返回,所有就有上面所說的結(jié)果了朴乖。
后來想了下祖屏,那能不能等文本的高度算出來之后,再執(zhí)行下一個數(shù)據(jù)的處理呢买羞,當(dāng)然可以袁勺,遞歸嘛,優(yōu)化之后:

private void getContentList(ContentCallBack contentCallBack) {
    if (index < contentList.size()) {
        ContentModel model = contentList.get(index);
        if (model.getType() == 0) {
            tempTextView.setText(model.getContent());
            tempTextView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
                @Override
                public boolean onPreDraw() {
                    //這個回調(diào)會調(diào)用多次畜普,獲取完行數(shù)記得注銷監(jiān)聽
                    tempTextView.getViewTreeObserver().removeOnPreDrawListener(this);
                    int contentHeight = (int) (tempTextView.getLineCount() * 69);
                    contentTotalHeight = contentTotalHeight - noteContentHeight;
                    tempContentList.add(model);
                    if (contentTotalHeight > 0) {
                        index++;
                        getContentList(contentCallBack);
                    } else {
                        if (noteContentCallBack != null) {
                            contentCallBack.success(tempContentList);
                        }
                    }
                    return false;
                }
            });

        } else if (model.getType() == 1) {
            contentTotalHeight = contentTotalHeight - 89;
            if (contentTotalHeight > 0) {
                tempContentList.add(model);
                index++;
                getContentList(contentCallBack);
            } else {
                if (contentCallBack != null) {
                    contentCallBack.success(tempContentList);
                }
            }
        } else if (model.getType() == 2) {
            contentTotalHeight = contentTotalHeight - 83;
            if (contentTotalHeight > 0) {
                tempContentList.add(model);
                index++;
                getContentList(contentCallBack);
            } else {
                if (contentCallBack != null) {
                    contentCallBack.success(tempContentList);
                }
            }
        }
    } else {
        if (contentCallBack != null) {
            contentCallBack.success(tempContentList);
        }
    }
}

private interface ContentCallBack {
    void success(List<ContentModel> contentModels);

}

調(diào)用:

getNoteContentList(new ContentCallBack() {
    @Override
    public void success(List<ContentModel> contentModels) {
        ....
        處理數(shù)據(jù)
    }
});

外層調(diào)用期丰,也需要用遞歸,不能用for循環(huán)去計算每個item內(nèi)容的高度吃挑,for循環(huán)不會等你執(zhí)行完異步再走下一個的index钝荡。
具體實現(xiàn)這里就不貼了,原理知道了就很簡單了儒鹿,這樣使用了2次遞歸之后化撕,返回的數(shù)據(jù)是正確的,而且不會錯亂约炎,不會不顯示植阴,長嘆一口氣蟹瘾,就一個內(nèi)容顯示搞這么復(fù)雜,然后連上我心愛的小米10測試機掠手,run起來憾朴,看下結(jié)果,咳咳咳喷鸽,完美众雷,不管數(shù)據(jù)怎么刷新,都不會有錯亂和顯示異常的問題做祝。但是總感覺好像太復(fù)雜了...2次遞歸

三砾省、優(yōu)化

下班之后,路上還是在想這個問題混槐,有沒有簡單的方法呢编兄,一步到位,而且沒有異步也沒有遞歸声登,TextView的行數(shù)怎么算出來的狠鸳,回家后就網(wǎng)上看了看,稍微看了下源碼悯嗓,找到了以下的方法:

/**
 * 提前獲取textview行數(shù)
 */
public static int getTextViewLines(TextView textView, int textViewWidth) {
    int width = textViewWidth - textView.getCompoundPaddingLeft() - textView.getCompoundPaddingRight();
    StaticLayout staticLayout;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        staticLayout = getStaticLayout23(textView, width);
    } else {
        staticLayout = getStaticLayout(textView, width);
    }
    int lines = staticLayout.getLineCount();
    int maxLines = textView.getMaxLines();
    if (maxLines > lines) {
        return lines;
    }
    return maxLines;
}

/**
 * sdk>=23
 */
@RequiresApi(api = Build.VERSION_CODES.M)
private static StaticLayout getStaticLayout23(TextView textView, int width) {
    StaticLayout.Builder builder = StaticLayout.Builder.obtain(textView.getText(),
            0, textView.getText().length(), textView.getPaint(), width)
            .setAlignment(Layout.Alignment.ALIGN_NORMAL)
            .setTextDirection(TextDirectionHeuristics.FIRSTSTRONG_LTR)
            .setLineSpacing(textView.getLineSpacingExtra(), textView.getLineSpacingMultiplier())
            .setIncludePad(textView.getIncludeFontPadding())
            .setBreakStrategy(textView.getBreakStrategy())
            .setHyphenationFrequency(textView.getHyphenationFrequency())
            .setMaxLines(textView.getMaxLines() == -1 ? Integer.MAX_VALUE : textView.getMaxLines());
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        builder.setJustificationMode(textView.getJustificationMode());
    }
    if (textView.getEllipsize() != null && textView.getKeyListener() == null) {
        builder.setEllipsize(textView.getEllipsize())
                .setEllipsizedWidth(width);
    }
    return builder.build();
}

/**
 * sdk<23
 */
private static StaticLayout getStaticLayout(TextView textView, int width) {
    return new StaticLayout(textView.getText(),
            0, textView.getText().length(),
            textView.getPaint(), width, Layout.Alignment.ALIGN_NORMAL,
            textView.getLineSpacingMultiplier(),
            textView.getLineSpacingExtra(), textView.getIncludeFontPadding(), textView.getEllipsize(),
            width);
}

這個方法有個前提件舵,就是要已知TextView的寬度,TextView內(nèi)部的換行是通過一個StaticLayout的類來處理的脯厨,而且我們調(diào)用的getLineCount()方法最后也是調(diào)用的StaticLayout類中的getLineCount()方法铅祸,所以我們只需要創(chuàng)建一個和TextView內(nèi)部一樣的StaticLayout就可以了,然后調(diào)用staticLayout.getLineCount()方法就可以獲取到和當(dāng)前TextView行數(shù)一樣的值了俄认。

總結(jié)

只能說自己這塊涉及的少个少,首先想到的還是通過一般的實現(xiàn)邏輯去解決問題,沒有仔細(xì)去研究原理眯杏,可能多走了彎路,但是也是增長了知識壳澳,希望對遇到類似的問題的同學(xué)有所幫助岂贩。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市巷波,隨后出現(xiàn)的幾起案子萎津,更是在濱河造成了極大的恐慌,老刑警劉巖抹镊,帶你破解...
    沈念sama閱讀 210,914評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件锉屈,死亡現(xiàn)場離奇詭異,居然都是意外死亡垮耳,警方通過查閱死者的電腦和手機颈渊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,935評論 2 383
  • 文/潘曉璐 我一進店門遂黍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人俊嗽,你說我怎么就攤上這事雾家。” “怎么了绍豁?”我有些...
    開封第一講書人閱讀 156,531評論 0 345
  • 文/不壞的土叔 我叫張陵芯咧,是天一觀的道長。 經(jīng)常有香客問我竹揍,道長敬飒,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,309評論 1 282
  • 正文 為了忘掉前任芬位,我火速辦了婚禮驶拱,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘晶衷。我一直安慰自己蓝纲,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,381評論 5 384
  • 文/花漫 我一把揭開白布晌纫。 她就那樣靜靜地躺著税迷,像睡著了一般。 火紅的嫁衣襯著肌膚如雪锹漱。 梳的紋絲不亂的頭發(fā)上箭养,一...
    開封第一講書人閱讀 49,730評論 1 289
  • 那天,我揣著相機與錄音哥牍,去河邊找鬼毕泌。 笑死,一個胖子當(dāng)著我的面吹牛嗅辣,可吹牛的內(nèi)容都是我干的撼泛。 我是一名探鬼主播,決...
    沈念sama閱讀 38,882評論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼澡谭,長吁一口氣:“原來是場噩夢啊……” “哼愿题!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起蛙奖,我...
    開封第一講書人閱讀 37,643評論 0 266
  • 序言:老撾萬榮一對情侶失蹤潘酗,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后雁仲,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體仔夺,經(jīng)...
    沈念sama閱讀 44,095評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,448評論 2 325
  • 正文 我和宋清朗相戀三年攒砖,在試婚紗的時候發(fā)現(xiàn)自己被綠了缸兔。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片日裙。...
    茶點故事閱讀 38,566評論 1 339
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖灶体,靈堂內(nèi)的尸體忽然破棺而出阅签,到底是詐尸還是另有隱情,我是刑警寧澤蝎抽,帶...
    沈念sama閱讀 34,253評論 4 328
  • 正文 年R本政府宣布政钟,位于F島的核電站,受9級特大地震影響樟结,放射性物質(zhì)發(fā)生泄漏养交。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,829評論 3 312
  • 文/蒙蒙 一瓢宦、第九天 我趴在偏房一處隱蔽的房頂上張望碎连。 院中可真熱鬧,春花似錦驮履、人聲如沸鱼辙。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,715評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽倒戏。三九已至,卻和暖如春恐似,著一層夾襖步出監(jiān)牢的瞬間杜跷,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,945評論 1 264
  • 我被黑心中介騙來泰國打工矫夷, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留葛闷,地道東北人。 一個月前我還...
    沈念sama閱讀 46,248評論 2 360
  • 正文 我出身青樓双藕,卻偏偏與公主長得像淑趾,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子蔓彩,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,440評論 2 348

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