网上很多TextView的“显示全部”,“显示更多”的方案实现都是两个TextView,一个在上面显示内容,一个在下面用来点击。但是我在实际工作中遇到的需求是“显示全部”提示要内联在原文的后面,使用一个TextView进行显示,不能放在原文的下面,下面把代码贴一下,主要实现的功能如下:
1、“显示全部”/“显示更多”紧连在正文的后面,不能另起一行
2、当字数超过一定数量显示“显示更多”,
3、当行数超过一定数量显示“显示更多”,比如每行只有一个字,不停的换行,虽然字少但是行数多,也应该将限制之外的行全部省略掉
效果展示
实现起来非常简单,主要步骤如下
1、首先判断要处理段落的字数是否超过限制,如果超过就在后面缀上“显示更多”
2、判断要处理段落在某个TextView上完整显示的行数,如果行数超过限制,那么就显示“显示全部”
3、使用SpannableString,构造:削减后的段落+“...显示更多”。然后将最后“...显示更多”这个字使用ClickableSpan设置上点击事件
有以下几个难点
1、如何在不进行UI绘制的情况下拿到TextView显示某段文字能显示多少行
2、如何获得第x行最后一个字的下标以便从此截取
3、在异步处理的环境中,如何在不进行绘制的情况下获得TextView会画多高以便预留位置
首先下面这段代码是通过传入一个TextView及其宽度,然后获得任意一行最末那个字符的下标的方法,只是为了业务方便,获取的是最大行限制的那一行最后一个字符,如果传入的文字不到最大行数限制,那么就返回-1,这个函数的作用是如果你要做行数“显示全部”限制的话,你知道该从一段文字的哪个地方开始截断。注意下面这个函数一定要在主线程进行执行
/**
* 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
}
下面这个函数是在上面函数的基础上,在不绘制UI的前提下,计算一段文本显示的高度,获得它高度的主要目的是为了占位高度,免得上下滑动的时候屏幕跳跃,方便异步的显示这些文字。下面的代码在逻辑上做了相应的具体业务的处理,如果文字没有超出最大行数,那么就返回这段文字实际高度,如果超过了最大行数,那么就只返回最大行数之内的文本的高度
/**
* 在不绘制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));
}