Android 單個TextView 點選“顯示全部”功能實現方法
網上很多TextView的“顯示全部”,“顯示更多”的方案實現都是兩個TextView,一個在上面顯示內容,一個在下面用來點選。但是我在實際工作中遇到的需求是“顯示全部”提示要內聯在原文的後面,使用一個TextView進行顯示,不能放在原文的下面,下面把程式碼貼一下,主要實現的功能如下:
1、“顯示全部”/“顯示更多”緊連在正文的後面,不能另起一行
2、當字數超過一定數量顯示“顯示更多”,
3、當行數超過一定數量顯示“顯示更多”,比如每行只有一個字,不停的換行,雖然字少但是行數多,也應該將限制之外的行全部省略掉
效果展示
實現起來非常簡單,主要步驟如下
1、首先判斷要處理段落的字數是否超過限制,如果超過就在後面綴上“顯示更多”
2、判斷要處理段落在某個TextView上完整顯示的行數,如果行數超過限制,那麼就顯示“顯示全部”
3、使用SpannableString,構造:削減後的段落+“...顯示更多”。然後將最後“...顯示更多”這個字使用ClickableSpan設定上點選事件
有以下幾個難點
1、如何在不進行UI繪製的情況下拿到TextView顯示某段文字能顯示多少行
2、如何獲得第x行最後一個字的下標以便從此擷取
3、在非同步處理的環境中,如何在不進行繪製的情況下獲得TextView會畫多高以便預留位置
首先下面這段程式碼是通過傳入一個TextView及其寬度,然後獲得任意一行最末那個字元的下標的方法,只是為了業務方便,獲取的是最大行限制的那一行最後一個字元,如果傳入的文字不到最大行數限制,那麼就返回-1,這個函式的作用是如果你要做行數“顯示全部”限制的話,你知道該從一段文字的哪個地方開始截斷。注意下面這個函式一定要在主執行緒進行執行
下面這個函式是在上面函式的基礎上,在不繪製UI的前提下,計算一段文字顯示的高度,獲得它高度的主要目的是為了佔位高度,免得上下滑動的時候螢幕跳躍,方便非同步的顯示這些文字。下面的程式碼在邏輯上做了相應的具體業務的處理,如果文字沒有超出最大行數,那麼就返回這段文字實際高度,如果超過了最大行數,那麼就只返回最大行數之內的文字的高度/** * get the last char index for max limit row,if not exceed the limit,return -1 * @param textView * @param content * @param width * @param maxLine * @return */ public static int getLastCharIndexForLimitTextView(TextView textView, String content, int width, int maxLine){ Log.i("Alex","寬度是"+width); TextPaint textPaint = textView.getPaint(); StaticLayout staticLayout = new StaticLayout(content, textPaint, width, Layout.Alignment.ALIGN_NORMAL, 1, 0, false); if(staticLayout.getLineCount()>maxLine) return staticLayout.getLineStart(maxLine) - 1;//exceed else return -1;//not exceed the max line }
/**
* 在不繪製textView的情況下算出textView的高度,並且根據最大行數得到應該顯示最後一個字元的下標,請在主執行緒順序執行,第一個返回值是最後一個字元的下標,第二個返回值是TextView最終應該佔用的高度
* @param textView
* @param content
* @param width
* @param maxLine
* @return
*/
public static int[] measureTextViewHeight(TextView textView, String content, int width, int maxLine){
Log.i("Alex","寬度是"+width);
TextPaint textPaint = textView.getPaint();
StaticLayout staticLayout = new StaticLayout(content, textPaint, width, Layout.Alignment.ALIGN_NORMAL, 1, 0, false);
int[] result = new int[2];
if(staticLayout.getLineCount()>maxLine) {//如果行數超出限制
int lastIndex = staticLayout.getLineStart(maxLine) - 1;
result[0] = lastIndex;
result[1] = new StaticLayout(content.substring(0, lastIndex), textPaint, width, Layout.Alignment.ALIGN_NORMAL, 1, 0, false).getHeight();
return result;
}else {//如果行數沒有超出限制
result[0] = -1;
result[1] = staticLayout.getHeight();
return result;
}
}
下面的函式就是上面效果展示中展示的例子,通過上面在不繪製UI的前提下獲得最大行末尾文字下標,然後讓源字串subString這個下標,在獲得的結果上加上“...read more”,然後將新增這一段文字設定點選事件,一個“顯示更多”的功能就做好了。
/**
* 限制為300字元,並且新增showmore和show more的點選事件
* @param summerize
* @param textView
* @param clickListener textview的clickListener
*/
public static void limitStringTo140(String summerize, final TextView textView, final View.OnClickListener clickListener){
final long startTime = System.currentTimeMillis();
if(textView==null)return;
int width = textView.getWidth();//在recyclerView和ListView中,由於複用的原因,這個TextView可能以前就畫好了,能獲得寬度
if(width==0)width = 1000;//獲取textview的實際寬度,這裡可以用各種方式(一般是dp轉px寫死)填入TextView的寬度
int lastCharIndex = getLastCharIndexForLimitTextView(textView,summerize,width,10);
if(lastCharIndex<0 && summerize.length() <= 300){//如果行數沒超過限制
textView.setText(summerize);
return;
}
//如果超出了行數限制
textView.setMovementMethod(LinkMovementMethod.getInstance());//this will deprive the recyclerView's focus
if(lastCharIndex>300 || lastCharIndex<0)lastCharIndex=300;
String explicitText = null;
if(summerize.charAt(lastCharIndex)=='\n'){//manual enter
explicitText = summerize.substring(0,lastCharIndex);
}else if(lastCharIndex > 12){//TextView auto enter
JLogUtils.i("Alex","the last char of this line is --"+lastCharIndex);
explicitText = summerize.substring(0,lastCharIndex-12);
}
int sourceLength = explicitText.length();
String showmore = "show more";
explicitText = explicitText + "..." + showmore;
final SpannableString mSpan = new SpannableString(explicitText);
final String finalSummerize = summerize;
mSpan.setSpan(new ClickableSpan() {
@Override
public void updateDrawState(TextPaint ds) {
super.updateDrawState(ds);
ds.setColor(textView.getResources().getColor(R.color.blue4d9cf2));
ds.setAntiAlias(true);
ds.setUnderlineText(false);
}
@Override
public void onClick(View widget) {//"...show more" click event
Log.i("Alex", "click showmore");
textView.setText(finalSummerize);
textView.setOnClickListener(null);
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
if (clickListener != null)
textView.setOnClickListener(clickListener);//prevent the double click
}
}, 20);
}
}, sourceLength, explicitText.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
textView.setText(mSpan);
Log.i("Alex", "字串處理耗時" + (System.currentTimeMillis() - startTime));
}