在开发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);
}
初始效果:
在start、end索引处插入字符串,效果
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);
}
效果:
5、 UnderlineSpan 下划线
public void func8(){
SpannableString ss = new SpannableString(text);
ss.setSpan(new UnderlineSpan(), 2, 4, Spanned.SPAN_EXCLUSIVE_INCLUSIVE);
tv.setText(ss);
}
效果:
6、ImageSpan 图片
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);
}
效果:
8、 URLSpan 文本超链接
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());
}
效果
对于如下面这样的内容,用这个在合适不过了,可以避免定义过多的TextView,当需求增加要显示另一种消息的格式时,不用再去定义一种XML布局。
TextUtils
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
对于这个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) {
}
public static Spanned fromHtml(String source, int flags) {
}
@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) {
mTagHandler.handleTag(false, tag, mSpannableStringBuilder, mReader);
}
}