前言
最近項目中遇到一個需求,需要動態(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é)有所幫助岂贩。