您的位置:首页 > 移动开发 > Android开发

android开发游记:textview超过长度点击展开自动滚动(在一个TextView中实现,不增加布局复杂度)

2017-12-26 11:27 1076 查看
最近有需求需要在一个有限高度的页面上显示超过其高度的文字,当文字超过最大行之后显示省略号和查看更多,然后点击查看更多显示完整的信息,并且可以滚动。

先看效果图:



功能很简单,网上搜的有人使用了scrollview来滚动,再添加一个按钮”查看更多”,然后点击后把消息全部展示,再把按钮隐藏。

但是这样做不是觉得很复杂,其实所有的工作在一个textView里就可以完成了,包括滚动效果,包括查看更多的按钮。这些都可以在同一个textView里处理完,先上代码,后面再解释其原理。

我封装成了一个工具类,使用该工具类来处理TextView的显示逻辑

使用方法:

导入下面的工具类MoreTextUtil,在你需要设置TextView拥有展开效果的时候添加下面代码:
textView.setText("你要显示的内容");
MoreTextUtil.setMore(textView);
1
2

你也可以这样,使用重载的方法:
MoreTextUtil.setMore(textView,"你要显示的内容");
1

默认最大可显示行数为5行,如果需要自定义就给自己的TextView的布局加上下面的属性:
android:maxLines="9"    //最多显示9行
1

用法很简单吧,下面上源码:
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();
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107

代码中的注释很清楚,说下重点: 

代码中为什么要使用布局变化监听器呢,如下:
ViewTreeObserver vto = textV.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener(){
....
});
1
2
3
4

原因是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);
1
2
3
4
5
6

这里就使用了复合文本格式来设置局部的点击事件,方法原型如下:

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();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

构造方法我传入了完整的字符串和一个颜色值,当点击后把显示的字符串换成完整的字符,并设置可以滚动就ok,至于颜色值,我希望“查看更多”这4个字拥有不同的颜色,当然你不设置也没有什么关系,丑一点而已。 

在点击事件执行之前,会先调用updateDrawState(TextPaint ds)方法设置绘制格式,在这里设置颜色和取消下划线,如果需要其他特殊效果的话也在这里处理。

觉得不错的下面有个顶,可以点一下 :)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  android