最近有需求需要在一个有限高度的页面上显示超过其高度的文字,当文字超过最大行之后显示省略号和查看更多,然后点击查看更多显示完整的信息,并且可以滚动。
先看效果图:
功能很简单,网上搜的有人使用了scrollview来滚动,再添加一个按钮”查看更多”,然后点击后把消息全部展示,再把按钮隐藏。
但是这样做不是觉得很复杂,其实所有的工作在一个textView里就可以完成了,包括滚动效果,包括查看更多的按钮。这些都可以在同一个textView里处理完,先上代码,后面再解释其原理。
我封装成了一个工具类,使用该工具类来处理TextView的显示逻辑
使用方法:
导入下面的工具类MoreTextUtil,在你需要设置TextView拥有展开效果的时候添加下面代码:
textView.setText("你要显示的内容");
MoreTextUtil.setMore(textView);
你也可以这样,使用重载的方法:
MoreTextUtil.setMore(textView,"你要显示的内容");
默认最大可显示行数为5行,如果需要自定义就给自己的TextView的布局加上下面的属性:
android:maxLines="9" //最多显示9行
用法很简单吧,下面上源码:
package com.shelwee.update.utils;
import android.annotation.SuppressLint;
import android.text.Layout;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.TextPaint;
import android.text.TextUtils.TruncateAt;
import android.text.method.LinkMovementMethod;
import android.text.method.ScrollingMovementMethod;
import android.text.style.ClickableSpan;
import android.view.View;
import android.view.ViewTreeObserver;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.widget.TextView;
import com.shelwee.updater.R;
public class MoreTextUtil {
public static boolean hasMesure = false; //是否已经执行过一次
public static void setMore(TextView textV,String content){
textV.setText(content);
setMore(textV,"...","查看更多");
}
public static void setMore(TextView textV){
setMore(textV,"...","查看更多");
}
@SuppressLint("NewApi")
public static void setMore(final TextView textV, final String ellipsis, final String strmore) {
if (textV == null) {
return;
}
if (2147483647 == textV.getMaxLines()) textV.setMaxLines(5);
textV.setEllipsize(TruncateAt.END);
textV.setVerticalScrollBarEnabled(true);
hasMesure = false;
//添加布局变化监听器,view 布局完成时调用,每次view改变时都会调用
ViewTreeObserver vto = textV.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
if (!hasMesure) {
int maxLines = textV.getMaxLines();
int lines = textV.getLineCount();
//如果文字的行数超过最大行数,展示缩略的textview
if (lines >= maxLines) {
Layout layout=textV.getLayout();
String str=layout.getText().toString();
int end = layout.getLineEnd(maxLines-2);
str = str.substring(0, end); //缩略的文字
String strall = textV.getText().toString(); //完整的文字
hasMesure = true;
SpannableString spanstr;
//如果以换行符结尾,则不再换行
if (str.endsWith("\n")) {
spanstr = new SpannableString(str + ellipsis + strmore);
}else {
spanstr = new SpannableString(str + "\n" + ellipsis + strmore);
}
//设置“查看更多”的点击事件
spanstr.setSpan(new MyClickableSpan(strall,textV.getResources().getColor(android.R.color.holo_green_dark)), spanstr.length() - strmore.length(),
spanstr.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
textV.setText(spanstr);
//移除默认背景色
textV.setHighlightColor(textV.getResources().getColor(android.R.color.transparent));
textV.setMovementMethod(LinkMovementMethod.getInstance());
}
}
}
});
}
static class MyClickableSpan extends ClickableSpan{
private String str;
private int color;
public MyClickableSpan(String str,int color) {
this.str = str;
this.color = color;
}
@Override
public void onClick(View view) {
((TextView)view).setMovementMethod(new ScrollingMovementMethod());
((TextView)view).setText(str);
}
@Override
public void updateDrawState(TextPaint ds) {
super.updateDrawState(ds);
ds.setColor(color); //设置“查看更多”字体颜色
ds.setUnderlineText(false); //设置“查看更多”无下划线,默认有
ds.clearShadowLayer();
}
}
}
代码中的注释很清楚,说下重点:
代码中为什么要使用布局变化监听器呢,如下:
ViewTreeObserver vto = textV.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener(){
....
});
原因是TextView会根据字符的排版规则自动换行,这种换行受字体大小的影响,而且英文字母符号中文等不同字符占的宽并不同,而且TextView还会自动匹配单词选择性换行,所以我们几乎无法通过一个字符串计算出TextView会在什么时候换行,我们也无法计算出在字符串的哪个位置会因为显示不下而出现截断。
那么我们就换一种方式,直接把字符串绘制到TextView中,在TextView生成布局渲染完成后把字符设置到页面前一刻再回调我们的方法,这样我们就可以得到TextView具体绘制了多少行,每行的字符是什么,这也就是为什么要使用ViewTreeObserver 添加 OnGlobalLayoutListener 的原因。在onGlobalLayout回调方法中完成我们的业务逻辑。
但是值得注意的是,OnGlobalLayoutListener 会在绘制过程中被调用多少,而我们的业务逻辑只需要处理一次就可以了,为了避免不必要的性能损失,我们得加以控制,通过hasMesure 这个布尔变量来控制代码只执行一次。
另外一个重要的地方就是,我们没有添加按钮,那就以为着我们的TextView必须要支持局部点击事件,就是说,点击“查看更多”的时候要有响应,而点其他地方没有。还好TextView的SpannableString (复合文本) 支持多事件和样式的处理,如下:
//设置“查看更多”的点击事件
spanstr.setSpan(new MyClickableSpan(strall,textV.getResources().getColor(R.color.cc_orage)),
spanstr.length() - strmore.length(),
spanstr.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
textV.setText(spanstr);
这里就使用了复合文本格式来设置局部的点击事件,方法原型如下:
spanstr.setSpan(Object what,int start,int end,int flags)
- 1
what 参数可以设置一个监听器
start 指定了设置的字符的开始位置
end 是指定字符的结束位置
flags 是间隔模式,SPAN_EXCLUSIVE_EXCLUSIVE表示不影响前后相邻的字符
接着看复合文本事件监听器:
static class MyClickableSpan extends ClickableSpan{
private String str;
private int color;
public MyClickableSpan(String str,int color) {
this.str = str;
this.color = color;
}
@Override
public void onClick(View view) {
((TextView)view).setMovementMethod(new ScrollingMovementMethod());
((TextView)view).setText(str);
}
@Override
public void updateDrawState(TextPaint ds) {
super.updateDrawState(ds);
ds.setColor(color); //设置“查看更多”字体颜色
ds.setUnderlineText(false); //设置“查看更多”无下划线,默认有
ds.clearShadowLayer();
}
}
构造方法我传入了完整的字符串和一个颜色值,当点击后把显示的字符串换成完整的字符,并设置可以滚动就ok,至于颜色值,我希望“查看更多”这4个字拥有不同的颜色,当然你不设置也没有什么关系,丑一点而已。
在点击事件执行之前,会先调用updateDrawState(TextPaint ds)方法设置绘制格式,在这里设置颜色和取消下划线,如果需要其他特殊效果的话也在这里处理。
觉得不错的下面有个顶,可以点一下 :)