在開發(fā)Android應(yīng)用中,常用的控件就是TextView、Button、EditText等,打交道最多的就是文本字符串了。在此分享一些字符串顯示、處理的一些技巧。
String
String 字符串,我們應(yīng)用中展示的文本信息,大多是以String的形式展示的。
CharSequence
細(xì)心的朋友留意到,TextView的setText(CharSequence text)的參數(shù)、getText()返回的值,不是String類型的,而是CharSequence類型的。那CharSequence是什么呢?它和String又有什么關(guān)系呢?
CharSequence,字面翻譯即字符序列,也就是字符串。CharSequence,它是一個(gè)接口interface。
public interface CharSequence {
//獲取字符串長(zhǎng)度
int length();
//返回指定索引出的字符
char charAt(int var1);
//截取指定索引區(qū)間的子字符串
CharSequence subSequence(int var1, int var2);
//轉(zhuǎn)為String類型
String toString();
default IntStream chars() {
throw new RuntimeException("Stub!");
}
default IntStream codePoints() {
throw new RuntimeException("Stub!");
}
}
CharSequence是一個(gè)接口,那String一定是繼承了CharSequence,看一下源碼:
public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
……
}
原來(lái)CharSequence是一個(gè)接口,String實(shí)現(xiàn)了這個(gè)接口。
StringBuffer、StringBuilder
StringBuffer、StringBuilder這兩個(gè)比較相似,都是字符串緩沖區(qū),用于處理字符串的拼接append、插入insert操作,都實(shí)現(xiàn)了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 + "");
}
打印的日志:

當(dāng)字符串計(jì)算量超大時(shí),使用StringBuffer、StringBuilder,顯著的優(yōu)點(diǎn)計(jì)算速度快、內(nèi)存開銷小。
以上是StringBuffer、StringBuilder的相同點(diǎn),他們的不同點(diǎn)有:
- StringBuilder比StringBuffer速度快
- StringBuffer線程安全,可將字符串緩沖區(qū)安全地用于多個(gè)線程,不需要額外的同步用于多線程中。StringBuilder線程不安全。
SpannableString
SpannableString,可以理解為帶樣式的字符串,它也實(shí)現(xiàn)了CharSequence接口
public class SpannableString extends SpannableStringInternal implements CharSequence, GetChars, Spannable{
……
}
那都可以帶哪些樣式呢?
- BackgroundColorSpan 背景色
- ClickableSpan 文本可點(diǎn)擊,有點(diǎn)擊事件
- ForegroundColorSpan 文本顏色(前景色)
- MaskFilterSpan 修飾效果,如模糊(BlurMaskFilter)、浮雕(EmbossMaskFilter)
- MetricAffectingSpan 父類,一般不用
- RasterizerSpan 光柵效果
- StrikethroughSpan 刪除線(中劃線)
- SuggestionSpan 相當(dāng)于占位符
- UnderlineSpan 下劃線
- AbsoluteSizeSpan 絕對(duì)大?。ㄎ谋咀煮w)
- DynamicDrawableSpan 設(shè)置圖片,基于文本基線或底部對(duì)齊。
- ImageSpan 圖片
- RelativeSizeSpan 相對(duì)大?。ㄎ谋咀煮w)
- ReplacementSpan 父類,一般不用
- ScaleXSpan 基于x軸縮放
- StyleSpan 字體樣式:粗體、斜體等
- SubscriptSpan 下標(biāo)(數(shù)學(xué)公式會(huì)用到)
- SuperscriptSpan 上標(biāo)(數(shù)學(xué)公式會(huì)用到)
- 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 :對(duì)應(yīng)的各種Span,后面會(huì)提到;
int start:開始應(yīng)用指定Span的位置,索引從0開始
int end:結(jié)束應(yīng)用指定Span的位置,特效并不包括這個(gè)位置。比如如果這里數(shù)為3(即第4個(gè)字符),第4個(gè)字符不會(huì)有任何特效。從下面的例子也可以看出來(lái)。
int flags:取值有如下四個(gè)
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE:前后都不包括,即在指定范圍的前面和后面插入新字符都不會(huì)應(yīng)用新樣式
Spannable.SPAN_EXCLUSIVE_INCLUSIVE:前面不包括,后面包括。即僅在范圍字符的后面插入新字符時(shí)會(huì)應(yīng)用新樣式
Spannable.SPAN_INCLUSIVE_EXCLUSIVE:前面包括,后面不包括。
Spannable.SPAN_INCLUSIVE_INCLUSIVE:前后都包括。
當(dāng)給TextView的子串設(shè)置樣式時(shí),這些都一樣的。只有當(dāng)子串的內(nèi)容改變時(shí),flag才會(huì)影響插入字符在start、end索引處的效果,其實(shí)就是對(duì)EditText設(shè)置的有用。
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 文本可點(diǎn)擊,有點(diǎn)擊事件
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.如果不設(shè)置會(huì)不起作用setMovementMethod(LinkMovementMethod.getInstance());
效果:

