在开发Android应用中,常用的控件就是TextView、Button、EditText等,打交道最多的就是文本字符串了。在此分享一些字符串显示、处理的一些技巧。
String
String 字符串,我们应用中展示的文本信息,大多是以String的形式展示的。
CharSequence
细心的朋友留意到,TextView的setText(CharSequence text)的参数、getText()返回的值,不是String类型的,而是CharSequence类型的。那CharSequence是什么呢?它和String又有什么关系呢?
CharSequence,字面翻译即字符序列,也就是字符串。CharSequence,它是一个接口interface。
public interface CharSequence {
//获取字符串长度
int length();
//返回指定索引出的字符
char charAt(int var1);
//截取指定索引区间的子字符串
CharSequence subSequence(int var1, int var2);
//转为String类型
String toString();
default IntStream chars() {
throw new RuntimeException("Stub!");
default IntStream codePoints() {
throw new RuntimeException("Stub!");
CharSequence是一个接口,那String一定是继承了CharSequence,看一下源码:
public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
原来CharSequence是一个接口,String实现了这个接口。
StringBuffer、StringBuilder
StringBuffer、StringBuilder这两个比较相似,都是字符串缓冲区,用于处理字符串的拼接append、插入insert操作,都实现了CharSequence接口,TextView的setText方法可以直接使用。在拼接字符串上面都比String之间的“+”操作快,尤其是大量的字符串拼接操作。
private void func3() {
String str = "123";
long startTime_1 = new Date().getTime();
for (int i = 0; i < 100000; i ++){
str += "1";
logTimeInterval("String", startTime_1);
StringBuffer sf = new StringBuffer("123");
long startTime_2 = new Date().getTime();
for (int i = 0; i < 100000; i ++){
sf.append("1");
logTimeInterval("StringBuffer", startTime_2);
StringBuilder sb = new StringBuilder("123");
long startTime_3 = new Date().getTime();
for (int i = 0; i < 100000; i ++){
sb.append("1");
logTimeInterval("StringBuilder", startTime_3);
private void logTimeInterval(String tag, long startTime){
long interval = new Date().getTime() - startTime;
Log.i(tag, interval + "");
打印的日志:
当字符串计算量超大时,使用StringBuffer、StringBuilder,显著的优点计算速度快、内存开销小。
以上是StringBuffer、StringBuilder的相同点,他们的不同点有:
StringBuilder比StringBuffer速度快
StringBuffer线程安全,可将字符串缓冲区安全地用于多个线程,不需要额外的同步用于多线程中。StringBuilder线程不安全。
SpannableString
SpannableString,可以理解为带样式的字符串,它也实现了CharSequence接口
public class SpannableString extends SpannableStringInternal implements CharSequence, GetChars, Spannable{
那都可以带哪些样式呢?
BackgroundColorSpan 背景色
ClickableSpan 文本可点击,有点击事件
ForegroundColorSpan 文本颜色(前景色)
MaskFilterSpan 修饰效果,如模糊(BlurMaskFilter)、浮雕(EmbossMaskFilter)
MetricAffectingSpan 父类,一般不用
RasterizerSpan 光栅效果
StrikethroughSpan 删除线(中划线)
SuggestionSpan 相当于占位符
UnderlineSpan 下划线
AbsoluteSizeSpan 绝对大小(文本字体)
DynamicDrawableSpan 设置图片,基于文本基线或底部对齐。
ImageSpan 图片
RelativeSizeSpan 相对大小(文本字体)
ReplacementSpan 父类,一般不用
ScaleXSpan 基于x轴缩放
StyleSpan 字体样式:粗体、斜体等
SubscriptSpan 下标(数学公式会用到)
SuperscriptSpan 上标(数学公式会用到)
TextAppearanceSpan 文本外貌(包括字体、大小、样式和颜色)
TypefaceSpan 文本字体
URLSpan 文本超链接
1、 BackgroundColorSpan 背景色
public void func4(){
SpannableString ss = new SpannableString(text);
ss.setSpan(new BackgroundColorSpan(Color.BLUE), 2, 4, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
tv.setText(ss);
SpannableString的setSpan方法:
public void setSpan(Object what, int start, int end, int flags) {
super.setSpan(what, start, end, flags);
object what :对应的各种Span,后面会提到;
int start:开始应用指定Span的位置,索引从0开始
int end:结束应用指定Span的位置,特效并不包括这个位置。比如如果这里数为3(即第4个字符),第4个字符不会有任何特效。从下面的例子也可以看出来。
int flags:取值有如下四个
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE:前后都不包括,即在指定范围的前面和后面插入新字符都不会应用新样式
Spannable.SPAN_EXCLUSIVE_INCLUSIVE:前面不包括,后面包括。即仅在范围字符的后面插入新字符时会应用新样式
Spannable.SPAN_INCLUSIVE_EXCLUSIVE:前面包括,后面不包括。
Spannable.SPAN_INCLUSIVE_INCLUSIVE:前后都包括。
当给TextView的子串设置样式时,这些都一样的。只有当子串的内容改变时,flag才会影响插入字符在start、end索引处的效果,其实就是对EditText设置的有用。
public void func1(){
SpannableString ss = new SpannableString(text);
ss.setSpan(new ForegroundColorSpan(Color.BLUE), 2, 4, Spanned.SPAN_EXCLUSIVE_INCLUSIVE);
et.setText(ss);
初始效果:
2、 ClickableSpan 文本可点击,有点击事件
public void func5(){
SpannableString ss = new SpannableString(text);
ClickableSpan span = new ClickableSpan() {
@Override
public void onClick(View view) {
Toast.makeText(MainActivity.this, "ClickableSpan", Toast.LENGTH_LONG).show();
ss.setSpan(span, 2, 4, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
tv.setText(ss);
tv.setMovementMethod(LinkMovementMethod.getInstance());
PS.如果不设置会不起作用setMovementMethod(LinkMovementMethod.getInstance());
可以重写ClickableSpan类来去掉下划线样式,以及改变点击后的字体颜色,有兴趣的可以自己试试。
3、 ForegroundColorSpan 文本颜色(前景色)
public void func6(){
SpannableString ss = new SpannableString(text);
ss.setSpan(new ForegroundColorSpan(Color.BLUE), 2, 4, Spanned.SPAN_EXCLUSIVE_INCLUSIVE);
tv.setText(ss);
4、 StrikethroughSpan 删除线(中划线)
public void func7(){
SpannableString ss = new SpannableString(text);
ss.setSpan(new StrikethroughSpan(), 2, 4, Spanned.SPAN_EXCLUSIVE_INCLUSIVE);
tv.setText(ss);
public void func8(){
SpannableString ss = new SpannableString(text);
ss.setSpan(new UnderlineSpan(), 2, 4, Spanned.SPAN_EXCLUSIVE_INCLUSIVE);
tv.setText(ss);
public void func9(){
SpannableString ss = new SpannableString(text);
Drawable d = getResources().getDrawable(R.mipmap.ic_launcher);
d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
ImageSpan span = new ImageSpan(d, DynamicDrawableSpan.ALIGN_BASELINE);
ss.setSpan(span, 2, 4, Spanned.SPAN_EXCLUSIVE_INCLUSIVE);
tv.setText(ss);
7、 SuperscriptSpan 上标(数学公式会用到)
public void func10(){
SpannableString ss = new SpannableString(text);
SuperscriptSpan superscriptSpan = new SuperscriptSpan();
ss.setSpan(superscriptSpan, 2, 4, Spanned.SPAN_EXCLUSIVE_INCLUSIVE);
tv.setText(ss);
public void func11(){
SpannableString ss = new SpannableString(text);
URLSpan urlSpan = new URLSpan("http://www.baidu.com");
ss.setSpan(urlSpan, 2, 4, Spanned.SPAN_EXCLUSIVE_INCLUSIVE);
tv.setText(ss);
tv.setMovementMethod(new LinkMovementMethod());
PS.不要忘了setMovementMethod(new LinkMovementMethod());
举得是一些常用的例子,其他的如果感兴趣可以自己试试,用法都是类似的。
SpannableStringBuilder
SpannableString与SpannableStringBuilder和String与StringBuilder类似,SpannableStringBuilder可以拼接各种的SpannableString,使字符串具有多种样式。而且这两个都实现了CharSequence接口,TextView的setText方法可以直接使用。
public void func12(){
SpannableString ss1 = new SpannableString(text);
SuperscriptSpan superscriptSpan = new SuperscriptSpan();
ss1.setSpan(superscriptSpan, 2, 4, Spanned.SPAN_EXCLUSIVE_INCLUSIVE);
SpannableString ss2 = new SpannableString(text);
URLSpan urlSpan = new URLSpan("http://www.baidu.com");
ss2.setSpan(urlSpan, 2, 4, Spanned.SPAN_EXCLUSIVE_INCLUSIVE);
SpannableStringBuilder ssb = new SpannableStringBuilder();
ssb.append(ss1);
ssb.append(ss2);
tv.setText(ssb);
tv.setMovementMethod(new LinkMovementMethod());
TextUtils 文本辅助类,位于android.text.TextUtils,Android提供的专门用于处理字符串文本的。
该类下常用的方法:
public void func13(){
Log.d("===", "---------------------------------");
//字符串拼接
Log.d("===", TextUtils.concat("Hello", " ", "world!").toString());
//判断是否为空字符串
Log.d("===", TextUtils.isEmpty("Hello") + "");
//判断是否只有数字
Log.d("===", TextUtils.isDigitsOnly("Hello") + "");
//判断字符串是否相等
Log.d("===", TextUtils.equals("Hello", "Hello") + "");
//获取字符串的倒序
Log.d("===", TextUtils.getReverse("Hello", 0, "Hello".length()).toString());
//获取字符串的长度
Log.d("===", TextUtils.getTrimmedLength("Hello world!") + "");
Log.d("===", TextUtils.getTrimmedLength(" Hello world! ") + "");
//获取html格式的字符串 , 将<、>、\、空格……转为html定义的 < >等
Log.d("===", TextUtils.htmlEncode("<html>\n" +
"<body>\n" +
"这是一个非常简单的HTML。\n" +
"</body>\n" +
"</html>"));
//获取字符串中第一次出现子字符串的字符位置
Log.d("===", TextUtils.indexOf("Hello world!", "Hello") + "");
//截取字符串
Log.d("===", TextUtils.substring("Hello world!", 0, 5));
//通过表达式截取字符串
Log.d("===", TextUtils.split(" Hello world! ", " ")[0]);
打印的结果:
对于这个html文本我们要怎么显示呢?
<p>用户就诊须知:</p><p>1.用户添加就诊人,就诊卡请确保其内容真实性!</p><p>2.添加的就诊人与其就诊卡为仁济医院真实存在。</p><p>3.不支持初次就诊。</p><p>4.一个账号最多只能绑定两个就诊人。</p>
用WebView显示,大材小用,不好。Android提供了一个Html类,专门用于处理,这个附带html样式的文本,支持的标签也很多,使用也很简单。
先看怎么使用:
public void func14(){
String text = "<p>用户就诊须知:</p><p>1.用户添加就诊人,就诊卡请确保其内容真实性!</p><p>2.添加的就诊人与其就诊卡为仁济医院真实存在。</p><p>3.不支持初次就诊。</p><p>4.一个账号最多只能绑定两个就诊人。</p>";
tv.setText(Html.fromHtml(text));
@Deprecated
public static Spanned fromHtml(String source, Html.ImageGetter imageGetter, Html.TagHandler tagHandler) {
public static Spanned fromHtml(String source, int flags, Html.ImageGetter imageGetter, Html.TagHandler tagHandler) {
String source : html文本字符串
int flags : 文本显示的模式(有很多种,有兴趣的可以试试)
Html.ImageGetter imageGetter : 当html文本字符串带图片时,用来处理图片的加载,重写其方法即可,可以从应用内部、手机内存、网络加载图片。
这里不再举例子,可参考这篇博客:Android中Textview显示带html文本二-------【Textview显示本地图片】
Html.TagHandler tagHandler : html文本中可以自定义标签,自己使用这个自己解析。
Spanned : 是一个接口,它继承了CharSequence接口,所以可以直接用于TextView
查看Html的源码,可以知道Html支持解析的标签有:
private void handleEndTag(String tag) {
if (tag.equalsIgnoreCase("br")) {
handleBr(mSpannableStringBuilder);
} else if (tag.equalsIgnoreCase("p")) {
endCssStyle(mSpannableStringBuilder);
endBlockElement(mSpannableStringBuilder);
} else if (tag.equalsIgnoreCase("ul")) {
endBlockElement(mSpannableStringBuilder);
} else if (tag.equalsIgnoreCase("li")) {
endLi(mSpannableStringBuilder);
} else if (tag.equalsIgnoreCase("div")) {
endBlockElement(mSpannableStringBuilder);
} else if (tag.equalsIgnoreCase("span")) {
endCssStyle(mSpannableStringBuilder);
} else if (tag.equalsIgnoreCase("strong")) {
end(mSpannableStringBuilder, Bold.class, new StyleSpan(Typeface.BOLD));
} else if (tag.equalsIgnoreCase("b")) {
end(mSpannableStringBuilder, Bold.class, new StyleSpan(Typeface.BOLD));
} else if (tag.equalsIgnoreCase("em")) {
end(mSpannableStringBuilder, Italic.class, new StyleSpan(Typeface.ITALIC));
} else if (tag.equalsIgnoreCase("cite")) {
end(mSpannableStringBuilder, Italic.class, new StyleSpan(Typeface.ITALIC));
} else if (tag.equalsIgnoreCase("dfn")) {
end(mSpannableStringBuilder, Italic.class, new StyleSpan(Typeface.ITALIC));
} else if (tag.equalsIgnoreCase("i")) {
end(mSpannableStringBuilder, Italic.class, new StyleSpan(Typeface.ITALIC));
} else if (tag.equalsIgnoreCase("big")) {
end(mSpannableStringBuilder, Big.class, new RelativeSizeSpan(1.25f));
} else if (tag.equalsIgnoreCase("small")) {
end(mSpannableStringBuilder, Small.class, new RelativeSizeSpan(0.8f));
} else if (tag.equalsIgnoreCase("font")) {
endFont(mSpannableStringBuilder);
} else if (tag.equalsIgnoreCase("blockquote")) {
endBlockquote(mSpannableStringBuilder);
} else if (tag.equalsIgnoreCase("tt")) {
end(mSpannableStringBuilder, Monospace.class, new TypefaceSpan("monospace"));
} else if (tag.equalsIgnoreCase("a")) {
endA(mSpannableStringBuilder);
} else if (tag.equalsIgnoreCase("u")) {
end(mSpannableStringBuilder, Underline.class, new UnderlineSpan());
} else if (tag.equalsIgnoreCase("del")) {
end(mSpannableStringBuilder, Strikethrough.class, new StrikethroughSpan());
} else if (tag.equalsIgnoreCase("s")) {
end(mSpannableStringBuilder, Strikethrough.class, new StrikethroughSpan());
} else if (tag.equalsIgnoreCase("strike")) {
end(mSpannableStringBuilder, Strikethrough.class, new StrikethroughSpan());
} else if (tag.equalsIgnoreCase("sup")) {
end(mSpannableStringBuilder, Super.class, new SuperscriptSpan());
} else if (tag.equalsIgnoreCase("sub")) {
end(mSpannableStringBuilder, Sub.class, new SubscriptSpan());
} else if (tag.length() == 2 &&
Character.toLowerCase(tag.charAt(0)) == 'h' &&
tag.charAt(1) >= '1' && tag.charAt(1) <= '6') {
endHeading(mSpannableStringBuilder);
} else if (mTagHandler != null) {