可以重寫ClickableSpan類來(lái)去掉下劃線樣式,以及改變點(diǎn)擊后的字體顏色,有興趣的可以自己試試。
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 上標(biāo)(數(shù)學(xué)公式會(huì)用到)
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,使字符串具有多種樣式。而且這兩個(gè)都實(shí)現(xiàn)了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());
}
效果

對(duì)于如下面這樣的內(nèi)容,用這個(gè)在合適不過(guò)了,可以避免定義過(guò)多的TextView,當(dāng)需求增加要顯示另一種消息的格式時(shí),不用再去定義一種XML布局。

TextUtils
TextUtils 文本輔助類,位于android.text.TextUtils,Android提供的專門用于處理字符串文本的。
該類下常用的方法:
public void func13(){
Log.d("===", "---------------------------------");
//字符串拼接
Log.d("===", TextUtils.concat("Hello", " ", "world!").toString());
//判斷是否為空字符串
Log.d("===", TextUtils.isEmpty("Hello") + "");
//判斷是否只有數(shù)字
Log.d("===", TextUtils.isDigitsOnly("Hello") + "");
//判斷字符串是否相等
Log.d("===", TextUtils.equals("Hello", "Hello") + "");
//獲取字符串的倒序
Log.d("===", TextUtils.getReverse("Hello", 0, "Hello".length()).toString());
//獲取字符串的長(zhǎng)度
Log.d("===", TextUtils.getTrimmedLength("Hello world!") + "");
Log.d("===", TextUtils.getTrimmedLength(" Hello world! ") + "");
//獲取html格式的字符串 , 將<、>、\、空格……轉(zhuǎn)為html定義的 < >等
Log.d("===", TextUtils.htmlEncode("<html>\n" +
"<body>\n" +
"這是一個(gè)非常簡(jiǎn)單的HTML。\n" +
"</body>\n" +
"</html>"));
//獲取字符串中第一次出現(xiàn)子字符串的字符位置
Log.d("===", TextUtils.indexOf("Hello world!", "Hello") + "");
//截取字符串
Log.d("===", TextUtils.substring("Hello world!", 0, 5));
//通過(guò)表達(dá)式截取字符串
Log.d("===", TextUtils.split(" Hello world! ", " ")[0]);
}
打印的結(jié)果:

Html
對(duì)于這個(gè)html文本我們要怎么顯示呢?
<p>用戶就診須知:</p><p>1.用戶添加就診人,就診卡請(qǐng)確保其內(nèi)容真實(shí)性!</p><p>2.添加的就診人與其就診卡為仁濟(jì)醫(yī)院真實(shí)存在。</p><p>3.不支持初次就診。</p><p>4.一個(gè)賬號(hào)最多只能綁定兩個(gè)就診人。</p>
用WebView顯示,大材小用,不好。Android提供了一個(gè)Html類,專門用于處理,這個(gè)附帶html樣式的文本,支持的標(biāo)簽也很多,使用也很簡(jiǎn)單。
先看怎么使用:
public void func14(){
String text = "<p>用戶就診須知:</p><p>1.用戶添加就診人,就診卡請(qǐng)確保其內(nèi)容真實(shí)性!</p><p>2.添加的就診人與其就診卡為仁濟(jì)醫(yī)院真實(shí)存在。</p><p>3.不支持初次就診。</p><p>4.一個(gè)賬號(hào)最多只能綁定兩個(gè)就診人。</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 : 當(dāng)html文本字符串帶圖片時(shí),用來(lái)處理圖片的加載,重寫其方法即可,可以從應(yīng)用內(nèi)部、手機(jī)內(nèi)存、網(wǎng)絡(luò)加載圖片。
這里不再舉例子,可參考這篇博客:Android中Textview顯示帶html文本二-------【Textview顯示本地圖片】
Html.TagHandler tagHandler : html文本中可以自定義標(biāo)簽,自己使用這個(gè)自己解析。
Spanned : 是一個(gè)接口,它繼承了CharSequence接口,所以可以直接用于TextView
查看Html的源碼,可以知道Html支持解析的標(biāo)簽有:
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);
}
}